ActionButton.cs 14 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355356357358359360361362363364365366367368369370371372373374375376377378379380381382383384385386387388389390391392393394395396397398399400401402403404405406407408409410411412413414415416417418419420421422423424425426427428429430431432433434435436437438439440441442443444445446447448449
  1. using System.Numerics;
  2. using Content.Client.Actions;
  3. using Content.Client.Actions.UI;
  4. using Content.Client.Cooldown;
  5. using Content.Client.Stylesheets;
  6. using Content.Shared.Actions;
  7. using Robust.Client.GameObjects;
  8. using Robust.Client.Graphics;
  9. using Robust.Client.UserInterface;
  10. using Robust.Client.UserInterface.Controls;
  11. using Robust.Shared.Input;
  12. using Robust.Shared.Timing;
  13. using Robust.Shared.Utility;
  14. using static Robust.Client.UserInterface.Controls.BoxContainer;
  15. using static Robust.Client.UserInterface.Controls.TextureRect;
  16. using Direction = Robust.Shared.Maths.Direction;
  17. namespace Content.Client.UserInterface.Systems.Actions.Controls;
  18. public sealed class ActionButton : Control, IEntityControl
  19. {
  20. private IEntityManager _entities;
  21. private SpriteSystem? _spriteSys;
  22. private ActionUIController? _controller;
  23. private bool _beingHovered;
  24. private bool _depressed;
  25. private bool _toggled;
  26. public BoundKeyFunction? KeyBind
  27. {
  28. set
  29. {
  30. _keybind = value;
  31. if (_keybind != null)
  32. {
  33. Label.Text = BoundKeyHelper.ShortKeyName(_keybind.Value);
  34. }
  35. }
  36. }
  37. private BoundKeyFunction? _keybind;
  38. public readonly TextureRect Button;
  39. public readonly PanelContainer HighlightRect;
  40. private readonly TextureRect _bigActionIcon;
  41. private readonly TextureRect _smallActionIcon;
  42. public readonly Label Label;
  43. public readonly CooldownGraphic Cooldown;
  44. private readonly SpriteView _smallItemSpriteView;
  45. private readonly SpriteView _bigItemSpriteView;
  46. private Texture? _buttonBackgroundTexture;
  47. public EntityUid? ActionId { get; private set; }
  48. private BaseActionComponent? _action;
  49. public bool Locked { get; set; }
  50. public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionPressed;
  51. public event Action<GUIBoundKeyEventArgs, ActionButton>? ActionUnpressed;
  52. public event Action<ActionButton>? ActionFocusExited;
  53. public ActionButton(IEntityManager entities, SpriteSystem? spriteSys = null, ActionUIController? controller = null)
  54. {
  55. // TODO why is this constructor so slooooow. The rest of the code is fine
  56. _entities = entities;
  57. _spriteSys = spriteSys;
  58. _controller = controller;
  59. MouseFilter = MouseFilterMode.Pass;
  60. Button = new TextureRect
  61. {
  62. Name = "Button",
  63. TextureScale = new Vector2(2, 2)
  64. };
  65. HighlightRect = new PanelContainer
  66. {
  67. StyleClasses = {StyleNano.StyleClassHandSlotHighlight},
  68. MinSize = new Vector2(32, 32),
  69. Visible = false
  70. };
  71. _bigActionIcon = new TextureRect
  72. {
  73. HorizontalExpand = true,
  74. VerticalExpand = true,
  75. Stretch = StretchMode.Scale,
  76. Visible = false
  77. };
  78. _smallActionIcon = new TextureRect
  79. {
  80. HorizontalAlignment = HAlignment.Right,
  81. VerticalAlignment = VAlignment.Bottom,
  82. Stretch = StretchMode.Scale,
  83. Visible = false
  84. };
  85. Label = new Label
  86. {
  87. Name = "Label",
  88. HorizontalAlignment = HAlignment.Left,
  89. VerticalAlignment = VAlignment.Top,
  90. Margin = new Thickness(5, 0, 0, 0)
  91. };
  92. _bigItemSpriteView = new SpriteView
  93. {
  94. Name = "Big Sprite",
  95. HorizontalExpand = true,
  96. VerticalExpand = true,
  97. Scale = new Vector2(2, 2),
  98. SetSize = new Vector2(64, 64),
  99. Visible = false,
  100. OverrideDirection = Direction.South,
  101. };
  102. _smallItemSpriteView = new SpriteView
  103. {
  104. Name = "Small Sprite",
  105. HorizontalAlignment = HAlignment.Right,
  106. VerticalAlignment = VAlignment.Bottom,
  107. Visible = false,
  108. OverrideDirection = Direction.South,
  109. };
  110. // padding to the left of the small icon
  111. var paddingBoxItemIcon = new BoxContainer
  112. {
  113. Orientation = LayoutOrientation.Horizontal,
  114. HorizontalExpand = true,
  115. VerticalExpand = true,
  116. MinSize = new Vector2(64, 64)
  117. };
  118. paddingBoxItemIcon.AddChild(new Control()
  119. {
  120. MinSize = new Vector2(32, 32),
  121. });
  122. paddingBoxItemIcon.AddChild(new Control
  123. {
  124. Children =
  125. {
  126. _smallActionIcon,
  127. _smallItemSpriteView
  128. }
  129. });
  130. Cooldown = new CooldownGraphic {Visible = false};
  131. AddChild(Button);
  132. AddChild(_bigActionIcon);
  133. AddChild(_bigItemSpriteView);
  134. AddChild(HighlightRect);
  135. AddChild(Label);
  136. AddChild(Cooldown);
  137. AddChild(paddingBoxItemIcon);
  138. Button.Modulate = new Color(255, 255, 255, 150);
  139. OnThemeUpdated();
  140. OnKeyBindDown += OnPressed;
  141. OnKeyBindUp += OnUnpressed;
  142. TooltipSupplier = SupplyTooltip;
  143. }
  144. protected override void OnThemeUpdated()
  145. {
  146. base.OnThemeUpdated();
  147. _buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
  148. Label.FontColorOverride = Theme.ResolveColorOrSpecified("whiteText");
  149. }
  150. private void OnPressed(GUIBoundKeyEventArgs args)
  151. {
  152. if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick)
  153. return;
  154. if (args.Function == EngineKeyFunctions.UIRightClick)
  155. Depress(args, true);
  156. ActionPressed?.Invoke(args, this);
  157. }
  158. private void OnUnpressed(GUIBoundKeyEventArgs args)
  159. {
  160. if (args.Function != EngineKeyFunctions.UIClick && args.Function != EngineKeyFunctions.UIRightClick)
  161. return;
  162. if (args.Function == EngineKeyFunctions.UIRightClick)
  163. Depress(args, false);
  164. ActionUnpressed?.Invoke(args, this);
  165. }
  166. private Control? SupplyTooltip(Control sender)
  167. {
  168. if (!_entities.TryGetComponent(ActionId, out MetaDataComponent? metadata))
  169. return null;
  170. var name = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityName));
  171. var decr = FormattedMessage.FromMarkupPermissive(Loc.GetString(metadata.EntityDescription));
  172. if (_action is { Charges: not null })
  173. {
  174. var charges = FormattedMessage.FromMarkupPermissive(Loc.GetString($"Charges: {_action.Charges.Value.ToString()}/{_action.MaxCharges.ToString()}"));
  175. return new ActionAlertTooltip(name, decr, charges: charges);
  176. }
  177. return new ActionAlertTooltip(name, decr);
  178. }
  179. protected override void ControlFocusExited()
  180. {
  181. ActionFocusExited?.Invoke(this);
  182. }
  183. private void UpdateItemIcon()
  184. {
  185. if (_action is not {EntityIcon: { } entity} ||
  186. !_entities.HasComponent<SpriteComponent>(entity))
  187. {
  188. _bigItemSpriteView.Visible = false;
  189. _bigItemSpriteView.SetEntity(null);
  190. _smallItemSpriteView.Visible = false;
  191. _smallItemSpriteView.SetEntity(null);
  192. }
  193. else
  194. {
  195. switch (_action.ItemIconStyle)
  196. {
  197. case ItemActionIconStyle.BigItem:
  198. _bigItemSpriteView.Visible = true;
  199. _bigItemSpriteView.SetEntity(entity);
  200. _smallItemSpriteView.Visible = false;
  201. _smallItemSpriteView.SetEntity(null);
  202. break;
  203. case ItemActionIconStyle.BigAction:
  204. _bigItemSpriteView.Visible = false;
  205. _bigItemSpriteView.SetEntity(null);
  206. _smallItemSpriteView.Visible = true;
  207. _smallItemSpriteView.SetEntity(entity);
  208. break;
  209. case ItemActionIconStyle.NoItem:
  210. _bigItemSpriteView.Visible = false;
  211. _bigItemSpriteView.SetEntity(null);
  212. _smallItemSpriteView.Visible = false;
  213. _smallItemSpriteView.SetEntity(null);
  214. break;
  215. }
  216. }
  217. }
  218. private void SetActionIcon(Texture? texture)
  219. {
  220. if (_action == null || texture == null)
  221. {
  222. _bigActionIcon.Texture = null;
  223. _bigActionIcon.Visible = false;
  224. _smallActionIcon.Texture = null;
  225. _smallActionIcon.Visible = false;
  226. }
  227. else if (_action.EntityIcon != null && _action.ItemIconStyle == ItemActionIconStyle.BigItem)
  228. {
  229. _smallActionIcon.Texture = texture;
  230. _smallActionIcon.Modulate = _action.IconColor;
  231. _smallActionIcon.Visible = true;
  232. _bigActionIcon.Texture = null;
  233. _bigActionIcon.Visible = false;
  234. }
  235. else
  236. {
  237. _bigActionIcon.Texture = texture;
  238. _bigActionIcon.Modulate = _action.IconColor;
  239. _bigActionIcon.Visible = true;
  240. _smallActionIcon.Texture = null;
  241. _smallActionIcon.Visible = false;
  242. }
  243. }
  244. public void UpdateIcons()
  245. {
  246. UpdateItemIcon();
  247. UpdateBackground();
  248. if (_action == null)
  249. {
  250. SetActionIcon(null);
  251. return;
  252. }
  253. _controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
  254. _spriteSys ??= _entities.System<SpriteSystem>();
  255. if ((_controller.SelectingTargetFor == ActionId || _action.Toggled))
  256. {
  257. if (_action.IconOn != null)
  258. SetActionIcon(_spriteSys.Frame0(_action.IconOn));
  259. else if (_action.Icon != null)
  260. SetActionIcon(_spriteSys.Frame0(_action.Icon));
  261. else
  262. SetActionIcon(null);
  263. if (_action.BackgroundOn != null)
  264. _buttonBackgroundTexture = _spriteSys.Frame0(_action.BackgroundOn);
  265. }
  266. else
  267. {
  268. SetActionIcon(_action.Icon != null ? _spriteSys.Frame0(_action.Icon) : null);
  269. _buttonBackgroundTexture = Theme.ResolveTexture("SlotBackground");
  270. }
  271. }
  272. public void UpdateBackground()
  273. {
  274. _controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
  275. if (_action != null ||
  276. _controller.IsDragging && GetPositionInParent() == Parent?.ChildCount - 1)
  277. {
  278. Button.Texture = _buttonBackgroundTexture;
  279. }
  280. else
  281. {
  282. Button.Texture = null;
  283. }
  284. }
  285. public bool TryReplaceWith(EntityUid actionId, ActionsSystem system)
  286. {
  287. if (Locked)
  288. {
  289. return false;
  290. }
  291. UpdateData(actionId, system);
  292. return true;
  293. }
  294. public void UpdateData(EntityUid? actionId, ActionsSystem system)
  295. {
  296. ActionId = actionId;
  297. system.TryGetActionData(actionId, out _action);
  298. Label.Visible = actionId != null;
  299. UpdateIcons();
  300. }
  301. public void ClearData()
  302. {
  303. ActionId = null;
  304. _action = null;
  305. Cooldown.Visible = false;
  306. Cooldown.Progress = 1;
  307. Label.Visible = false;
  308. UpdateIcons();
  309. }
  310. protected override void FrameUpdate(FrameEventArgs args)
  311. {
  312. base.FrameUpdate(args);
  313. UpdateBackground();
  314. Cooldown.Visible = _action != null && _action.Cooldown != null;
  315. if (_action == null)
  316. return;
  317. if (_action.Cooldown != null)
  318. {
  319. Cooldown.FromTime(_action.Cooldown.Value.Start, _action.Cooldown.Value.End);
  320. }
  321. if (ActionId != null && _toggled != _action.Toggled)
  322. {
  323. _toggled = _action.Toggled;
  324. }
  325. }
  326. protected override void MouseEntered()
  327. {
  328. base.MouseEntered();
  329. UserInterfaceManager.HoverSound();
  330. _beingHovered = true;
  331. DrawModeChanged();
  332. }
  333. protected override void MouseExited()
  334. {
  335. base.MouseExited();
  336. _beingHovered = false;
  337. DrawModeChanged();
  338. }
  339. /// <summary>
  340. /// Press this button down. If it was depressed and now set to not depressed, will
  341. /// trigger the action.
  342. /// </summary>
  343. public void Depress(GUIBoundKeyEventArgs args, bool depress)
  344. {
  345. // action can still be toggled if it's allowed to stay selected
  346. if (_action is not {Enabled: true})
  347. return;
  348. _depressed = depress;
  349. DrawModeChanged();
  350. }
  351. public void DrawModeChanged()
  352. {
  353. _controller ??= UserInterfaceManager.GetUIController<ActionUIController>();
  354. HighlightRect.Visible = _beingHovered && (_action != null || _controller.IsDragging);
  355. // always show the normal empty button style if no action in this slot
  356. if (_action == null)
  357. {
  358. SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
  359. return;
  360. }
  361. // show a hover only if the action is usable or another action is being dragged on top of this
  362. if (_beingHovered && (_controller.IsDragging || _action!.Enabled))
  363. {
  364. SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassHover);
  365. }
  366. // it's only depress-able if it's usable, so if we're depressed
  367. // show the depressed style
  368. if (_depressed && !_beingHovered)
  369. {
  370. HighlightRect.Visible = false;
  371. SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassPressed);
  372. return;
  373. }
  374. // if it's toggled on, always show the toggled on style (currently same as depressed style)
  375. if (_action.Toggled || _controller.SelectingTargetFor == ActionId)
  376. {
  377. // when there's a toggle sprite, we're showing that sprite instead of highlighting this slot
  378. SetOnlyStylePseudoClass(_action.IconOn != null
  379. ? ContainerButton.StylePseudoClassNormal
  380. : ContainerButton.StylePseudoClassPressed);
  381. return;
  382. }
  383. if (!_action.Enabled)
  384. {
  385. SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassDisabled);
  386. return;
  387. }
  388. SetOnlyStylePseudoClass(ContainerButton.StylePseudoClassNormal);
  389. }
  390. EntityUid? IEntityControl.UiEntity => ActionId;
  391. }