Razor Panel for a tooltip-style popup that lists the skill combinations between two linked squad players, including skill badges, effect text, out-of-position warnings and bilingual labels. Each tick it positions and sizes itself next to an anchor rect in screen space.
@using Sandbox
@using Sandbox.UI
@using Socer
@using System
@inherits Panel
<root class="squad-link-popup localized-text @(Connection==null?"hidden":"")">
@if(Connection is { } selected)
{
<div class="squad-link-tooltip" @ref="TipPanel">
<div class="squad-link-tooltip-heading"><label>@T("Связка скиллов","Skill combination")</label>@if(Pinned){<button class="squad-link-close" onclick=@(()=>OnClose?.Invoke())>✕</button>}</div>
<div class="squad-link-players"><label>@selected.First.Name</label><span>↔</span><label>@selected.Second.Name</label></div>
@foreach(var pair in selected.Pairs)
{
<div class="squad-link-pair">
<div class="squad-link-pair-skills"><div><SkillBadgeView Skill="@pair.First.Skill" Grade="@pair.First.Grade" CssClass="squad-link-skill-icon" /><label>@UniqueSkills.Name(pair.First.Skill,GamePreferences.Russian)</label></div><span>+</span><div><SkillBadgeView Skill="@pair.Second.Skill" Grade="@pair.Second.Grade" CssClass="squad-link-skill-icon" /><label>@UniqueSkills.Name(pair.Second.Skill,GamePreferences.Russian)</label></div></div>
<label class="squad-link-effect">@pair.Effect(GamePreferences.Russian)</label>
</div>
}
@if(Formation.Fit(selected.First,Career.Shape,selected.FirstSlot)<.7f || Formation.Fit(selected.Second,Career.Shape,selected.SecondSlot)<.7f){<label class="squad-link-position-note">@T("Один из игроков не на своей позиции — часть взаимодействий может не сработать.","One player is out of position — some combinations may not activate.")</label>}
<label class="squad-link-condition">@T("Работает в подходящих эпизодах матча.","Works in suitable match situations.")</label>
</div>
}
</root>
@code {
public SquadSkillConnection Connection {get;set;}
public ClubCareer Career {get;set;}
public bool Pinned {get;set;}
public Action OnClose {get;set;}
public Rect Anchor {get;set;}
Panel TipPanel;
Panel positionedPanel;
float positionedLeft=float.NaN,positionedTop=float.NaN,positionedWidth=float.NaN,positionedMaxHeight=float.NaN;
public bool TipHovered=>TipPanel?.HasHovered==true;
static string T(string ru,string en)=>GamePreferences.Russian?ru:en;
static Rect Bounds(Panel panel){var a=panel.PanelPositionToScreenPosition(Vector2.Zero);var b=panel.PanelPositionToScreenPosition(new Vector2(panel.Box.Rect.Width,panel.Box.Rect.Height));return new Rect(a.x,a.y,b.x-a.x,b.y-a.y);}
public override void Tick()
{
base.Tick();if(TipPanel==null || Connection==null)return;
var root=FindRootPanel();if(root==null)return;
var bounds=Bounds(root);float scale=root.ScaleFromScreen;
float w=bounds.Width*scale,h=bounds.Height*scale;
float width=Math.Min(380,w-24),height=Math.Max(180,TipPanel.Box.Rect.Height*scale);
float x=(Anchor.Right-bounds.Left)*scale+12;
if(x+width>w-12)x=(Anchor.Left-bounds.Left)*scale-width-12;
float y=(Anchor.Center.y-bounds.Top)*scale-30;
x=MathF.Round(Math.Clamp(x,12,Math.Max(12,w-width-12))*4)/4;
y=MathF.Round(Math.Clamp(y,12,Math.Max(12,h-height-12))*4)/4;
width=MathF.Round(width*4)/4;float maxHeight=MathF.Round((h-24)*4)/4;
bool panelChanged=!ReferenceEquals(positionedPanel,TipPanel);
if(panelChanged || positionedLeft!=x){TipPanel.Style.Left=Length.Pixels(x);positionedLeft=x;}
if(panelChanged || positionedTop!=y){TipPanel.Style.Top=Length.Pixels(y);positionedTop=y;}
if(panelChanged || positionedWidth!=width){TipPanel.Style.Width=Length.Pixels(width);positionedWidth=width;}
if(panelChanged || positionedMaxHeight!=maxHeight){TipPanel.Style.MaxHeight=Length.Pixels(maxHeight);positionedMaxHeight=maxHeight;}
positionedPanel=TipPanel;
}
protected override int BuildHash()=>HashCode.Combine(Connection?.Key,Connection?.Pairs,Career?.FormationIndex,Pinned,GamePreferences.Russian);
}