NetworkConfiguratorLinkMenu.xaml.cs 7.5 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222
  1. using System.Numerics;
  2. using Content.Client.UserInterface.Controls;
  3. using Content.Shared.DeviceLinking;
  4. using Content.Shared.DeviceNetwork;
  5. using Robust.Client.AutoGenerated;
  6. using Robust.Client.Graphics;
  7. using Robust.Client.UserInterface;
  8. using Robust.Client.UserInterface.Controls;
  9. using Robust.Client.UserInterface.XAML;
  10. using Robust.Shared.Prototypes;
  11. namespace Content.Client.NetworkConfigurator;
  12. [GenerateTypedNameReferences]
  13. public sealed partial class NetworkConfiguratorLinkMenu : FancyWindow
  14. {
  15. private const string PanelBgColor = "#202023";
  16. private readonly LinksRender _links;
  17. private readonly List<SourcePortPrototype> _sources = new();
  18. private readonly List<SinkPortPrototype> _sinks = new();
  19. private (ButtonPosition position, string id, int index)? _selectedButton;
  20. private List<(string left, string right)>? _defaults;
  21. public event Action? OnClearLinks;
  22. public event Action<string, string>? OnToggleLink;
  23. public event Action<List<(string left, string right)>>? OnLinkDefaults;
  24. public NetworkConfiguratorLinkMenu()
  25. {
  26. RobustXamlLoader.Load(this);
  27. var footerStyleBox = new StyleBoxFlat()
  28. {
  29. BorderThickness = new Thickness(0, 2, 0, 0),
  30. BorderColor = Color.FromHex("#5A5A5A")
  31. };
  32. FooterPanel.PanelOverride = footerStyleBox;
  33. MainPanel.PanelOverride = new StyleBoxFlat(Color.FromHex(PanelBgColor));
  34. ButtonClear.AddStyleClass("ButtonColorRed");
  35. ButtonLinkDefault.Disabled = true;
  36. _links = new LinksRender(ButtonContainerLeft, ButtonContainerRight);
  37. _links.VerticalExpand = true;
  38. MiddleContainer.AddChild(_links);
  39. ButtonOk.OnPressed += _ => Close();
  40. ButtonLinkDefault.OnPressed += _ => LinkDefaults();
  41. ButtonClear.OnPressed += _ => OnClearLinks?.Invoke();
  42. }
  43. public void UpdateState(DeviceLinkUserInterfaceState linkState)
  44. {
  45. ButtonContainerLeft.RemoveAllChildren();
  46. ButtonContainerRight.RemoveAllChildren();
  47. _sources.Clear();
  48. _sources.AddRange(linkState.Sources);
  49. _links.SourceButtons.Clear();
  50. var i = 0;
  51. foreach (var source in _sources)
  52. {
  53. var button = CreateButton(ButtonPosition.Left, source.Name, source.Description, source.ID, i);
  54. ButtonContainerLeft.AddChild(button);
  55. _links.SourceButtons.Add(source.ID, button);
  56. i++;
  57. }
  58. _sinks.Clear();
  59. _sinks.AddRange(linkState.Sinks);
  60. _links.SinkButtons.Clear();
  61. i = 0;
  62. foreach (var sink in _sinks)
  63. {
  64. var button = CreateButton(ButtonPosition.Right, sink.Name, sink.Description, sink.ID, i);
  65. ButtonContainerRight.AddChild(button);
  66. _links.SinkButtons.Add(sink.ID, button);
  67. i++;
  68. }
  69. _links.Links.Clear();
  70. _links.Links.AddRange(linkState.Links);
  71. _defaults = linkState.Defaults;
  72. ButtonLinkDefault.Disabled = _defaults == default;
  73. FromAddressLabel.Text = linkState.SourceAddress;
  74. ToAddressLabel.Text = linkState.SinkAddress;
  75. }
  76. private void LinkDefaults()
  77. {
  78. if (_defaults == default)
  79. return;
  80. OnLinkDefaults?.Invoke(_defaults);
  81. }
  82. private Button CreateButton(ButtonPosition position, string name, string description, string id, int index)
  83. {
  84. var button = new Button();
  85. button.AddStyleClass("OpenBoth");
  86. button.Text = Loc.GetString(name);
  87. button.ToolTip = Loc.GetString(description);
  88. button.ToggleMode = true;
  89. button.OnPressed += args => OnButtonPressed(args, position, id, index);
  90. return button;
  91. }
  92. private void OnButtonPressed(BaseButton.ButtonEventArgs args, ButtonPosition position, string id, int index)
  93. {
  94. var key = (position, id, index);
  95. if (_selectedButton == key)
  96. {
  97. args.Button.Pressed = false;
  98. _selectedButton = null;
  99. return;
  100. }
  101. if (!_selectedButton.HasValue)
  102. {
  103. args.Button.Pressed = true;
  104. _selectedButton = key;
  105. return;
  106. }
  107. if (_selectedButton.Value.position == position)
  108. {
  109. args.Button.Pressed = false;
  110. return;
  111. }
  112. var left = _selectedButton.Value.position == ButtonPosition.Left ? _selectedButton.Value.id : id;
  113. var right = _selectedButton.Value.position == ButtonPosition.Left ? id : _selectedButton.Value.id;
  114. OnToggleLink?.Invoke(left, right);
  115. args.Button.Pressed = false;
  116. var container = _selectedButton.Value.position == ButtonPosition.Left ? ButtonContainerLeft : ButtonContainerRight;
  117. if (container.GetChild(_selectedButton.Value.index) is Button button)
  118. button.Pressed = false;
  119. _selectedButton = null;
  120. }
  121. private enum ButtonPosition
  122. {
  123. Left,
  124. Right
  125. }
  126. /// <summary>
  127. /// Draws lines between linked ports using bezier curve calculated with polynomial coefficients
  128. /// See: https://youtu.be/jvPPXbo87ds?t=351
  129. /// </summary>
  130. private sealed class LinksRender : Control
  131. {
  132. public readonly List<(ProtoId<SourcePortPrototype>, ProtoId<SinkPortPrototype>)> Links = new();
  133. public readonly Dictionary<string, Button> SourceButtons = new();
  134. public readonly Dictionary<string, Button> SinkButtons = new();
  135. private readonly BoxContainer _leftButtonContainer;
  136. private readonly BoxContainer _rightButtonContainer;
  137. public LinksRender(BoxContainer leftButtonContainer, BoxContainer rightButtonContainer)
  138. {
  139. _leftButtonContainer = leftButtonContainer;
  140. _rightButtonContainer = rightButtonContainer;
  141. }
  142. protected override void Draw(DrawingHandleScreen handle)
  143. {
  144. foreach (var (left, right) in Links)
  145. {
  146. if (!SourceButtons.TryGetValue(left, out var leftChild) || !SinkButtons.TryGetValue(right, out var rightChild))
  147. continue;
  148. var leftOffset = _leftButtonContainer.PixelPosition.Y;
  149. var rightOffset = _rightButtonContainer.PixelPosition.Y;
  150. var y1 = leftChild.PixelPosition.Y + leftChild.PixelHeight / 2 + leftOffset;
  151. var y2 = rightChild.PixelPosition.Y + rightChild.PixelHeight / 2 + rightOffset;
  152. if (left == right)
  153. {
  154. handle.DrawLine(new Vector2(0, y1), new Vector2(PixelWidth, y2), Color.Cyan);
  155. continue;
  156. }
  157. var controls = new List<Vector2>
  158. {
  159. new(0, y1),
  160. new(30, y1),
  161. new(PixelWidth - 30, y2),
  162. new(PixelWidth, y2),
  163. };
  164. //Calculate coefficients
  165. var c0 = controls[0];
  166. var c1 = controls[0] * -3 + controls[1] * 3;
  167. var c2 = controls[0] * 3 + controls[1] * -6 + controls[2] * 3;
  168. var c3 = controls[0] * -1 + controls[1] * 3 + controls[2] * -3 + controls[3];
  169. var points = new List<Vector2>();
  170. //Calculate points using coefficients
  171. for (float t = 0; t <= 1; t += 0.0001f)
  172. {
  173. var point = c0 + c1 * t + c2 * (t * t) + c3 * (t * t * t);
  174. points.Add(point);
  175. }
  176. handle.DrawPrimitives(DrawPrimitiveTopology.LineStrip, points.ToArray(), Color.Cyan);
  177. }
  178. }
  179. }
  180. }