using System.Numerics; using Content.Client.UserInterface.Controls; using Content.Shared.DeviceLinking; using Content.Shared.DeviceNetwork; using Robust.Client.AutoGenerated; using Robust.Client.Graphics; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Prototypes; namespace Content.Client.NetworkConfigurator; [GenerateTypedNameReferences] public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow { private const string PanelBgColor = "#202023"; private readonly LinksRender _links; private readonly List _sources = new(); private readonly List _sinks = new(); private (ButtonPosition position, string id, int index)? _selectedButton; private List<(string left, string right)>? _defaults; public event Action? OnClearLinks; public event Action? OnToggleLink; public event Action>? OnLinkDefaults; public NetworkConfiguratorLinkMenu() { RobustXamlLoader.Load(this); var footerStyleBox = new StyleBoxFlat() { BorderThickness = new Thickness(0, 2, 0, 0), BorderColor = Color.FromHex("#5A5A5A") }; FooterPanel.PanelOverride = footerStyleBox; MainPanel.PanelOverride = new StyleBoxFlat(Color.FromHex(PanelBgColor)); ButtonClear.AddStyleClass("ButtonColorRed"); ButtonLinkDefault.Disabled = true; _links = new LinksRender(ButtonContainerLeft, ButtonContainerRight); _links.VerticalExpand = true; MiddleContainer.AddChild(_links); ButtonOk.OnPressed += _ => Close(); ButtonLinkDefault.OnPressed += _ => LinkDefaults(); ButtonClear.OnPressed += _ => OnClearLinks?.Invoke(); } public void UpdateState(DeviceLinkUserInterfaceState linkState) { ButtonContainerLeft.RemoveAllChildren(); ButtonContainerRight.RemoveAllChildren(); _sources.Clear(); _sources.AddRange(linkState.Sources); _links.SourceButtons.Clear(); var i = 0; foreach (var source in _sources) { var button = CreateButton(ButtonPosition.Left, source.Name, source.Description, source.ID, i); ButtonContainerLeft.AddChild(button); _links.SourceButtons.Add(source.ID, button); i++; } _sinks.Clear(); _sinks.AddRange(linkState.Sinks); _links.SinkButtons.Clear(); i = 0; foreach (var sink in _sinks) { var button = CreateButton(ButtonPosition.Right, sink.Name, sink.Description, sink.ID, i); ButtonContainerRight.AddChild(button); _links.SinkButtons.Add(sink.ID, button); i++; } _links.Links.Clear(); _links.Links.AddRange(linkState.Links); _defaults = linkState.Defaults; ButtonLinkDefault.Disabled = _defaults == default; FromAddressLabel.Text = linkState.SourceAddress; ToAddressLabel.Text = linkState.SinkAddress; } private void LinkDefaults() { if (_defaults == default) return; OnLinkDefaults?.Invoke(_defaults); } private Button CreateButton(ButtonPosition position, string name, string description, string id, int index) { var button = new Button(); button.AddStyleClass("OpenBoth"); button.Text = Loc.GetString(name); button.ToolTip = Loc.GetString(description); button.ToggleMode = true; button.OnPressed += args => OnButtonPressed(args, position, id, index); return button; } private void OnButtonPressed(BaseButton.ButtonEventArgs args, ButtonPosition position, string id, int index) { var key = (position, id, index); if (_selectedButton == key) { args.Button.Pressed = false; _selectedButton = null; return; } if (!_selectedButton.HasValue) { args.Button.Pressed = true; _selectedButton = key; return; } if (_selectedButton.Value.position == position) { args.Button.Pressed = false; return; } var left = _selectedButton.Value.position == ButtonPosition.Left ? _selectedButton.Value.id : id; var right = _selectedButton.Value.position == ButtonPosition.Left ? id : _selectedButton.Value.id; OnToggleLink?.Invoke(left, right); args.Button.Pressed = false; var container = _selectedButton.Value.position == ButtonPosition.Left ? ButtonContainerLeft : ButtonContainerRight; if (container.GetChild(_selectedButton.Value.index) is Button button) button.Pressed = false; _selectedButton = null; } private enum ButtonPosition { Left, Right } /// /// Draws lines between linked ports using bezier curve calculated with polynomial coefficients /// See: https://youtu.be/jvPPXbo87ds?t=351 /// private sealed class LinksRender : Control { public readonly List<(ProtoId, ProtoId)> Links = new(); public readonly Dictionary SourceButtons = new(); public readonly Dictionary SinkButtons = new(); private readonly BoxContainer _leftButtonContainer; private readonly BoxContainer _rightButtonContainer; public LinksRender(BoxContainer leftButtonContainer, BoxContainer rightButtonContainer) { _leftButtonContainer = leftButtonContainer; _rightButtonContainer = rightButtonContainer; } protected override void Draw(DrawingHandleScreen handle) { foreach (var (left, right) in Links) { if (!SourceButtons.TryGetValue(left, out var leftChild) || !SinkButtons.TryGetValue(right, out var rightChild)) continue; var leftOffset = _leftButtonContainer.PixelPosition.Y; var rightOffset = _rightButtonContainer.PixelPosition.Y; var y1 = leftChild.PixelPosition.Y + leftChild.PixelHeight / 2 + leftOffset; var y2 = rightChild.PixelPosition.Y + rightChild.PixelHeight / 2 + rightOffset; if (left == right) { handle.DrawLine(new Vector2(0, y1), new Vector2(PixelWidth, y2), Color.Cyan); continue; } var controls = new List { new(0, y1), new(30, y1), new(PixelWidth - 30, y2), new(PixelWidth, y2), }; //Calculate coefficients var c0 = controls[0]; var c1 = controls[0] * -3 + controls[1] * 3; var c2 = controls[0] * 3 + controls[1] * -6 + controls[2] * 3; var c3 = controls[0] * -1 + controls[1] * 3 + controls[2] * -3 + controls[3]; var points = new List(); //Calculate points using coefficients for (float t = 0; t <= 1; t += 0.0001f) { var point = c0 + c1 * t + c2 * (t * t) + c3 * (t * t * t); points.Add(point); } handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, points.ToArray(), Color.Cyan); } } } }