diff --git a/Content.Client/Actions/ActionsSystem.cs b/Content.Client/Actions/ActionsSystem.cs index 15afc08e19..3611287269 100644 --- a/Content.Client/Actions/ActionsSystem.cs +++ b/Content.Client/Actions/ActionsSystem.cs @@ -183,8 +183,7 @@ namespace Content.Client.Actions if (uid != _playerManager.LocalPlayer?.ControlledEntity) return; - LinkActions?.Invoke(component); - PlayerActions = component; + LinkAllActions(component); } private void OnPlayerDetached(EntityUid uid, ActionsComponent component, PlayerDetachedEvent? args = null) @@ -192,10 +191,27 @@ namespace Content.Client.Actions if (uid != _playerManager.LocalPlayer?.ControlledEntity) return; + UnlinkAllActions(); + } + + public void UnlinkAllActions() + { UnlinkActions?.Invoke(); PlayerActions = null; } + public void LinkAllActions(ActionsComponent? actions = null) + { + var player = _playerManager.LocalPlayer?.ControlledEntity; + if (player == null || !Resolve(player.Value, ref actions)) + { + return; + } + + LinkActions?.Invoke(actions); + PlayerActions = actions; + } + public override void Shutdown() { base.Shutdown(); diff --git a/Content.Client/Gameplay/GameplayState.cs b/Content.Client/Gameplay/GameplayState.cs index 4d9836d830..ca102e32ae 100644 --- a/Content.Client/Gameplay/GameplayState.cs +++ b/Content.Client/Gameplay/GameplayState.cs @@ -2,6 +2,16 @@ using Content.Client.Construction.UI; using Content.Client.Hands; using Content.Client.UserInterface.Controls; using Content.Client.UserInterface.Screens; +using Content.Client.UserInterface.Systems.Actions; +using Content.Client.UserInterface.Systems.Alerts; +using Content.Client.UserInterface.Systems.Chat; +using Content.Client.UserInterface.Systems.Ghost; +using Content.Client.UserInterface.Systems.Hands; +using Content.Client.UserInterface.Systems.Hotbar; +using Content.Client.UserInterface.Systems.Hotbar.Widgets; +using Content.Client.UserInterface.Systems.Inventory; +using Content.Client.UserInterface.Systems.MenuBar; +using Content.Client.UserInterface.Systems.Viewport; using Content.Client.Viewport; using Content.Shared.CCVar; using Robust.Client.Graphics; @@ -26,36 +36,47 @@ namespace Content.Client.Gameplay [Dependency] private readonly IConfigurationManager _configurationManager = default!; [Dependency] private readonly IEntityManager _entMan = default!; - protected override Type? LinkedScreenType => typeof(DefaultGameScreen); - public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15); private FpsCounter _fpsCounter = default!; - public MainViewport Viewport { get; private set; } = default!; + public MainViewport Viewport => _uiManager.ActiveScreen!.GetWidget()!; + + private readonly GhostUIController _ghostController; + private readonly ActionUIController _actionController; + private readonly AlertsUIController _alertsController; + private readonly HotbarUIController _hotbarController; + private readonly ChatUIController _chatController; + private readonly ViewportUIController _viewportController; + private readonly GameTopMenuBarUIController _menuController; public GameplayState() { IoCManager.InjectDependencies(this); + + _ghostController = _uiManager.GetUIController(); + _actionController = _uiManager.GetUIController(); + _alertsController = _uiManager.GetUIController(); + _hotbarController = _uiManager.GetUIController(); + _chatController = _uiManager.GetUIController(); + _viewportController = _uiManager.GetUIController(); + _menuController = _uiManager.GetUIController(); } protected override void Startup() { base.Startup(); - Viewport = new MainViewport - { - Viewport = - { - ViewportSize = ViewportSize - } - }; - UserInterfaceManager.StateRoot.AddChild(Viewport); - LayoutContainer.SetAnchorPreset(Viewport, LayoutContainer.LayoutPreset.Wide); - Viewport.SetPositionFirst(); - _eyeManager.MainViewport = Viewport.Viewport; + + LoadMainScreen(); + + // Add the hand-item overlay. _overlayManager.AddOverlay(new ShowHandItemOverlay()); + + // FPS counter. + // yeah this can just stay here, whatever _fpsCounter = new FpsCounter(_gameTiming); UserInterfaceManager.PopupRoot.AddChild(_fpsCounter); _fpsCounter.Visible = _configurationManager.GetCVar(CCVars.HudFpsCounterVisible); _configurationManager.OnValueChanged(CCVars.HudFpsCounterVisible, (show) => { _fpsCounter.Visible = show; }); + _configurationManager.OnValueChanged(CCVars.UILayout, _ => ReloadMainScreen()); } protected override void Shutdown() @@ -63,36 +84,65 @@ namespace Content.Client.Gameplay _overlayManager.RemoveOverlay(); base.Shutdown(); - Viewport.Dispose(); // Clear viewport to some fallback, whatever. _eyeManager.MainViewport = UserInterfaceManager.MainViewport; _fpsCounter.Dispose(); _uiManager.ClearWindows(); + UnloadMainScreen(); } - public override void FrameUpdate(FrameEventArgs e) + public void ReloadMainScreen() { - base.FrameUpdate(e); - - Viewport.Viewport.Eye = _eyeManager.CurrentEye; - - // verify that the current eye is not "null". Fuck IEyeManager. - - var ent = _playerMan.LocalPlayer?.ControlledEntity; - if (_eyeManager.CurrentEye.Position != default || ent == null) + if (_uiManager.ActiveScreen == null) + { return; + } - _entMan.TryGetComponent(ent, out EyeComponent? eye); - - if (eye?.Eye == _eyeManager.CurrentEye - && _entMan.GetComponent(ent.Value).WorldPosition == default) - return; // nothing to worry about, the player is just in null space... actually that is probably a problem? - - // Currently, this shouldn't happen. This likely happened because the main eye was set to null. When this - // does happen it can create hard to troubleshoot bugs, so lets print some helpful warnings: - Logger.Warning($"Main viewport's eye is in nullspace (main eye is null?). Attached entity: {_entMan.ToPrettyString(ent.Value)}. Entity has eye comp: {eye != null}"); + UnloadMainScreen(); + LoadMainScreen(); } + private void UnloadMainScreen() + { + _chatController.SetMainChat(false); + _menuController.UnloadButtons(); + _uiManager.UnloadScreen(); + } + + private void LoadMainScreen() + { + var screenTypeString = _configurationManager.GetCVar(CCVars.UILayout); + if (!Enum.TryParse(screenTypeString, out ScreenType screenType)) + { + screenType = default; + } + + switch (screenType) + { + case ScreenType.Default: + _uiManager.LoadScreen(); + break; + case ScreenType.Separated: + _uiManager.LoadScreen(); + break; + } + + _chatController.SetMainChat(true); + _viewportController.ReloadViewport(); + _menuController.LoadButtons(); + + // TODO: This could just be like, the equivalent of an event or something + _ghostController.UpdateGui(); + _actionController.RegisterActionContainer(); + _alertsController.SyncAlerts(); + _hotbarController.ReloadHotbar(); + + var viewportContainer = _uiManager.ActiveScreen!.FindControl("ViewportContainer"); + _chatController.SetSpeechBubbleRoot(viewportContainer); + } + + + protected override void OnKeyBindStateChanged(ViewportBoundKeyEventArgs args) { if (args.Viewport == null) diff --git a/Content.Client/Hands/Systems/HandsSystem.cs b/Content.Client/Hands/Systems/HandsSystem.cs index 66d518aa40..4d1dfeb732 100644 --- a/Content.Client/Hands/Systems/HandsSystem.cs +++ b/Content.Client/Hands/Systems/HandsSystem.cs @@ -130,6 +130,16 @@ namespace Content.Client.Hands.Systems } #endregion + public void ReloadHandButtons() + { + if (!TryGetPlayerHands(out var hands)) + { + return; + } + + OnPlayerHandsAdded?.Invoke(hands); + } + public override void DoDrop(EntityUid uid, Hand hand, bool doDropInteraction = true, SharedHandsComponent? hands = null) { base.DoDrop(uid, hand, doDropInteraction, hands); diff --git a/Content.Client/Inventory/ClientInventorySystem.cs b/Content.Client/Inventory/ClientInventorySystem.cs index 4db601abea..76785d3aaa 100644 --- a/Content.Client/Inventory/ClientInventorySystem.cs +++ b/Content.Client/Inventory/ClientInventorySystem.cs @@ -158,6 +158,18 @@ namespace Content.Client.Inventory } } + public void ReloadInventory(ClientInventoryComponent? component = null) + { + var player = _playerManager.LocalPlayer?.ControlledEntity; + if (player == null || !Resolve(player.Value, ref component)) + { + return; + } + + OnUnlinkInventory?.Invoke(); + OnLinkInventory?.Invoke(component); + } + public void SetSlotHighlight(EntityUid owner, ClientInventoryComponent component, string slotName, bool state) { var oldData = component.SlotData[slotName]; diff --git a/Content.Client/Lobby/LobbyState.cs b/Content.Client/Lobby/LobbyState.cs index 185f129e42..69ff8600d1 100644 --- a/Content.Client/Lobby/LobbyState.cs +++ b/Content.Client/Lobby/LobbyState.cs @@ -4,6 +4,7 @@ using Content.Client.LateJoin; using Content.Client.Lobby.UI; using Content.Client.Preferences; using Content.Client.Preferences.UI; +using Content.Client.UserInterface.Systems.Chat; using Content.Client.Voting; using Robust.Client; using Robust.Client.Console; @@ -34,45 +35,47 @@ namespace Content.Client.Lobby [Dependency] private readonly IConfigurationManager _configurationManager = default!; [ViewVariables] private CharacterSetupGui? _characterSetup; - [ViewVariables] private LobbyGui? _lobby; private ClientGameTicker _gameTicker = default!; + protected override Type? LinkedScreenType { get; } = typeof(LobbyGui); + private LobbyGui Lobby => (LobbyGui) _userInterfaceManager.ActiveScreen!; + protected override void Startup() { + var chatController = _userInterfaceManager.GetUIController(); _gameTicker = _entityManager.System(); _characterSetup = new CharacterSetupGui(_entityManager, _resourceCache, _preferencesManager, _prototypeManager, _configurationManager); LayoutContainer.SetAnchorPreset(_characterSetup, LayoutContainer.LayoutPreset.Wide); - _lobby = new LobbyGui(_entityManager, _preferencesManager); - _userInterfaceManager.StateRoot.AddChild(_lobby); + chatController.SetMainChat(true); _characterSetup.CloseButton.OnPressed += _ => { - _userInterfaceManager.StateRoot.AddChild(_lobby); + _userInterfaceManager.StateRoot.AddChild(Lobby); _userInterfaceManager.StateRoot.RemoveChild(_characterSetup); }; _characterSetup.SaveButton.OnPressed += _ => { _characterSetup.Save(); - _lobby?.CharacterPreview.UpdateUI(); + Lobby?.CharacterPreview.UpdateUI(); }; - LayoutContainer.SetAnchorPreset(_lobby, LayoutContainer.LayoutPreset.Wide); - _voteManager.SetPopupContainer(_lobby.VoteContainer); - _lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you... + LayoutContainer.SetAnchorPreset(Lobby, LayoutContainer.LayoutPreset.Wide); + _voteManager.SetPopupContainer(Lobby.VoteContainer); + Lobby.ServerName.Text = _baseClient.GameInfo?.ServerName; //The eye of refactor gazes upon you... UpdateLobbyUi(); - _lobby.CharacterPreview.CharacterSetupButton.OnPressed += _ => + Lobby.CharacterPreview.CharacterSetupButton.OnPressed += _ => { SetReady(false); - _userInterfaceManager.StateRoot.RemoveChild(_lobby); + _userInterfaceManager.StateRoot.RemoveChild(Lobby); _userInterfaceManager.StateRoot.AddChild(_characterSetup); }; - _lobby.ReadyButton.OnPressed += _ => + Lobby.ReadyButton.OnPressed += _ => { if (!_gameTicker.IsGameStarted) { @@ -82,13 +85,13 @@ namespace Content.Client.Lobby new LateJoinGui().OpenCentered(); }; - _lobby.ReadyButton.OnToggled += args => + Lobby.ReadyButton.OnToggled += args => { SetReady(args.Pressed); }; - _lobby.LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect"); - _lobby.OptionsButton.OnPressed += _ => _userInterfaceManager.GetUIController().ToggleWindow(); + Lobby.LeaveButton.OnPressed += _ => _consoleHost.ExecuteCommand("disconnect"); + Lobby.OptionsButton.OnPressed += _ => _userInterfaceManager.GetUIController().ToggleWindow(); _gameTicker.InfoBlobUpdated += UpdateLobbyUi; @@ -98,25 +101,22 @@ namespace Content.Client.Lobby protected override void Shutdown() { + var chatController = _userInterfaceManager.GetUIController(); + chatController.SetMainChat(false); _gameTicker.InfoBlobUpdated -= UpdateLobbyUi; _gameTicker.LobbyStatusUpdated -= LobbyStatusUpdated; _gameTicker.LobbyLateJoinStatusUpdated -= LobbyLateJoinStatusUpdated; - _lobby?.Dispose(); _characterSetup?.Dispose(); - _lobby = null; _characterSetup = null; } public override void FrameUpdate(FrameEventArgs e) { - if (_lobby == null) - return; - if (_gameTicker.IsGameStarted) { - _lobby.StartTime.Text = string.Empty; - _lobby.StationTime.Text = Loc.GetString("lobby-state-player-status-station-time", ("stationTime", _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan).ToString("hh\\:mm"))); + Lobby.StartTime.Text = string.Empty; + Lobby.StationTime.Text = Loc.GetString("lobby-state-player-status-station-time", ("stationTime", _gameTiming.CurTime.Subtract(_gameTicker.RoundStartTimeSpan).ToString("hh\\:mm"))); return; } @@ -140,8 +140,8 @@ namespace Content.Client.Lobby } } - _lobby.StationTime.Text = Loc.GetString("lobby-state-player-status-station-time", ("stationTime", TimeSpan.Zero.ToString("hh\\:mm"))); - _lobby.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); + Lobby.StationTime.Text = Loc.GetString("lobby-state-player-status-station-time", ("stationTime", TimeSpan.Zero.ToString("hh\\:mm"))); + Lobby.StartTime.Text = Loc.GetString("lobby-state-round-start-countdown-text", ("timeLeft", text)); } private void LobbyStatusUpdated() @@ -152,50 +152,43 @@ namespace Content.Client.Lobby private void LobbyLateJoinStatusUpdated() { - if (_lobby == null) return; - _lobby.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; + Lobby.ReadyButton.Disabled = _gameTicker.DisallowedLateJoin; } private void UpdateLobbyUi() { - if (_lobby == null) - return; - if (_gameTicker.IsGameStarted) { - _lobby.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); - _lobby.ReadyButton.ToggleMode = false; - _lobby.ReadyButton.Pressed = false; - _lobby.ObserveButton.Disabled = false; + Lobby.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-join-state"); + Lobby.ReadyButton.ToggleMode = false; + Lobby.ReadyButton.Pressed = false; + Lobby.ObserveButton.Disabled = false; } else { - _lobby.StartTime.Text = string.Empty; - _lobby.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-ready-up-state"); - _lobby.ReadyButton.ToggleMode = true; - _lobby.ReadyButton.Disabled = false; - _lobby.ReadyButton.Pressed = _gameTicker.AreWeReady; - _lobby.ObserveButton.Disabled = true; + Lobby.StartTime.Text = string.Empty; + Lobby.ReadyButton.Text = Loc.GetString("lobby-state-ready-button-ready-up-state"); + Lobby.ReadyButton.ToggleMode = true; + Lobby.ReadyButton.Disabled = false; + Lobby.ReadyButton.Pressed = _gameTicker.AreWeReady; + Lobby.ObserveButton.Disabled = true; } if (_gameTicker.ServerInfoBlob != null) { - _lobby.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); + Lobby.ServerInfo.SetInfoBlob(_gameTicker.ServerInfoBlob); } } private void UpdateLobbyBackground() { - if (_lobby == null) - return; - if (_gameTicker.LobbyBackground != null) { - _lobby.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground ); + Lobby.Background.Texture = _resourceCache.GetResource(_gameTicker.LobbyBackground ); } else { - _lobby.Background.Texture = null; + Lobby.Background.Texture = null; } } diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml b/Content.Client/Lobby/UI/LobbyGui.xaml index 7b90d84db8..0adf5d486d 100644 --- a/Content.Client/Lobby/UI/LobbyGui.xaml +++ b/Content.Client/Lobby/UI/LobbyGui.xaml @@ -1,4 +1,4 @@ - - + @@ -47,7 +47,7 @@ @@ -71,9 +71,9 @@ - + - + diff --git a/Content.Client/Lobby/UI/LobbyGui.xaml.cs b/Content.Client/Lobby/UI/LobbyGui.xaml.cs index b332809a79..d002fa4e42 100644 --- a/Content.Client/Lobby/UI/LobbyGui.xaml.cs +++ b/Content.Client/Lobby/UI/LobbyGui.xaml.cs @@ -15,14 +15,12 @@ using static Robust.Client.UserInterface.Controls.BoxContainer; namespace Content.Client.Lobby.UI { [GenerateTypedNameReferences] - internal sealed partial class LobbyGui : Control + internal sealed partial class LobbyGui : UIScreen { - public LobbyGui(IEntityManager entityManager, - IClientPreferencesManager preferencesManager) + public LobbyGui() { RobustXamlLoader.Load(this); - ServerName.HorizontalExpand = true; - ServerName.HorizontalAlignment = HAlignment.Center; + SetAnchorPreset(MainContainer, LayoutPreset.Wide); } } } diff --git a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml index 2008e7e094..caf4dca99a 100644 --- a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml +++ b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml @@ -32,6 +32,18 @@ MinWidth="200" /> + + + + diff --git a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs index a54122280a..39b7f49331 100644 --- a/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs +++ b/Content.Client/Options/UI/Tabs/GraphicsTab.xaml.cs @@ -1,3 +1,4 @@ +using Content.Client.UserInterface.Screens; using Content.Shared.CCVar; using Content.Shared.HUD; using Robust.Client.AutoGenerated; @@ -58,6 +59,27 @@ namespace Content.Client.Options.UI.Tabs } HudThemeOption.OnItemSelected += OnHudThemeChanged; + var hudLayout = _cfg.GetCVar(CCVars.UILayout); + var id = 0; + foreach (var layout in Enum.GetValues(typeof(ScreenType))) + { + var name = layout.ToString()!; + HudLayoutOption.AddItem(name, id); + if (name == hudLayout) + { + HudLayoutOption.SelectId(id); + } + HudLayoutOption.SetItemMetadata(id, name); + + id++; + } + + HudLayoutOption.OnItemSelected += args => + { + HudLayoutOption.SelectId(args.Id); + UpdateApplyButton(); + }; + ViewportStretchCheckBox.OnToggled += _ => { UpdateViewportScale(); @@ -70,6 +92,12 @@ namespace Content.Client.Options.UI.Tabs UpdateViewportScale(); }; + ViewportWidthSlider.OnValueChanged += _ => + { + UpdateViewportWidthDisplay(); + UpdateApplyButton(); + }; + ShowHeldItemCheckBox.OnToggled += OnCheckBoxToggled; IntegerScalingCheckBox.OnToggled += OnCheckBoxToggled; ViewportLowResCheckBox.OnToggled += OnCheckBoxToggled; @@ -88,7 +116,13 @@ namespace Content.Client.Options.UI.Tabs ParallaxLowQualityCheckBox.Pressed = _cfg.GetCVar(CCVars.ParallaxLowQuality); FpsCounterCheckBox.Pressed = _cfg.GetCVar(CCVars.HudFpsCounterVisible); ShowHeldItemCheckBox.Pressed = _cfg.GetCVar(CCVars.HudHeldItemShow); + ViewportWidthSlider.Value = _cfg.GetCVar(CCVars.ViewportWidth); + _cfg.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportWidthRange()); + _cfg.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportWidthRange()); + + UpdateViewportWidthRange(); + UpdateViewportWidthDisplay(); UpdateViewportScale(); UpdateApplyButton(); } @@ -125,6 +159,13 @@ namespace Content.Client.Options.UI.Tabs _cfg.SetCVar(CCVars.ParallaxLowQuality, ParallaxLowQualityCheckBox.Pressed); _cfg.SetCVar(CCVars.HudHeldItemShow, ShowHeldItemCheckBox.Pressed); _cfg.SetCVar(CCVars.HudFpsCounterVisible, FpsCounterCheckBox.Pressed); + _cfg.SetCVar(CCVars.ViewportWidth, (int) ViewportWidthSlider.Value); + + if (HudLayoutOption.SelectedMetadata is string opt) + { + _cfg.SetCVar(CCVars.UILayout, opt); + } + _cfg.SaveToFile(); UpdateApplyButton(); } @@ -154,6 +195,8 @@ namespace Content.Client.Options.UI.Tabs var isPLQSame = ParallaxLowQualityCheckBox.Pressed == _cfg.GetCVar(CCVars.ParallaxLowQuality); var isShowHeldItemSame = ShowHeldItemCheckBox.Pressed == _cfg.GetCVar(CCVars.HudHeldItemShow); var isFpsCounterVisibleSame = FpsCounterCheckBox.Pressed == _cfg.GetCVar(CCVars.HudFpsCounterVisible); + var isWidthSame = (int) ViewportWidthSlider.Value == _cfg.GetCVar(CCVars.ViewportWidth); + var isLayoutSame = HudLayoutOption.SelectedMetadata is string opt && opt == _cfg.GetCVar(CCVars.UILayout); ApplyButton.Disabled = isVSyncSame && isFullscreenSame && @@ -166,7 +209,9 @@ namespace Content.Client.Options.UI.Tabs isPLQSame && isHudThemeSame && isShowHeldItemSame && - isFpsCounterVisibleSame; + isFpsCounterVisibleSame && + isWidthSame && + isLayoutSame; } private bool ConfigIsFullscreen => @@ -242,5 +287,19 @@ namespace Content.Client.Options.UI.Tabs IntegerScalingCheckBox.Visible = ViewportStretchCheckBox.Pressed; ViewportScaleText.Text = Loc.GetString("ui-options-vp-scale", ("scale", ViewportScaleSlider.Value)); } + + private void UpdateViewportWidthRange() + { + var min = _cfg.GetCVar(CCVars.ViewportMinimumWidth); + var max = _cfg.GetCVar(CCVars.ViewportMaximumWidth); + + ViewportWidthSlider.MinValue = min; + ViewportWidthSlider.MaxValue = max; + } + + private void UpdateViewportWidthDisplay() + { + ViewportWidthSliderDisplay.Text = Loc.GetString("ui-options-vp-width", ("width", (int) ViewportWidthSlider.Value)); + } } } diff --git a/Content.Client/UserInterface/Controls/MainViewport.cs b/Content.Client/UserInterface/Controls/MainViewport.cs index 373a33db60..05c6ed7411 100644 --- a/Content.Client/UserInterface/Controls/MainViewport.cs +++ b/Content.Client/UserInterface/Controls/MainViewport.cs @@ -1,6 +1,7 @@ using Content.Client.Viewport; using Content.Shared.CCVar; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; using Robust.Shared.Configuration; namespace Content.Client.UserInterface.Controls @@ -9,7 +10,7 @@ namespace Content.Client.UserInterface.Controls /// Wrapper for that listens to configuration variables. /// Also does NN-snapping within tolerances. /// - public sealed class MainViewport : Control + public sealed class MainViewport : UIWidget { [Dependency] private readonly IConfigurationManager _cfg = default!; [Dependency] private readonly ViewportManager _vpManager = default!; diff --git a/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml b/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml index c1ce4e226c..a6d489147a 100644 --- a/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml +++ b/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml @@ -7,14 +7,18 @@ xmlns:alerts="clr-namespace:Content.Client.UserInterface.Systems.Alerts.Widgets" xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Ghost.Widgets" xmlns:hotbar="clr-namespace:Content.Client.UserInterface.Systems.Hotbar.Widgets" + xmlns:controls="clr-namespace:Content.Client.UserInterface.Controls" Name="DefaultHud" VerticalExpand="False" VerticalAlignment="Bottom" HorizontalAlignment="Center"> + + + - + diff --git a/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml.cs b/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml.cs index c8d44e4e8c..abbaad7b7d 100644 --- a/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml.cs +++ b/Content.Client/UserInterface/Screens/DefaultGameScreen.xaml.cs @@ -13,6 +13,8 @@ public sealed partial class DefaultGameScreen : UIScreen AutoscaleMaxResolution = new Vector2i(1080, 770); + SetAnchorPreset(MainViewport, LayoutPreset.Wide); + SetAnchorPreset(ViewportContainer, LayoutPreset.Wide); SetAnchorAndMarginPreset(TopBar, LayoutPreset.TopLeft, margin: 10); SetAnchorAndMarginPreset(Actions, LayoutPreset.BottomLeft, margin: 10); SetAnchorAndMarginPreset(Ghost, LayoutPreset.BottomWide, margin: 80); diff --git a/Content.Client/UserInterface/Screens/ScreenType.cs b/Content.Client/UserInterface/Screens/ScreenType.cs new file mode 100644 index 0000000000..595dc79556 --- /dev/null +++ b/Content.Client/UserInterface/Screens/ScreenType.cs @@ -0,0 +1,13 @@ +namespace Content.Client.UserInterface.Screens; + +public enum ScreenType +{ + /// + /// The modern SS14 user interface. + /// + Default, + /// + /// The classic SS13 user interface. + /// + Separated +} diff --git a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml new file mode 100644 index 0000000000..a24aeb5d28 --- /dev/null +++ b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml @@ -0,0 +1,35 @@ + + + + + + + + + + + + + + + + + + + + + diff --git a/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml.cs b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml.cs new file mode 100644 index 0000000000..f15819484d --- /dev/null +++ b/Content.Client/UserInterface/Screens/SeparatedChatGameScreen.xaml.cs @@ -0,0 +1,24 @@ +using Robust.Client.AutoGenerated; +using Robust.Client.UserInterface; +using Robust.Client.UserInterface.XAML; + +namespace Content.Client.UserInterface.Screens; + +[GenerateTypedNameReferences] +public sealed partial class SeparatedChatGameScreen : UIScreen +{ + public SeparatedChatGameScreen() + { + RobustXamlLoader.Load(this); + + AutoscaleMaxResolution = new Vector2i(1080, 770); + + SetAnchorPreset(ScreenContainer, LayoutPreset.Wide); + SetAnchorPreset(ViewportContainer, LayoutPreset.Wide); + SetAnchorPreset(MainViewport, LayoutPreset.Wide); + SetAnchorAndMarginPreset(Actions, LayoutPreset.BottomLeft, margin: 10); + SetAnchorAndMarginPreset(Ghost, LayoutPreset.BottomWide, margin: 80); + SetAnchorAndMarginPreset(Hotbar, LayoutPreset.BottomWide, margin: 5); + SetAnchorAndMarginPreset(Alerts, LayoutPreset.CenterRight, margin: 10); + } +} diff --git a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs index 52b6c64b36..096fd0bcd2 100644 --- a/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs +++ b/Content.Client/UserInterface/Systems/Actions/ActionUIController.cs @@ -52,7 +52,7 @@ public sealed class ActionUIController : UIController, IOnStateChanged UIManager.GetActiveUIWidgetOrNull()?.ActionButton; private ActionPage CurrentPage => _pages[_currentPageIndex]; public bool IsDragging => _menuDragHelper.IsDragging; @@ -88,7 +88,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); - _actionButton = UIManager.GetActiveUIWidget().ActionButton; _actionsBar = UIManager.GetActiveUIWidget(); LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); @@ -97,7 +96,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); } + public void UnloadButton() + { + if (ActionButton == null) + { + return; + } + + ActionButton.OnPressed -= ActionButtonPressed; + } + + public void LoadButton() + { + if (ActionButton == null) + { + return; + } + + ActionButton.OnPressed += ActionButtonPressed; + } + private void OnWindowOpened() { - if (_actionButton != null) - _actionButton.Pressed = true; + if (ActionButton != null) + ActionButton.Pressed = true; } private void OnWindowClosed() { - if (_actionButton != null) - _actionButton.Pressed = false; + if (ActionButton != null) + ActionButton.Pressed = false; } public void OnStateExited(GameplayState state) @@ -186,12 +204,6 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); } @@ -607,12 +619,37 @@ public sealed class ActionUIController : UIController, IOnStateChanged(); + if (widget == null) + { + return; + } + + _actionsSystem?.UnlinkAllActions(); + + RegisterActionContainer(widget.ActionsContainer); + + _actionsSystem?.LinkAllActions(); + } + public void RegisterActionContainer(ActionButtonContainer container) { if (_container != null) { - Logger.Warning("Action container already defined for UI controller"); - return; + _container.ActionPressed -= OnActionPressed; + _container.ActionUnpressed -= OnActionPressed; } _container = container; diff --git a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs index be3b1fffb6..b9be5533a4 100644 --- a/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs +++ b/Content.Client/UserInterface/Systems/Admin/AdminUIController.cs @@ -30,14 +30,13 @@ public sealed class AdminUIController : UIController, IOnStateEntered UIManager.GetActiveUIWidgetOrNull()?.AdminButton; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_window == null); _window = UIManager.CreateWindow(); - _adminButton = UIManager.GetActiveUIWidget().AdminButton; LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.Center); _window.PlayerTabControl.OnEntryPressed += PlayerTabEntryPressed; @@ -45,7 +44,6 @@ public sealed class AdminUIController : UIController, IOnStateEntered Toggle())); @@ -53,16 +51,36 @@ public sealed class AdminUIController : UIController, IOnStateEntered(); } private void AdminStatusUpdated() { - _adminButton!.Visible = _conGroups.CanAdminMenu(); + AdminButton!.Visible = _conGroups.CanAdminMenu(); } private void AdminButtonPressed(ButtonEventArgs args) diff --git a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs index f61fb64c3b..1026f8322a 100644 --- a/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs +++ b/Content.Client/UserInterface/Systems/Alerts/AlertsUIController.cs @@ -49,6 +49,11 @@ public sealed class AlertsUIController : UIController, IOnStateEntered UIManager.GetActiveUIWidgetOrNull()?.AHelpButton; private IAHelpUIHandler? _uiHelper; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_uiHelper == null); - _ahelpButton = UIManager.GetActiveUIWidget().AHelpButton; - _ahelpButton.OnPressed += AHelpButtonPressed; _adminManager.AdminStatusUpdated += OnAdminStatusUpdated; CommandBinds.Builder @@ -45,6 +43,26 @@ public sealed class AHelpUIController: UIController, IOnStateChanged(); } + public void UnloadButton() + { + if (AhelpButton == null) + { + return; + } + + AhelpButton.OnPressed -= AHelpButtonPressed; + } + + public void LoadButton() + { + if (AhelpButton == null) + { + return; + } + + AhelpButton.OnPressed += AHelpButtonPressed; + } + private void OnAdminStatusUpdated() { if (_uiHelper is not { IsOpen: true }) @@ -60,9 +78,7 @@ public sealed class AHelpUIController: UIController, IOnStateChanged UIManager.GetActiveUIWidgetOrNull()?.CharacterButton; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_window == null); - _characterButton = UIManager.GetActiveUIWidget().CharacterButton; - _characterButton.OnPressed += CharacterButtonPressed; _window = UIManager.CreateWindow(); LayoutContainer.SetAnchorPreset(_window, LayoutContainer.LayoutPreset.CenterTop); - _window.OnClose += () => { _characterButton.Pressed = false; }; - _window.OnOpen += () => { _characterButton.Pressed = true; }; + CommandBinds.Builder .Bind(ContentKeyFunctions.OpenCharacterMenu, @@ -51,13 +48,6 @@ public sealed class CharacterUIController : UIController, IOnStateEntered(); } @@ -73,6 +63,37 @@ public sealed class CharacterUIController : UIController, IOnStateEntered CharacterButton!.Pressed = false; + private void ActivateButton() => CharacterButton!.Pressed = true; + private void CharacterUpdated(CharacterData data) { if (_window == null) @@ -141,6 +162,12 @@ public sealed class CharacterUIController : UIController, IOnStateEntered CycleChatChannel(false))); } + public void SetMainChat(bool setting) + { + // This isn't very nice to look at. + var widget = UIManager.ActiveScreen?.GetWidget(); + if (widget == null) + { + widget = UIManager.ActiveScreen?.GetWidget(); + if (widget == null) + { + return; + } + } + + widget.Main = setting; + } + private void FocusChat() { foreach (var chat in _chats) @@ -230,13 +246,13 @@ public sealed class ChatUIController : UIController } UpdateChannelPermissions(); + } - if (_speechBubbleRoot.Parent == UIManager.StateRoot) - return; - + public void SetSpeechBubbleRoot(LayoutContainer root) + { _speechBubbleRoot.Orphan(); + root.AddChild(_speechBubbleRoot); LayoutContainer.SetAnchorPreset(_speechBubbleRoot, LayoutContainer.LayoutPreset.Wide); - UIManager.StateRoot.AddChild(_speechBubbleRoot); _speechBubbleRoot.SetPositionLast(); } @@ -701,6 +717,11 @@ public sealed class ChatUIController : UIController return MapLocalIfGhost(PreferredChannel); } + public void NotifyChatTextChange() + { + _typingIndicator?.ClientChangedChatText(); + } + private readonly record struct SpeechBubbleData(string Message, SpeechBubble.SpeechType Type); private sealed class SpeechBubbleQueueData diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml index 01007b3355..781c4a2d0c 100644 --- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml +++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml @@ -4,15 +4,17 @@ xmlns:widgets="clr-namespace:Content.Client.UserInterface.Systems.Chat.Widgets" xmlns:controls="clr-namespace:Content.Client.UserInterface.Systems.Chat.Controls" MouseFilter="Stop" + HorizontalExpand="True" + VerticalExpand="True" MinSize="465 225"> - + - - - + + + diff --git a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs index 9fdc214e91..cc39b0da39 100644 --- a/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs +++ b/Content.Client/UserInterface/Systems/Chat/Widgets/ChatBox.xaml.cs @@ -5,6 +5,7 @@ using Content.Shared.Chat; using Content.Shared.Input; using Robust.Client.AutoGenerated; using Robust.Client.UserInterface; +using Robust.Client.UserInterface.Controls; using Robust.Client.UserInterface.XAML; using Robust.Shared.Input; using Robust.Shared.Utility; @@ -14,7 +15,7 @@ namespace Content.Client.UserInterface.Systems.Chat.Widgets; [GenerateTypedNameReferences] #pragma warning disable RA0003 -public partial class ChatBox : Control +public partial class ChatBox : UIWidget #pragma warning restore RA0003 { private readonly ChatUIController _controller; @@ -197,7 +198,7 @@ public partial class ChatBox : Control UpdateSelectedChannel(); // Warn typing indicator about change - EntitySystem.Get().ClientChangedChatText(); + _controller.NotifyChatTextChange(); } protected override void Dispose(bool disposing) diff --git a/Content.Client/UserInterface/Systems/Crafting/CraftingUIController.cs b/Content.Client/UserInterface/Systems/Crafting/CraftingUIController.cs index 99ca6ce693..e38739c3a7 100644 --- a/Content.Client/UserInterface/Systems/Crafting/CraftingUIController.cs +++ b/Content.Client/UserInterface/Systems/Crafting/CraftingUIController.cs @@ -11,24 +11,50 @@ namespace Content.Client.UserInterface.Systems.Crafting; public sealed class CraftingUIController : UIController, IOnStateChanged { private ConstructionMenuPresenter? _presenter; - private MenuButton? _craftingButton; + private MenuButton? CraftingButton => UIManager.GetActiveUIWidgetOrNull()?.CraftingButton; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_presenter == null); _presenter = new ConstructionMenuPresenter(); - _craftingButton = UIManager.GetActiveUIWidget().CraftingButton; - _craftingButton.OnToggled += _presenter.OnHudCraftingButtonToggled; } public void OnStateExited(GameplayState state) { if (_presenter == null) return; - _craftingButton!.Pressed = false; - _craftingButton!.OnToggled -= _presenter.OnHudCraftingButtonToggled; - _craftingButton = null; + UnloadButton(_presenter); _presenter.Dispose(); _presenter = null; } + + internal void UnloadButton(ConstructionMenuPresenter? presenter = null) + { + if (CraftingButton == null) + { + return; + } + + if (presenter == null) + { + presenter ??= _presenter; + if (presenter == null) + { + return; + } + } + + CraftingButton.Pressed = false; + CraftingButton.OnToggled -= presenter.OnHudCraftingButtonToggled; + } + + public void LoadButton() + { + if (CraftingButton == null || _presenter == null) + { + return; + } + + CraftingButton.OnToggled += _presenter.OnHudCraftingButtonToggled; + } } diff --git a/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs b/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs index 2ddcc7b1c5..5f4232dc32 100644 --- a/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs +++ b/Content.Client/UserInterface/Systems/EscapeMenu/EscapeUIController.cs @@ -26,17 +26,53 @@ public sealed class EscapeUIController : UIController, IOnStateEntered UIManager.GetActiveUIWidgetOrNull()?.EscapeButton; + + public void UnloadButton() + { + if (EscapeButton == null) + { + return; + } + + EscapeButton.Pressed = false; + EscapeButton.OnPressed += EscapeButtonOnOnPressed; + + if (_escapeWindow == null) + { + return; + } + + _escapeWindow.OnClose -= DeactivateButton; + _escapeWindow.OnOpen -= ActivateButton; + } + + public void LoadButton() + { + if (EscapeButton == null) + { + return; + } + + EscapeButton.OnPressed += EscapeButtonOnOnPressed; + + if (_escapeWindow == null) + { + return; + } + + _escapeWindow.OnClose += DeactivateButton; + _escapeWindow.OnOpen += ActivateButton; + } + + private void ActivateButton() => EscapeButton!.Pressed = true; + private void DeactivateButton() => EscapeButton!.Pressed = false; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_escapeWindow == null); - _escapeButton = UIManager.GetActiveUIWidget().EscapeButton; - _escapeButton.OnPressed += EscapeButtonOnOnPressed; _escapeWindow = UIManager.CreateWindow(); - _escapeWindow.OnClose += () => { _escapeButton.Pressed = false; }; - _escapeWindow.OnOpen += () => { _escapeButton.Pressed = true; }; _escapeWindow.ChangelogButton.OnPressed += _ => { @@ -87,13 +123,6 @@ public sealed class EscapeUIController : UIController, IOnStateEntered(); } @@ -119,7 +148,7 @@ public sealed class EscapeUIController : UIController, IOnStateEntered public int ColumnLimit { get => _grid.Columns; set => _grid.Columns = value; } public int MaxButtonCount { get; set; } = 0; + /// + /// Indexer. This is used to reference a HandsContainer from the + /// controller. + /// + public string? Indexer { get; set; } + public HandsContainer() { AddChild(_grid = new GridContainer()); diff --git a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs index 736622eee8..e49688d2d2 100644 --- a/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs +++ b/Content.Client/UserInterface/Systems/Hands/HandsUIController.cs @@ -238,11 +238,59 @@ public sealed class HandsUIController : UIController, IOnStateEntered + /// Reload all hands. + /// + public void ReloadHands() + { + UnloadPlayerHands(); + _handsSystem.ReloadHandButtons(); + } + + /// + /// Swap hands from one container to the other. + /// + /// + /// + public void SwapHands(HandsContainer other, HandsContainer? source = null) + { + if (HandsGui == null && source == null) + { + throw new ArgumentException("Cannot swap hands if no source hand container exists!"); + } + + source ??= HandsGui!.HandContainer; + + var transfer = new List(); + foreach (var child in source.Children) + { + if (child is not HandButton) + { + continue; + } + + transfer.Add(child); + } + + foreach (var control in transfer) + { + source.RemoveChild(control); + other.AddChild(control); + } + } + private void RemoveHand(string handName) { RemoveHand(handName, out _); @@ -266,15 +314,15 @@ public sealed class HandsUIController : UIController, IOnStateEntered(); + + if (hotbar == null) + { + return; + } + + foreach (var container in GetAllItemSlotContainers(hotbar)) + { + // Yes, this is dirty. + container.SlotGroup = container.SlotGroup; + } + + _hands?.ReloadHands(); + _inventory?.ReloadSlots(); + _inventory?.RegisterInventoryBarContainer(hotbar.InventoryHotbar); + } + + private IEnumerable GetAllItemSlotContainers(Control gui) + { + var result = new List(); + + foreach (var child in gui.Children) + { + if (child is ItemSlotButtonContainer container) + { + result.Add(container); + } + + result.AddRange(GetAllItemSlotContainers(child)); + } + + return result; + } } diff --git a/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml b/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml index 3e6722b889..cd34412da6 100644 --- a/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml +++ b/Content.Client/UserInterface/Systems/Hotbar/Widgets/HotbarGui.xaml @@ -15,6 +15,7 @@ /> UIManager.ActiveScreen?.GetWidget()?.InventoryButton; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_strippingWindow == null); _strippingWindow = UIManager.CreateWindow(); - _inventoryButton = UIManager.GetActiveUIWidget().InventoryButton; LayoutContainer.SetAnchorPreset(_strippingWindow, LayoutContainer.LayoutPreset.Center); //bind open inventory key to OpenInventoryMenu; CommandBinds.Builder .Bind(ContentKeyFunctions.OpenInventoryMenu, InputCmdHandler.FromDelegate(_ => ToggleInventoryBar())) .Register(); - _inventoryButton.OnPressed += InventoryButtonPressed; } public void OnStateExited(GameplayState state) @@ -60,14 +58,27 @@ public sealed class InventoryUIController : UIController, IOnStateEntered(); + } + + public void UnloadButton() + { + if (InventoryButton == null) { - _inventoryButton.OnPressed -= InventoryButtonPressed; - _inventoryButton.Pressed = false; - _inventoryButton = null; + return; } - CommandBinds.Unregister(); + InventoryButton.OnPressed -= InventoryButtonPressed; + } + + public void LoadButton() + { + if (InventoryButton == null) + { + return; + } + + InventoryButton.OnPressed += InventoryButtonPressed; } private SlotButton CreateSlotButton(SlotData data) @@ -166,14 +177,14 @@ public sealed class InventoryUIController : UIController, IOnStateEntered UIManager.GetActiveUIWidgetOrNull(); + + public void UnloadButtons() + { + _escape.UnloadButton(); + _inventory.UnloadButton(); + _admin.UnloadButton(); + _character.UnloadButton(); + _crafting.UnloadButton(); + _ahelp.UnloadButton(); + _action.UnloadButton(); + _sandbox.UnloadButton(); + } + + public void LoadButtons() + { + _escape.LoadButton(); + _inventory.LoadButton(); + _admin.LoadButton(); + _character.LoadButton(); + _crafting.LoadButton(); + _ahelp.LoadButton(); + _action.LoadButton(); + _sandbox.LoadButton(); + } +} diff --git a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml index 0fd861a6fa..81aae512d1 100644 --- a/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml +++ b/Content.Client/UserInterface/Systems/MenuBar/Widgets/GameTopMenuBar.xaml @@ -20,6 +20,7 @@ BoundKey = "{x:Static ic:EngineKeyFunctions.EscapeMenu}" ToolTip="{Loc 'game-hud-open-escape-menu-button-tooltip'}" MinSize="70 64" + HorizontalExpand="True" AppendStyleClass="{x:Static style:StyleBase.ButtonOpenRight}" /> diff --git a/Content.Client/UserInterface/Systems/Sandbox/SandboxUIController.cs b/Content.Client/UserInterface/Systems/Sandbox/SandboxUIController.cs index baa6457307..d7ec9f3497 100644 --- a/Content.Client/UserInterface/Systems/Sandbox/SandboxUIController.cs +++ b/Content.Client/UserInterface/Systems/Sandbox/SandboxUIController.cs @@ -43,13 +43,11 @@ public sealed class SandboxUIController : UIController, IOnStateChanged UIManager.GetUIController(); private DecalPlacerUIController DecalPlacerController => UIManager.GetUIController(); - private MenuButton? _sandboxButton; + private MenuButton? SandboxButton => UIManager.GetActiveUIWidgetOrNull()?.SandboxButton; public void OnStateEntered(GameplayState state) { DebugTools.Assert(_window == null); - _sandboxButton = UIManager.GetActiveUIWidget().SandboxButton; - _sandboxButton.OnPressed += SandboxButtonPressed; EnsureWindow(); CheckSandboxVisibility(); @@ -68,13 +66,33 @@ public sealed class SandboxUIController : UIController, IOnStateChanged(); } + public void UnloadButton() + { + if (SandboxButton == null) + { + return; + } + + SandboxButton.OnPressed -= SandboxButtonPressed; + } + + public void LoadButton() + { + if (SandboxButton == null) + { + return; + } + + SandboxButton.OnPressed += SandboxButtonPressed; + } + private void EnsureWindow() { if(_window is { Disposed: false }) return; _window = UIManager.CreateWindow(); - _window.OnOpen += () => { _sandboxButton!.Pressed = true; }; - _window.OnClose += () => { _sandboxButton!.Pressed = false; }; + _window.OnOpen += () => { SandboxButton!.Pressed = true; }; + _window.OnClose += () => { SandboxButton!.Pressed = false; }; _window.ToggleLightButton.Pressed = !_light.Enabled; _window.ToggleFovButton.Pressed = !_eye.CurrentEye.DrawFov; _window.ToggleShadowsButton.Pressed = !_light.DrawShadows; @@ -100,10 +118,10 @@ public sealed class SandboxUIController : UIController, IOnStateChanged(); } diff --git a/Content.Client/UserInterface/Systems/Viewport/ViewportUIController.cs b/Content.Client/UserInterface/Systems/Viewport/ViewportUIController.cs new file mode 100644 index 0000000000..f0773b9a0d --- /dev/null +++ b/Content.Client/UserInterface/Systems/Viewport/ViewportUIController.cs @@ -0,0 +1,89 @@ +using Content.Client.UserInterface.Controls; +using Content.Shared.CCVar; +using Robust.Client.GameObjects; +using Robust.Client.Graphics; +using Robust.Client.Player; +using Robust.Client.UserInterface.Controllers; +using Robust.Shared.Configuration; +using Robust.Shared.Timing; + +namespace Content.Client.UserInterface.Systems.Viewport; + +public sealed class ViewportUIController : UIController +{ + [Dependency] private readonly IEyeManager _eyeManager = default!; + [Dependency] private readonly IPlayerManager _playerMan = default!; + [Dependency] private readonly IEntityManager _entMan = default!; + [Dependency] private readonly IConfigurationManager _configurationManager = default!; + + public static readonly Vector2i ViewportSize = (EyeManager.PixelsPerMeter * 21, EyeManager.PixelsPerMeter * 15); + public const int ViewportHeight = 15; + private MainViewport? Viewport => UIManager.ActiveScreen?.GetWidget(); + + public override void Initialize() + { + _configurationManager.OnValueChanged(CCVars.ViewportMinimumWidth, _ => UpdateViewportRatio()); + _configurationManager.OnValueChanged(CCVars.ViewportMaximumWidth, _ => UpdateViewportRatio()); + _configurationManager.OnValueChanged(CCVars.ViewportWidth, _ => UpdateViewportRatio()); + } + + private void UpdateViewportRatio() + { + if (Viewport == null) + { + return; + } + + var min = _configurationManager.GetCVar(CCVars.ViewportMinimumWidth); + var max = _configurationManager.GetCVar(CCVars.ViewportMaximumWidth); + var width = _configurationManager.GetCVar(CCVars.ViewportWidth); + + if (width < min || width > max) + { + width = CCVars.ViewportWidth.DefaultValue; + } + + Viewport.Viewport.ViewportSize = (EyeManager.PixelsPerMeter * width, EyeManager.PixelsPerMeter * ViewportHeight); + } + + public void ReloadViewport() + { + if (Viewport == null) + { + return; + } + + UpdateViewportRatio(); + Viewport.Viewport.HorizontalExpand = true; + Viewport.Viewport.VerticalExpand = true; + _eyeManager.MainViewport = Viewport.Viewport; + } + + public override void FrameUpdate(FrameEventArgs e) + { + if (Viewport == null) + { + return; + } + + base.FrameUpdate(e); + + Viewport.Viewport.Eye = _eyeManager.CurrentEye; + + // verify that the current eye is not "null". Fuck IEyeManager. + + var ent = _playerMan.LocalPlayer?.ControlledEntity; + if (_eyeManager.CurrentEye.Position != default || ent == null) + return; + + _entMan.TryGetComponent(ent, out EyeComponent? eye); + + if (eye?.Eye == _eyeManager.CurrentEye + && _entMan.GetComponent(ent.Value).WorldPosition == default) + return; // nothing to worry about, the player is just in null space... actually that is probably a problem? + + // Currently, this shouldn't happen. This likely happened because the main eye was set to null. When this + // does happen it can create hard to troubleshoot bugs, so lets print some helpful warnings: + Logger.Warning($"Main viewport's eye is in nullspace (main eye is null?). Attached entity: {_entMan.ToPrettyString(ent.Value)}. Entity has eye comp: {eye != null}"); + } +} diff --git a/Content.Shared/CCVar/CCVars.cs b/Content.Shared/CCVar/CCVars.cs index 1fd9bd43cc..4bbaf779da 100644 --- a/Content.Shared/CCVar/CCVars.cs +++ b/Content.Shared/CCVar/CCVars.cs @@ -1073,6 +1073,24 @@ namespace Content.Shared.CCVar public static readonly CVarDef ViewportScaleRender = CVarDef.Create("viewport.scale_render", true, CVar.CLIENTONLY | CVar.ARCHIVE); + public static readonly CVarDef ViewportMinimumWidth = + CVarDef.Create("viewport.minimum_width", 15, CVar.REPLICATED); + + public static readonly CVarDef ViewportMaximumWidth = + CVarDef.Create("viewport.maximum_width", 21, CVar.REPLICATED); + + public static readonly CVarDef ViewportWidth = + CVarDef.Create("viewport.width", 21, CVar.CLIENTONLY | CVar.ARCHIVE); + + /* + * UI + */ + + public static readonly CVarDef UILayout = + CVarDef.Create("ui.layout", "Default", CVar.CLIENTONLY | CVar.ARCHIVE); + + + /* * CHAT */ diff --git a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl index fdf85e1b88..03f53952c1 100644 --- a/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl +++ b/Resources/Locale/en-US/escape-menu/ui/options-menu.ftl @@ -57,6 +57,8 @@ ui-options-vp-integer-scaling-tooltip = If this option is enabled, the viewport ui-options-vp-low-res = Low-resolution viewport ui-options-parallax-low-quality = Low-quality Parallax (background) ui-options-fps-counter = Show FPS counter +ui-options-vp-width = Viewport width: { $width } +ui-options-hud-layout = HUD layout: ## Controls menu