1
0

WiresMenu.cs 22 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449450451452453454455456457458459460461462463464465466467468469470471472473474475476477478479480481482483484485486487488489490491492493494495496497498499500501502503504505506507508509510511512513514515516517518519520521522523524525526527528529530531532533534535536537538539540541542543544545546547548549550551552553554555556557558559560561562563564565566567568569570571572573574575576577578579580581582583584585586587588589590591592593594595596597598599600601
  1. using System.Numerics;
  2. using Content.Client.Examine;
  3. using Content.Client.Resources;
  4. using Content.Client.Stylesheets;
  5. using Content.Shared.Wires;
  6. using Robust.Client.Animations;
  7. using Robust.Client.Graphics;
  8. using Robust.Client.ResourceManagement;
  9. using Robust.Client.UserInterface;
  10. using Robust.Client.UserInterface.Controls;
  11. using Robust.Client.UserInterface.CustomControls;
  12. using Robust.Shared.Animations;
  13. using Robust.Shared.Input;
  14. using static Robust.Client.UserInterface.Controls.BoxContainer;
  15. namespace Content.Client.Wires.UI
  16. {
  17. public sealed class WiresMenu : BaseWindow
  18. {
  19. [Dependency] private readonly IResourceCache _resourceCache = default!;
  20. private readonly Control _wiresHBox;
  21. private readonly Control _topContainer;
  22. private readonly Control _statusContainer;
  23. private readonly Label _nameLabel;
  24. private readonly Label _serialLabel;
  25. public TextureButton CloseButton { get; set; }
  26. public event Action<int, WiresAction>? OnAction;
  27. public WiresMenu()
  28. {
  29. IoCManager.InjectDependencies(this);
  30. var rootContainer = new LayoutContainer {Name = "WireRoot"};
  31. AddChild(rootContainer);
  32. MouseFilter = MouseFilterMode.Stop;
  33. var panelTex = _resourceCache.GetTexture("/Textures/Interface/Nano/button.svg.96dpi.png");
  34. var back = new StyleBoxTexture
  35. {
  36. Texture = panelTex,
  37. Modulate = Color.FromHex("#25252A"),
  38. };
  39. back.SetPatchMargin(StyleBox.Margin.All, 10);
  40. var topPanel = new PanelContainer
  41. {
  42. PanelOverride = back,
  43. MouseFilter = MouseFilterMode.Pass
  44. };
  45. var bottomWrap = new LayoutContainer
  46. {
  47. Name = "BottomWrap"
  48. };
  49. var bottomPanel = new PanelContainer
  50. {
  51. PanelOverride = back,
  52. MouseFilter = MouseFilterMode.Pass
  53. };
  54. var shadow = new BoxContainer
  55. {
  56. Orientation = LayoutOrientation.Horizontal,
  57. Children =
  58. {
  59. new PanelContainer
  60. {
  61. MinSize = new Vector2(2, 0),
  62. PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
  63. },
  64. new PanelContainer
  65. {
  66. HorizontalExpand = true,
  67. MouseFilter = MouseFilterMode.Stop,
  68. Name = "Shadow",
  69. PanelOverride = new StyleBoxFlat {BackgroundColor = Color.Black.WithAlpha(0.5f)}
  70. },
  71. new PanelContainer
  72. {
  73. MinSize = new Vector2(2, 0),
  74. PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
  75. },
  76. }
  77. };
  78. var wrappingHBox = new BoxContainer
  79. {
  80. Orientation = LayoutOrientation.Horizontal
  81. };
  82. _wiresHBox = new BoxContainer
  83. {
  84. Orientation = LayoutOrientation.Horizontal,
  85. SeparationOverride = 4,
  86. VerticalAlignment = VAlignment.Bottom
  87. };
  88. wrappingHBox.AddChild(new Control {MinSize = new Vector2(20, 0)});
  89. wrappingHBox.AddChild(_wiresHBox);
  90. wrappingHBox.AddChild(new Control {MinSize = new Vector2(20, 0)});
  91. bottomWrap.AddChild(bottomPanel);
  92. LayoutContainer.SetAnchorPreset(bottomPanel, LayoutContainer.LayoutPreset.BottomWide);
  93. LayoutContainer.SetMarginTop(bottomPanel, -55);
  94. bottomWrap.AddChild(shadow);
  95. LayoutContainer.SetAnchorPreset(shadow, LayoutContainer.LayoutPreset.BottomWide);
  96. LayoutContainer.SetMarginBottom(shadow, -55);
  97. LayoutContainer.SetMarginTop(shadow, -80);
  98. LayoutContainer.SetMarginLeft(shadow, 12);
  99. LayoutContainer.SetMarginRight(shadow, -12);
  100. bottomWrap.AddChild(wrappingHBox);
  101. LayoutContainer.SetAnchorPreset(wrappingHBox, LayoutContainer.LayoutPreset.Wide);
  102. LayoutContainer.SetMarginBottom(wrappingHBox, -4);
  103. rootContainer.AddChild(topPanel);
  104. rootContainer.AddChild(bottomWrap);
  105. LayoutContainer.SetAnchorPreset(topPanel, LayoutContainer.LayoutPreset.Wide);
  106. LayoutContainer.SetMarginBottom(topPanel, -80);
  107. LayoutContainer.SetAnchorPreset(bottomWrap, LayoutContainer.LayoutPreset.VerticalCenterWide);
  108. LayoutContainer.SetGrowHorizontal(bottomWrap, LayoutContainer.GrowDirection.Both);
  109. var topContainerWrap = new BoxContainer
  110. {
  111. Orientation = LayoutOrientation.Vertical,
  112. Children =
  113. {
  114. (_topContainer = new BoxContainer
  115. {
  116. Orientation = LayoutOrientation.Vertical
  117. }),
  118. new Control {MinSize = new Vector2(0, 110)}
  119. }
  120. };
  121. rootContainer.AddChild(topContainerWrap);
  122. LayoutContainer.SetAnchorPreset(topContainerWrap, LayoutContainer.LayoutPreset.Wide);
  123. var font = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 13);
  124. var fontSmall = _resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 10);
  125. Button helpButton;
  126. var topRow = new BoxContainer
  127. {
  128. Orientation = LayoutOrientation.Horizontal,
  129. Margin = new Thickness(4, 2, 12, 2),
  130. Children =
  131. {
  132. (_nameLabel = new Label
  133. {
  134. Text = Loc.GetString("wires-menu-name-label"),
  135. FontOverride = font,
  136. FontColorOverride = StyleNano.NanoGold,
  137. VerticalAlignment = VAlignment.Center,
  138. }),
  139. (_serialLabel = new Label
  140. {
  141. Text = Loc.GetString("wires-menu-dead-beef-text"),
  142. FontOverride = fontSmall,
  143. FontColorOverride = Color.Gray,
  144. VerticalAlignment = VAlignment.Center,
  145. Margin = new Thickness(8, 0, 20, 0),
  146. HorizontalAlignment = HAlignment.Left,
  147. HorizontalExpand = true,
  148. }),
  149. (helpButton = new Button
  150. {
  151. Text = "?",
  152. Margin = new Thickness(0, 0, 2, 0),
  153. }),
  154. (CloseButton = new TextureButton
  155. {
  156. StyleClasses = {DefaultWindow.StyleClassWindowCloseButton},
  157. VerticalAlignment = VAlignment.Center
  158. })
  159. }
  160. };
  161. helpButton.OnPressed += a =>
  162. {
  163. var popup = new HelpPopup();
  164. UserInterfaceManager.ModalRoot.AddChild(popup);
  165. popup.Open(UIBox2.FromDimensions(a.Event.PointerLocation.Position, new Vector2(400, 200)));
  166. };
  167. var middle = new PanelContainer
  168. {
  169. PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#202025")},
  170. Children =
  171. {
  172. new BoxContainer
  173. {
  174. Orientation = LayoutOrientation.Horizontal,
  175. Children =
  176. {
  177. (_statusContainer = new GridContainer
  178. {
  179. Margin = new Thickness(8, 4),
  180. Rows = 2
  181. })
  182. }
  183. }
  184. }
  185. };
  186. _topContainer.AddChild(topRow);
  187. _topContainer.AddChild(new PanelContainer
  188. {
  189. MinSize = new Vector2(0, 2),
  190. PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
  191. });
  192. _topContainer.AddChild(middle);
  193. _topContainer.AddChild(new PanelContainer
  194. {
  195. MinSize = new Vector2(0, 2),
  196. PanelOverride = new StyleBoxFlat {BackgroundColor = Color.FromHex("#525252ff")}
  197. });
  198. CloseButton.OnPressed += _ => Close();
  199. SetHeight = 200;
  200. MinWidth = 320;
  201. }
  202. public void Populate(WiresBoundUserInterfaceState state)
  203. {
  204. _nameLabel.Text = state.BoardName;
  205. _serialLabel.Text = state.SerialNumber;
  206. _wiresHBox.RemoveAllChildren();
  207. var random = new Random(state.WireSeed);
  208. foreach (var wire in state.WiresList)
  209. {
  210. var mirror = random.Next(2) == 0;
  211. var flip = random.Next(2) == 0;
  212. var type = random.Next(2);
  213. var control = new WireControl(wire.Color, wire.Letter, wire.IsCut, flip, mirror, type, _resourceCache)
  214. {
  215. VerticalAlignment = VAlignment.Bottom
  216. };
  217. _wiresHBox.AddChild(control);
  218. control.WireClicked += () =>
  219. {
  220. OnAction?.Invoke(wire.Id, wire.IsCut ? WiresAction.Mend : WiresAction.Cut);
  221. };
  222. control.ContactsClicked += () =>
  223. {
  224. OnAction?.Invoke(wire.Id, WiresAction.Pulse);
  225. };
  226. }
  227. _statusContainer.RemoveAllChildren();
  228. foreach (var status in state.Statuses)
  229. {
  230. if (status.Value is StatusLightData statusLightData)
  231. {
  232. _statusContainer.AddChild(new StatusLight(statusLightData, _resourceCache));
  233. }
  234. else
  235. {
  236. _statusContainer.AddChild(new Label
  237. {
  238. Text = status.ToString()
  239. });
  240. }
  241. }
  242. }
  243. protected override DragMode GetDragModeFor(Vector2 relativeMousePos)
  244. {
  245. return DragMode.Move;
  246. }
  247. protected override bool HasPoint(Vector2 point)
  248. {
  249. // This makes it so our base window won't count for hit tests,
  250. // but we will still receive mouse events coming in from Pass mouse filter mode.
  251. // So basically, it perfectly shells out the hit tests to the panels we have!
  252. return false;
  253. }
  254. private sealed class WireControl : Control
  255. {
  256. private IResourceCache _resourceCache;
  257. private const string TextureContact = "/Textures/Interface/WireHacking/contact.svg.96dpi.png";
  258. public event Action? WireClicked;
  259. public event Action? ContactsClicked;
  260. public WireControl(WireColor color, WireLetter letter, bool isCut, bool flip, bool mirror, int type,
  261. IResourceCache resourceCache)
  262. {
  263. _resourceCache = resourceCache;
  264. HorizontalAlignment = HAlignment.Center;
  265. MouseFilter = MouseFilterMode.Stop;
  266. var layout = new LayoutContainer();
  267. AddChild(layout);
  268. var greek = new Label
  269. {
  270. Text = letter.Letter().ToString(),
  271. VerticalAlignment = VAlignment.Bottom,
  272. HorizontalAlignment = HAlignment.Center,
  273. Align = Label.AlignMode.Center,
  274. FontOverride = _resourceCache.GetFont("/Fonts/NotoSansDisplay/NotoSansDisplay-Bold.ttf", 12),
  275. FontColorOverride = Color.Gray,
  276. ToolTip = letter.Name(),
  277. MouseFilter = MouseFilterMode.Stop
  278. };
  279. layout.AddChild(greek);
  280. LayoutContainer.SetAnchorPreset(greek, LayoutContainer.LayoutPreset.BottomWide);
  281. LayoutContainer.SetGrowVertical(greek, LayoutContainer.GrowDirection.Begin);
  282. LayoutContainer.SetGrowHorizontal(greek, LayoutContainer.GrowDirection.Both);
  283. var contactTexture = _resourceCache.GetTexture(TextureContact);
  284. var contact1 = new TextureRect
  285. {
  286. Texture = contactTexture,
  287. Modulate = Color.FromHex("#E1CA76")
  288. };
  289. layout.AddChild(contact1);
  290. LayoutContainer.SetPosition(contact1, new Vector2(0, 0));
  291. var contact2 = new TextureRect
  292. {
  293. Texture = contactTexture,
  294. Modulate = Color.FromHex("#E1CA76")
  295. };
  296. layout.AddChild(contact2);
  297. LayoutContainer.SetPosition(contact2, new Vector2(0, 60));
  298. var wire = new WireRender(color, isCut, flip, mirror, type, _resourceCache);
  299. layout.AddChild(wire);
  300. LayoutContainer.SetPosition(wire, new Vector2(2, 16));
  301. ToolTip = color.Name();
  302. MinSize = new Vector2(20, 102);
  303. }
  304. protected override void KeyBindDown(GUIBoundKeyEventArgs args)
  305. {
  306. base.KeyBindDown(args);
  307. if (args.Function != EngineKeyFunctions.UIClick)
  308. {
  309. return;
  310. }
  311. if (args.RelativePosition.Y > 20 && args.RelativePosition.Y < 60)
  312. {
  313. WireClicked?.Invoke();
  314. }
  315. else
  316. {
  317. ContactsClicked?.Invoke();
  318. }
  319. }
  320. protected override bool HasPoint(Vector2 point)
  321. {
  322. return base.HasPoint(point) && point.Y <= 80;
  323. }
  324. private sealed class WireRender : Control
  325. {
  326. private readonly WireColor _color;
  327. private readonly bool _isCut;
  328. private readonly bool _flip;
  329. private readonly bool _mirror;
  330. private readonly int _type;
  331. private static readonly string[] TextureNormal =
  332. {
  333. "/Textures/Interface/WireHacking/wire_1.svg.96dpi.png",
  334. "/Textures/Interface/WireHacking/wire_2.svg.96dpi.png"
  335. };
  336. private static readonly string[] TextureCut =
  337. {
  338. "/Textures/Interface/WireHacking/wire_1_cut.svg.96dpi.png",
  339. "/Textures/Interface/WireHacking/wire_2_cut.svg.96dpi.png",
  340. };
  341. private static readonly string[] TextureCopper =
  342. {
  343. "/Textures/Interface/WireHacking/wire_1_copper.svg.96dpi.png",
  344. "/Textures/Interface/WireHacking/wire_2_copper.svg.96dpi.png"
  345. };
  346. private readonly IResourceCache _resourceCache;
  347. public WireRender(WireColor color, bool isCut, bool flip, bool mirror, int type,
  348. IResourceCache resourceCache)
  349. {
  350. _resourceCache = resourceCache;
  351. _color = color;
  352. _isCut = isCut;
  353. _flip = flip;
  354. _mirror = mirror;
  355. _type = type;
  356. SetSize = new Vector2(16, 50);
  357. }
  358. protected override void Draw(DrawingHandleScreen handle)
  359. {
  360. var colorValue = _color.ColorValue();
  361. var tex = _resourceCache.GetTexture(_isCut ? TextureCut[_type] : TextureNormal[_type]);
  362. var l = 0f;
  363. var r = tex.Width + l;
  364. var t = 0f;
  365. var b = tex.Height + t;
  366. if (_flip)
  367. {
  368. (t, b) = (b, t);
  369. }
  370. if (_mirror)
  371. {
  372. (l, r) = (r, l);
  373. }
  374. l *= UIScale;
  375. r *= UIScale;
  376. t *= UIScale;
  377. b *= UIScale;
  378. var rect = new UIBox2(l, t, r, b);
  379. if (_isCut)
  380. {
  381. var copper = Color.Orange;
  382. var copperTex = _resourceCache.GetTexture(TextureCopper[_type]);
  383. handle.DrawTextureRect(copperTex, rect, copper);
  384. }
  385. handle.DrawTextureRect(tex, rect, colorValue);
  386. }
  387. }
  388. }
  389. private sealed class StatusLight : Control
  390. {
  391. private static readonly Animation _blinkingFast = new()
  392. {
  393. Length = TimeSpan.FromSeconds(0.2),
  394. AnimationTracks =
  395. {
  396. new AnimationTrackControlProperty
  397. {
  398. Property = nameof(Control.Modulate),
  399. InterpolationMode = AnimationInterpolationMode.Linear,
  400. KeyFrames =
  401. {
  402. new AnimationTrackProperty.KeyFrame(Color.White, 0f),
  403. new AnimationTrackProperty.KeyFrame(Color.Transparent, 0.1f),
  404. new AnimationTrackProperty.KeyFrame(Color.White, 0.1f)
  405. }
  406. }
  407. }
  408. };
  409. private static readonly Animation _blinkingSlow = new()
  410. {
  411. Length = TimeSpan.FromSeconds(0.8),
  412. AnimationTracks =
  413. {
  414. new AnimationTrackControlProperty
  415. {
  416. Property = nameof(Control.Modulate),
  417. InterpolationMode = AnimationInterpolationMode.Linear,
  418. KeyFrames =
  419. {
  420. new AnimationTrackProperty.KeyFrame(Color.White, 0f),
  421. new AnimationTrackProperty.KeyFrame(Color.White, 0.3f),
  422. new AnimationTrackProperty.KeyFrame(Color.Transparent, 0.1f),
  423. new AnimationTrackProperty.KeyFrame(Color.Transparent, 0.3f),
  424. new AnimationTrackProperty.KeyFrame(Color.White, 0.1f),
  425. }
  426. }
  427. }
  428. };
  429. public StatusLight(StatusLightData data, IResourceCache resourceCache)
  430. {
  431. HorizontalAlignment = HAlignment.Right;
  432. var hsv = Color.ToHsv(data.Color);
  433. hsv.Z /= 2;
  434. var dimColor = Color.FromHsv(hsv);
  435. TextureRect activeLight;
  436. var lightContainer = new Control
  437. {
  438. SetSize = new Vector2(20, 20),
  439. Children =
  440. {
  441. new TextureRect
  442. {
  443. Texture = resourceCache.GetTexture(
  444. "/Textures/Interface/WireHacking/light_off_base.svg.96dpi.png"),
  445. Stretch = TextureRect.StretchMode.KeepCentered,
  446. ModulateSelfOverride = dimColor
  447. },
  448. (activeLight = new TextureRect
  449. {
  450. ModulateSelfOverride = data.Color.WithAlpha(0.4f),
  451. Stretch = TextureRect.StretchMode.KeepCentered,
  452. Texture =
  453. resourceCache.GetTexture("/Textures/Interface/WireHacking/light_on_base.svg.96dpi.png"),
  454. })
  455. }
  456. };
  457. Animation? animation = null;
  458. switch (data.State)
  459. {
  460. case StatusLightState.Off:
  461. activeLight.Visible = false;
  462. break;
  463. case StatusLightState.On:
  464. break;
  465. case StatusLightState.BlinkingFast:
  466. animation = _blinkingFast;
  467. break;
  468. case StatusLightState.BlinkingSlow:
  469. animation = _blinkingSlow;
  470. break;
  471. default:
  472. throw new ArgumentOutOfRangeException();
  473. }
  474. if (animation != null)
  475. {
  476. activeLight.PlayAnimation(animation, "blink");
  477. activeLight.AnimationCompleted += s =>
  478. {
  479. if (s == "blink")
  480. {
  481. activeLight.PlayAnimation(animation, s);
  482. }
  483. };
  484. }
  485. var font = resourceCache.GetFont("/Fonts/Boxfont-round/Boxfont Round.ttf", 12);
  486. var hBox = new BoxContainer
  487. {
  488. Orientation = LayoutOrientation.Horizontal,
  489. SeparationOverride = 4
  490. };
  491. hBox.AddChild(new Label
  492. {
  493. Text = data.Text,
  494. FontOverride = font,
  495. FontColorOverride = Color.FromHex("#A1A6AE"),
  496. VerticalAlignment = VAlignment.Center,
  497. });
  498. hBox.AddChild(lightContainer);
  499. hBox.AddChild(new Control {MinSize = new Vector2(6, 0)});
  500. AddChild(hBox);
  501. }
  502. }
  503. private sealed class HelpPopup : Popup
  504. {
  505. public HelpPopup()
  506. {
  507. var label = new RichTextLabel();
  508. label.SetMessage(Loc.GetString("wires-menu-help-popup"));
  509. AddChild(new PanelContainer
  510. {
  511. StyleClasses = {ExamineSystem.StyleClassEntityTooltip},
  512. Children = {label}
  513. });
  514. }
  515. }
  516. }
  517. }