using System; using System.Transactions; using Content.Client.UserInterface.Stylesheets; using Content.Client.Utility; using Content.Shared.GameObjects.Components.Mobs; using Content.Shared.Input; using Robust.Client.Graphics; using Robust.Client.Graphics.Drawing; using Robust.Client.Input; using Robust.Client.Interfaces.Input; using Robust.Client.Interfaces.ResourceManagement; using Robust.Client.UserInterface; using Robust.Client.UserInterface.Controls; using Robust.Shared.Input; using Robust.Shared.Input.Binding; using Robust.Shared.IoC; using Robust.Shared.Localization; using Robust.Shared.Maths; using Robust.Shared.Utility; using YamlDotNet.Core.Tokens; using static Robust.Client.Input.Keyboard.Key; using Control = Robust.Client.UserInterface.Control; namespace Content.Client.UserInterface { /// /// Responsible for laying out the default game HUD. /// public interface IGameHud { Control RootControl { get; } // Escape top button. bool EscapeButtonDown { get; set; } Action EscapeButtonToggled { get; set; } // Character top button. bool CharacterButtonDown { get; set; } bool CharacterButtonVisible { get; set; } Action CharacterButtonToggled { get; set; } // Inventory top button. bool InventoryButtonDown { get; set; } bool InventoryButtonVisible { get; set; } Action InventoryButtonToggled { get; set; } // Crafting top button. bool CraftingButtonDown { get; set; } bool CraftingButtonVisible { get; set; } Action CraftingButtonToggled { get; set; } // Actions top button. bool ActionsButtonDown { get; set; } bool ActionsButtonVisible { get; set; } Action ActionsButtonToggled { get; set; } // Admin top button. bool AdminButtonDown { get; set; } bool AdminButtonVisible { get; set; } Action AdminButtonToggled { get; set; } // Sandbox top button. bool SandboxButtonDown { get; set; } bool SandboxButtonVisible { get; set; } Action SandboxButtonToggled { get; set; } Control HandsContainer { get; } Control SuspicionContainer { get; } Control RightInventoryQuickButtonContainer { get; } Control LeftInventoryQuickButtonContainer { get; } bool CombatPanelVisible { get; set; } bool CombatModeActive { get; set; } TargetingZone TargetingZone { get; set; } Action OnCombatModeChanged { get; set; } Action OnTargetingZoneChanged { get; set; } // Init logic. void Initialize(); } internal sealed class GameHud : IGameHud { private HBoxContainer _topButtonsContainer; private TopButton _buttonEscapeMenu; private TopButton _buttonTutorial; private TopButton _buttonCharacterMenu; private TopButton _buttonInventoryMenu; private TopButton _buttonCraftingMenu; private TopButton _buttonActionsMenu; private TopButton _buttonAdminMenu; private TopButton _buttonSandboxMenu; private TutorialWindow _tutorialWindow; private TargetingDoll _targetingDoll; private Button _combatModeButton; private VBoxContainer _combatPanelContainer; [Dependency] private readonly IResourceCache _resourceCache = default!; [Dependency] private readonly IInputManager _inputManager = default!; public Control HandsContainer { get; private set; } public Control SuspicionContainer { get; private set; } public Control RightInventoryQuickButtonContainer { get; private set; } public Control LeftInventoryQuickButtonContainer { get; private set; } public bool CombatPanelVisible { get => _combatPanelContainer.Visible; set => _combatPanelContainer.Visible = value; } public bool CombatModeActive { get => _combatModeButton.Pressed; set => _combatModeButton.Pressed = value; } public TargetingZone TargetingZone { get => _targetingDoll.ActiveZone; set => _targetingDoll.ActiveZone = value; } public Action OnCombatModeChanged { get; set; } public Action OnTargetingZoneChanged { get; set; } public void Initialize() { RootControl = new LayoutContainer(); LayoutContainer.SetAnchorPreset(RootControl, LayoutContainer.LayoutPreset.Wide); var escapeTexture = _resourceCache.GetTexture("/Textures/Interface/hamburger.svg.192dpi.png"); var characterTexture = _resourceCache.GetTexture("/Textures/Interface/character.svg.192dpi.png"); var inventoryTexture = _resourceCache.GetTexture("/Textures/Interface/inventory.svg.192dpi.png"); var craftingTexture = _resourceCache.GetTexture("/Textures/Interface/hammer.svg.192dpi.png"); var actionsTexture = _resourceCache.GetTexture("/Textures/Interface/fist.svg.192dpi.png"); var adminTexture = _resourceCache.GetTexture("/Textures/Interface/gavel.svg.192dpi.png"); var tutorialTexture = _resourceCache.GetTexture("/Textures/Interface/tutorial.svg.192dpi.png"); var sandboxTexture = _resourceCache.GetTexture("/Textures/Interface/sandbox.svg.192dpi.png"); _topButtonsContainer = new HBoxContainer { SeparationOverride = 8 }; RootControl.AddChild(_topButtonsContainer); LayoutContainer.SetAnchorAndMarginPreset(_topButtonsContainer, LayoutContainer.LayoutPreset.TopLeft, margin: 10); // the icon textures here should all have the same image height (32) but different widths, so in order to ensure // the buttons themselves are consistent widths we set a common custom min size Vector2 topMinSize = (42, 64); // Escape _buttonEscapeMenu = new TopButton(escapeTexture, EngineKeyFunctions.EscapeMenu, _inputManager) { ToolTip = Loc.GetString("Open escape menu."), CustomMinimumSize = (70, 64), StyleClasses = {StyleBase.ButtonOpenRight} }; _topButtonsContainer.AddChild(_buttonEscapeMenu); _buttonEscapeMenu.OnToggled += args => EscapeButtonToggled?.Invoke(args.Pressed); // Character _buttonCharacterMenu = new TopButton(characterTexture, ContentKeyFunctions.OpenCharacterMenu, _inputManager) { ToolTip = Loc.GetString("Open character menu."), CustomMinimumSize = topMinSize, Visible = false, StyleClasses = {StyleBase.ButtonSquare} }; _topButtonsContainer.AddChild(_buttonCharacterMenu); _buttonCharacterMenu.OnToggled += args => CharacterButtonToggled?.Invoke(args.Pressed); // Inventory _buttonInventoryMenu = new TopButton(inventoryTexture, ContentKeyFunctions.OpenInventoryMenu, _inputManager) { ToolTip = Loc.GetString("Open inventory menu."), CustomMinimumSize = topMinSize, Visible = false, StyleClasses = {StyleBase.ButtonSquare} }; _topButtonsContainer.AddChild(_buttonInventoryMenu); _buttonInventoryMenu.OnToggled += args => InventoryButtonToggled?.Invoke(args.Pressed); // Crafting _buttonCraftingMenu = new TopButton(craftingTexture, ContentKeyFunctions.OpenCraftingMenu, _inputManager) { ToolTip = Loc.GetString("Open crafting menu."), CustomMinimumSize = topMinSize, Visible = false, StyleClasses = {StyleBase.ButtonSquare} }; _topButtonsContainer.AddChild(_buttonCraftingMenu); _buttonCraftingMenu.OnToggled += args => CraftingButtonToggled?.Invoke(args.Pressed); // Actions _buttonActionsMenu = new TopButton(actionsTexture, ContentKeyFunctions.OpenActionsMenu, _inputManager) { ToolTip = Loc.GetString("Open actions menu."), CustomMinimumSize = topMinSize, Visible = false, StyleClasses = {StyleBase.ButtonSquare} }; _topButtonsContainer.AddChild(_buttonActionsMenu); _buttonActionsMenu.OnToggled += args => ActionsButtonToggled?.Invoke(args.Pressed); // Admin _buttonAdminMenu = new TopButton(adminTexture, ContentKeyFunctions.OpenAdminMenu, _inputManager) { ToolTip = Loc.GetString("Open admin menu."), CustomMinimumSize = topMinSize, Visible = false, StyleClasses = {StyleBase.ButtonSquare} }; _topButtonsContainer.AddChild(_buttonAdminMenu); _buttonAdminMenu.OnToggled += args => AdminButtonToggled?.Invoke(args.Pressed); // Sandbox _buttonSandboxMenu = new TopButton(sandboxTexture, ContentKeyFunctions.OpenSandboxWindow, _inputManager) { ToolTip = Loc.GetString("Open sandbox menu."), CustomMinimumSize = topMinSize, Visible = false, StyleClasses = {StyleBase.ButtonSquare} }; _topButtonsContainer.AddChild(_buttonSandboxMenu); _buttonSandboxMenu.OnToggled += args => SandboxButtonToggled?.Invoke(args.Pressed); // Tutorial _buttonTutorial = new TopButton(tutorialTexture, ContentKeyFunctions.OpenTutorial, _inputManager) { ToolTip = Loc.GetString("Open tutorial."), CustomMinimumSize = topMinSize, StyleClasses = {StyleBase.ButtonOpenLeft, TopButton.StyleClassRedTopButton}, }; _topButtonsContainer.AddChild(_buttonTutorial); _buttonTutorial.OnToggled += a => ButtonTutorialOnOnToggled(); _tutorialWindow = new TutorialWindow(); _tutorialWindow.OnClose += () => _buttonTutorial.Pressed = false; _inputManager.SetInputCommand(ContentKeyFunctions.OpenTutorial, InputCmdHandler.FromDelegate(s => ButtonTutorialOnOnToggled())); _combatPanelContainer = new VBoxContainer { Children = { (_combatModeButton = new Button { Text = Loc.GetString("Combat Mode"), ToggleMode = true }), (_targetingDoll = new TargetingDoll(_resourceCache)) } }; LayoutContainer.SetGrowHorizontal(_combatPanelContainer, LayoutContainer.GrowDirection.Begin); LayoutContainer.SetGrowVertical(_combatPanelContainer, LayoutContainer.GrowDirection.Begin); LayoutContainer.SetAnchorAndMarginPreset(_combatPanelContainer, LayoutContainer.LayoutPreset.BottomRight); LayoutContainer.SetMarginBottom(_combatPanelContainer, -10f); RootControl.AddChild(_combatPanelContainer); _combatModeButton.OnToggled += args => OnCombatModeChanged?.Invoke(args.Pressed); _targetingDoll.OnZoneChanged += args => OnTargetingZoneChanged?.Invoke(args); var centerBottomContainer = new HBoxContainer { SeparationOverride = 5 }; LayoutContainer.SetAnchorAndMarginPreset(centerBottomContainer, LayoutContainer.LayoutPreset.CenterBottom); LayoutContainer.SetGrowHorizontal(centerBottomContainer, LayoutContainer.GrowDirection.Both); LayoutContainer.SetGrowVertical(centerBottomContainer, LayoutContainer.GrowDirection.Begin); LayoutContainer.SetMarginBottom(centerBottomContainer, -10f); RootControl.AddChild(centerBottomContainer); HandsContainer = new MarginContainer { SizeFlagsVertical = Control.SizeFlags.ShrinkEnd }; RightInventoryQuickButtonContainer = new MarginContainer { SizeFlagsVertical = Control.SizeFlags.ShrinkEnd }; LeftInventoryQuickButtonContainer = new MarginContainer { SizeFlagsVertical = Control.SizeFlags.ShrinkEnd }; centerBottomContainer.AddChild(LeftInventoryQuickButtonContainer); centerBottomContainer.AddChild(HandsContainer); centerBottomContainer.AddChild(RightInventoryQuickButtonContainer); SuspicionContainer = new MarginContainer { SizeFlagsHorizontal = Control.SizeFlags.ShrinkCenter }; RootControl.AddChild(SuspicionContainer); LayoutContainer.SetAnchorAndMarginPreset(SuspicionContainer, LayoutContainer.LayoutPreset.BottomLeft, margin: 10); LayoutContainer.SetGrowHorizontal(SuspicionContainer, LayoutContainer.GrowDirection.End); LayoutContainer.SetGrowVertical(SuspicionContainer, LayoutContainer.GrowDirection.Begin); } private void ButtonTutorialOnOnToggled() { _buttonTutorial.StyleClasses.Remove(TopButton.StyleClassRedTopButton); if (_tutorialWindow.IsOpen) { if (!_tutorialWindow.IsAtFront()) { _tutorialWindow.MoveToFront(); _buttonTutorial.Pressed = true; } else { _tutorialWindow.Close(); _buttonTutorial.Pressed = false; } } else { _tutorialWindow.OpenCentered(); _buttonTutorial.Pressed = true; } } public Control RootControl { get; private set; } public bool EscapeButtonDown { get => _buttonEscapeMenu.Pressed; set => _buttonEscapeMenu.Pressed = value; } public Action EscapeButtonToggled { get; set; } public bool CharacterButtonDown { get => _buttonCharacterMenu.Pressed; set => _buttonCharacterMenu.Pressed = value; } public bool CharacterButtonVisible { get => _buttonCharacterMenu.Visible; set => _buttonCharacterMenu.Visible = value; } public Action CharacterButtonToggled { get; set; } public bool InventoryButtonDown { get => _buttonInventoryMenu.Pressed; set => _buttonInventoryMenu.Pressed = value; } public bool InventoryButtonVisible { get => _buttonInventoryMenu.Visible; set => _buttonInventoryMenu.Visible = value; } public Action InventoryButtonToggled { get; set; } public bool CraftingButtonDown { get => _buttonCraftingMenu.Pressed; set => _buttonCraftingMenu.Pressed = value; } public bool CraftingButtonVisible { get => _buttonCraftingMenu.Visible; set => _buttonCraftingMenu.Visible = value; } public Action CraftingButtonToggled { get; set; } public bool ActionsButtonDown { get => _buttonActionsMenu.Pressed; set => _buttonActionsMenu.Pressed = value; } public bool ActionsButtonVisible { get => _buttonActionsMenu.Visible; set => _buttonActionsMenu.Visible = value; } public Action ActionsButtonToggled { get; set; } public bool AdminButtonDown { get => _buttonAdminMenu.Pressed; set => _buttonAdminMenu.Pressed = value; } public bool AdminButtonVisible { get => _buttonAdminMenu.Visible; set => _buttonAdminMenu.Visible = value; } public Action AdminButtonToggled { get; set; } public bool SandboxButtonDown { get => _buttonSandboxMenu.Pressed; set => _buttonSandboxMenu.Pressed = value; } public bool SandboxButtonVisible { get => _buttonSandboxMenu.Visible; set => _buttonSandboxMenu.Visible = value; } public Action SandboxButtonToggled { get; set; } public sealed class TopButton : ContainerButton { public const string StyleClassLabelTopButton = "topButtonLabel"; public const string StyleClassRedTopButton = "topButtonLabel"; private const float CustomTooltipDelay = 0.4f; private static readonly Color ColorNormal = Color.FromHex("#7b7e9e"); private static readonly Color ColorRedNormal = Color.FromHex("#FEFEFE"); private static readonly Color ColorHovered = Color.FromHex("#9699bb"); private static readonly Color ColorRedHovered = Color.FromHex("#FFFFFF"); private static readonly Color ColorPressed = Color.FromHex("#789B8C"); private const float VertPad = 8f; private Color NormalColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedNormal : ColorNormal; private Color HoveredColor => HasStyleClass(StyleClassRedTopButton) ? ColorRedHovered : ColorHovered; private readonly TextureRect _textureRect; private readonly Label _label; private readonly BoundKeyFunction _function; private readonly IInputManager _inputManager; public TopButton(Texture texture, BoundKeyFunction function, IInputManager inputManager) { _function = function; _inputManager = inputManager; TooltipDelay = CustomTooltipDelay; AddChild( new VBoxContainer { Children = { new Control {CustomMinimumSize = (0, VertPad)}, (_textureRect = new TextureRect { TextureScale = (0.5f, 0.5f), Texture = texture, SizeFlagsHorizontal = SizeFlags.ShrinkCenter, SizeFlagsVertical = SizeFlags.Expand | SizeFlags.ShrinkCenter, ModulateSelfOverride = NormalColor, Stretch = TextureRect.StretchMode.KeepCentered }), new Control {CustomMinimumSize = (0, VertPad)}, (_label = new Label { Text = ShortKeyName(_function), SizeFlagsHorizontal = SizeFlags.ShrinkCenter, ModulateSelfOverride = NormalColor, StyleClasses = {StyleClassLabelTopButton} }) } } ); ToggleMode = true; } protected override void EnteredTree() { _inputManager.OnKeyBindingAdded += OnKeyBindingChanged; _inputManager.OnKeyBindingRemoved += OnKeyBindingChanged; } protected override void ExitedTree() { _inputManager.OnKeyBindingAdded -= OnKeyBindingChanged; _inputManager.OnKeyBindingRemoved -= OnKeyBindingChanged; } private void OnKeyBindingChanged(IKeyBinding obj) { _label.Text = ShortKeyName(_function); } private string ShortKeyName(BoundKeyFunction keyFunction) { // need to use shortened key names so they fit in the buttons. return TryGetShortKeyName(keyFunction, out var name) ? Loc.GetString(name) : " "; } private bool TryGetShortKeyName(BoundKeyFunction keyFunction, out string name) { if (_inputManager.TryGetKeyBinding(keyFunction, out var binding)) { // can't possibly fit a modifier key in the top button, so omit it var key = binding.BaseKey; if (binding.Mod1 != Unknown || binding.Mod2 != Unknown || binding.Mod3 != Unknown) { name = null; return false; } name = null; name = key switch { Apostrophe => "'", Comma => ",", Delete => "Del", Down => "Dwn", Escape => "Esc", Equal => "=", Home => "Hom", Insert => "Ins", Left => "Lft", Menu => "Men", Minus => "-", Num0 => "0", Num1 => "1", Num2 => "2", Num3 => "3", Num4 => "4", Num5 => "5", Num6 => "6", Num7 => "7", Num8 => "8", Num9 => "9", Pause => "||", Period => ".", Return => "Ret", Right => "Rgt", Slash => "/", Space => "Spc", Tab => "Tab", Tilde => "~", BackSlash => "\\", BackSpace => "Bks", LBracket => "[", MouseButton4 => "M4", MouseButton5 => "M5", MouseButton6 => "M6", MouseButton7 => "M7", MouseButton8 => "M8", MouseButton9 => "M9", MouseLeft => "ML", MouseMiddle => "MM", MouseRight => "MR", NumpadDecimal => "N.", NumpadDivide => "N/", NumpadEnter => "Ent", NumpadMultiply => "*", NumpadNum0 => "0", NumpadNum1 => "1", NumpadNum2 => "2", NumpadNum3 => "3", NumpadNum4 => "4", NumpadNum5 => "5", NumpadNum6 => "6", NumpadNum7 => "7", NumpadNum8 => "8", NumpadNum9 => "9", NumpadSubtract => "N-", PageDown => "PgD", PageUp => "PgU", RBracket => "]", SemiColon => ";", _ => DefaultShortKeyName(keyFunction) }; return name != null; } name = null; return false; } private string DefaultShortKeyName(BoundKeyFunction keyFunction) { var name = FormattedMessage.EscapeText(_inputManager.GetKeyFunctionButtonString(keyFunction)); return name.Length > 3 ? null : name; } protected override void StylePropertiesChanged() { // colors of children depend on style, so ensure we update when style is changed base.StylePropertiesChanged(); UpdateChildColors(); } private void UpdateChildColors() { if (_label == null || _textureRect == null) return; switch (DrawMode) { case DrawModeEnum.Normal: _textureRect.ModulateSelfOverride = NormalColor; _label.ModulateSelfOverride = NormalColor; break; case DrawModeEnum.Pressed: _textureRect.ModulateSelfOverride = ColorPressed; _label.ModulateSelfOverride = ColorPressed; break; case DrawModeEnum.Hover: _textureRect.ModulateSelfOverride = HoveredColor; _label.ModulateSelfOverride = HoveredColor; break; case DrawModeEnum.Disabled: break; } } protected override void DrawModeChanged() { base.DrawModeChanged(); UpdateChildColors(); } } } }