From c6275a0cb4b69f9a2747ffb976780c3a7019469c Mon Sep 17 00:00:00 2001 From: Robbie Lodico Date: Sun, 2 Feb 2025 15:00:34 +0100 Subject: [PATCH 01/30] wip: rewrite SettingsWindow to be an actual window instead of an ImagePanel fix compile error --- .../Interface/Shared/SettingsWindow.cs | 110 ++++++++++++------ 1 file changed, 77 insertions(+), 33 deletions(-) diff --git a/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs b/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs index 6b22d3e811..2a1c0bb4d8 100644 --- a/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs +++ b/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs @@ -11,13 +11,17 @@ using Intersect.Client.Interface.Menu; using Intersect.Client.Localization; using Intersect.Config; +using Intersect.Core; using Intersect.Utilities; +using Microsoft.Extensions.Logging; using static Intersect.Client.Framework.File_Management.GameContentManager; using MathHelper = Intersect.Client.Utilities.MathHelper; +using static Intersect.Client.Framework.File_Management.GameContentManager; + namespace Intersect.Client.Interface.Shared; -public partial class SettingsWindow : ImagePanel +public partial class SettingsWindow : WindowControl { // Parent Window. private readonly MainMenu? _mainMenu; @@ -25,8 +29,8 @@ public partial class SettingsWindow : ImagePanel // Settings Window. private readonly Label _settingsHeader; - private readonly Button _settingsApplyBtn; - private readonly Button _settingsCancelBtn; + private readonly Button _applyPendingChangesButton; + private readonly Button _cancelPendingChangesButton; // Settings Containers. private readonly ScrollControl _gameSettingsContainer; @@ -91,43 +95,74 @@ public partial class SettingsWindow : ImagePanel private Control _keybindingEditControl; private Controls? _keybindingEditControls; private Button? _keybindingEditBtn; - private readonly Button _keybindingRestoreBtn; + private readonly Button _restoreDefaultKeybindingsButton; private long _keybindingListeningTimer; private int _keyEdit = -1; private readonly Dictionary mKeybindingBtns = []; + private ImagePanel _bottomBar; + private TabControl _tabs; + // Open Settings. private bool _returnToMenu; // Initialize. - public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : base(parent, nameof(SettingsWindow)) + public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : base(parent, Strings.Settings.Title, modal: false, nameof(SettingsWindow)) { - // Assign References. _mainMenu = mainMenu; _escapeMenu = escapeMenu; - // Main Menu Window. Interface.InputBlockingElements.Add(this); - // Menu Header. - _settingsHeader = new Label(this, "SettingsHeader") + IconName = "SettingsWindow.icon.png"; + + MinimumSize = new Point(600, 400); + DisableResizing(); + IsClosable = false; + + Titlebar.MouseInputEnabled = false; + + TitleLabel.FontSize = 14; + TitleLabel.TextColorOverride = Color.White; + + var fontNormal = Current?.GetFont(TitleLabel.FontName, 12); + + _bottomBar = new ImagePanel(this, "BottomBar") { - Text = Strings.Settings.Title + Dock = Pos.Bottom, + MinimumSize = new Point(0, 40), + Padding = new Padding(8, 4), }; - // Apply Button. - _settingsApplyBtn = new Button(this, "SettingsApplyBtn") + _tabs = new TabControl(this) { - Text = Strings.Settings.Apply + Dock = Pos.Fill, + Margin = Margin.Four, + }; + + // Apply Button. + _applyPendingChangesButton = new Button(_bottomBar, nameof(_applyPendingChangesButton)) + { + Alignment = [Alignments.Center], + AutoSizeToContents = true, + Font = fontNormal, + MinimumSize = new Point(96, 24), + TextPadding = new Padding(16, 2), + Text = Strings.Settings.Apply, }; - _settingsApplyBtn.Clicked += SettingsApplyBtn_Clicked; + _applyPendingChangesButton.Clicked += SettingsApplyBtn_Clicked; // Cancel Button. - _settingsCancelBtn = new Button(this, "SettingsCancelBtn") - { - Text = Strings.Settings.Cancel + _cancelPendingChangesButton = new Button(_bottomBar, nameof(_cancelPendingChangesButton)) + { + Alignment = [Alignments.Right, Alignments.CenterV], + AutoSizeToContents = true, + Font = fontNormal, + MinimumSize = new Point(96, 24), + TextPadding = new Padding(16, 2), + Text = Strings.Settings.Cancel, }; - _settingsCancelBtn.Clicked += SettingsCancelBtn_Clicked; + _cancelPendingChangesButton.Clicked += CancelPendingChangesButton_Clicked; #region InitGameSettings @@ -430,11 +465,16 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : _keybindingSettingsContainer.EnableScroll(false, true); // Keybinding Settings - Restore Default Keys Button. - _keybindingRestoreBtn = new Button(this, "KeybindingsRestoreBtn") - { - Text = Strings.Settings.Restore + _restoreDefaultKeybindingsButton = new Button(_bottomBar, nameof(_restoreDefaultKeybindingsButton)) + { + Alignment = [Alignments.Left, Alignments.CenterV], + AutoSizeToContents = true, + Font = fontNormal, + MinimumSize = new Point(96, 24), + TextPadding = new Padding(16, 2), + Text = Strings.Settings.Restore, }; - _keybindingRestoreBtn.Clicked += KeybindingsRestoreBtn_Clicked; + _restoreDefaultKeybindingsButton.Clicked += RestoreDefaultKeybindingsButton_Clicked; // Keybinding Settings - Controls var row = 0; @@ -450,6 +490,11 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : #endregion + _tabs.AddPage(Strings.Settings.GameSettingsTab, _gameSettingsContainer); + _tabs.AddPage(Strings.Settings.VideoSettingsTab, _videoSettingsContainer); + _tabs.AddPage(Strings.Settings.AudioSettingsTab, _audioSettingsContainer); + _tabs.AddPage(Strings.Settings.KeyBindingSettingsTab, _keybindingSettingsContainer); + LoadJsonUi(UI.Shared, Graphics.Renderer?.GetResolutionString()); IsHidden = true; } @@ -587,7 +632,7 @@ private void GameSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) _keybindingSettingsContainer.Hide(); // Restore Default KeybindingSettings Button. - _keybindingRestoreBtn.Hide(); + _restoreDefaultKeybindingsButton.Hide(); } } @@ -633,7 +678,7 @@ private void VideoSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) _keybindingSettingsContainer.Hide(); // Restore Default KeybindingSettings Button. - _keybindingRestoreBtn.Hide(); + _restoreDefaultKeybindingsButton.Hide(); } } @@ -655,7 +700,7 @@ private void AudioSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) _keybindingSettingsContainer.Hide(); // Restore Default KeybindingSettings Button. - _keybindingRestoreBtn.Hide(); + _restoreDefaultKeybindingsButton.Hide(); } } @@ -677,7 +722,7 @@ private void KeybindingSettingsTab_Clicked(Base sender, ClickedEventArgs argumen _keybindingSettingsContainer.Show(); // Restore Default KeybindingSettings Button. - _keybindingRestoreBtn.Show(); + _restoreDefaultKeybindingsButton.Show(); bool controlsAdded = false; @@ -706,8 +751,7 @@ private void KeybindingSettingsTab_Clicked(Base sender, ClickedEventArgs argumen private void LoadSettingsWindow() { - // Settings Window Title. - _settingsHeader.SetText(Strings.Settings.Title); + Title = Strings.Settings.Title; // Containers. _gameSettingsContainer.Show(); @@ -728,9 +772,9 @@ private void LoadSettingsWindow() _keybindingSettingsTab.Enable(); // Buttons. - _settingsApplyBtn.Show(); - _settingsCancelBtn.Show(); - _keybindingRestoreBtn.Hide(); + _applyPendingChangesButton.Show(); + _cancelPendingChangesButton.Show(); + _restoreDefaultKeybindingsButton.Hide(); UpdateWorldScaleControls(); } @@ -975,7 +1019,7 @@ private void EditKeyPressed(Button sender) } } - private void KeybindingsRestoreBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void RestoreDefaultKeybindingsButton_Clicked(Base sender, ClickedEventArgs arguments) { if (_keybindingEditControls is not {} controls) { @@ -1099,7 +1143,7 @@ private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) Hide(); } - private void SettingsCancelBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void CancelPendingChangesButton_Clicked(Base sender, ClickedEventArgs arguments) { // Update previously saved values in order to discard changes. Globals.Database.MusicVolume = _previousMusicVolume; From 893e761295ea000f91fd9a0da504ed3743d2103b Mon Sep 17 00:00:00 2001 From: Robbie Lodico Date: Tue, 4 Feb 2025 23:43:11 +0100 Subject: [PATCH 02/30] more progress on settings menu, panel component, fixes for tables, sliders, scrollbars --- .../Interface/Debugging/DebugWindow.cs | 4 +- .../Interface/Menu/MainMenu.cs | 2 +- .../Interface/Shared/SettingsWindow.cs | 593 ++++++++---------- Intersect.Client.Core/Localization/Strings.cs | 9 + .../Gwen/Control/Base.cs | 283 ++++++--- .../Gwen/Control/DockedTabControl.cs | 4 +- .../EventArguments/ValueChangedEventArgs.cs | 8 + .../Gwen/Control/HorizontalScrollBar.cs | 2 +- .../Gwen/Control/HorizontalSlider.cs | 19 +- .../Gwen/Control/Label.cs | 6 + .../Gwen/Control/LabeledHorizontalSlider.cs | 1 - .../Gwen/Control/LabeledSlider.cs | 322 ++++++++++ .../Gwen/Control/Layout/Table.cs | 379 +++++++---- .../Gwen/Control/Layout/TableRow.cs | 196 ++++-- .../Gwen/Control/ListBox.cs | 4 +- .../Gwen/Control/NumericUpDown.cs | 8 +- .../Gwen/Control/Panel.cs | 16 + .../Gwen/Control/ResizableControl.cs | 26 +- .../Gwen/Control/ScrollBar.cs | 11 +- .../Gwen/Control/ScrollControl.cs | 2 +- .../Gwen/Control/Slider.cs | 423 +++++++++---- .../Gwen/Control/TabButton.cs | 2 + .../Gwen/Control/TabChangeEventArgs.cs | 8 + .../Gwen/Control/TabControl.cs | 39 +- .../Gwen/Control/TextBoxNumeric.cs | 37 +- .../Gwen/Control/VerticalScrollBar.cs | 11 +- .../Gwen/Control/VerticalSlider.cs | 16 +- .../Gwen/Control/WindowControl.cs | 6 - .../Gwen/ControlInternal/Dragger.cs | 48 +- .../Gwen/ControlInternal/SliderBar.cs | 3 +- .../Gwen/ControlInternal/Text.cs | 3 + Intersect.Client.Framework/Gwen/Margin.cs | 5 + .../Gwen/Orientation.cs | 15 + Intersect.Client.Framework/Gwen/Padding.cs | 5 + .../Gwen/Renderer/IntersectRenderer.cs | 9 +- Intersect.Client.Framework/Gwen/Skin/Base.cs | 55 +- .../Gwen/Skin/Intersect2021.cs | 1 + .../Gwen/Skin/IntersectSkin.cs | 1 + .../Gwen/Skin/TexturedBase.cs | 206 +++++- .../Gwen/Skin/skin-intersect.png | Bin 39800 -> 40030 bytes 40 files changed, 1951 insertions(+), 837 deletions(-) create mode 100644 Intersect.Client.Framework/Gwen/Control/EventArguments/ValueChangedEventArgs.cs create mode 100644 Intersect.Client.Framework/Gwen/Control/LabeledSlider.cs create mode 100644 Intersect.Client.Framework/Gwen/Control/Panel.cs create mode 100644 Intersect.Client.Framework/Gwen/Control/TabChangeEventArgs.cs create mode 100644 Intersect.Client.Framework/Gwen/Orientation.cs diff --git a/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs b/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs index 6a0e82d3df..1b7fe05dd3 100644 --- a/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs +++ b/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs @@ -276,7 +276,9 @@ private Table CreateInfoTableDebugStats(Base parent) { var table = new Table(parent, nameof(TableDebugStats)) { + CellSpacing = new Point(8, 2), ColumnCount = 2, + ColumnWidths = [150, null], Dock = Pos.Fill, Font = _defaultFont, }; @@ -441,7 +443,7 @@ private async Task WaitForOwnerVisible(CancellationToken cancellationToken) private static Base CreateValue(Task _) => Interface.FindControlAtCursor(); private void HandleValue(Base component) - { + { DataChanged?.Invoke( this, new TableDataChangedEventArgs( diff --git a/Intersect.Client.Core/Interface/Menu/MainMenu.cs b/Intersect.Client.Core/Interface/Menu/MainMenu.cs index 6bf6db6cf6..143d86f249 100644 --- a/Intersect.Client.Core/Interface/Menu/MainMenu.cs +++ b/Intersect.Client.Core/Interface/Menu/MainMenu.cs @@ -52,7 +52,7 @@ public MainMenu(Canvas menuCanvas) : base(menuCanvas) _createCharacterWindow = new CreateCharacterWindow(_menuCanvas, this, SelectCharacterWindow); _settingsWindow = new SettingsWindow(_menuCanvas, this, null) { - AlignmentDistance = new Padding(0, 140, 0, 0), + AlignmentDistance = new Padding(0, 100, 0, 0), }; _creditsWindow = new CreditsWindow(_menuCanvas, this); } diff --git a/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs b/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs index 2a1c0bb4d8..1841bcca98 100644 --- a/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs +++ b/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs @@ -1,10 +1,13 @@ using Intersect.Client.Core; using Intersect.Client.Core.Controls; +using Intersect.Client.Framework.Content; using Intersect.Client.Framework.GenericClasses; using Intersect.Client.Framework.Graphics; using Intersect.Client.Framework.Gwen; using Intersect.Client.Framework.Gwen.Control; using Intersect.Client.Framework.Gwen.Control.EventArguments; +using Intersect.Client.Framework.Gwen.Control.Layout; +using Intersect.Client.Framework.Gwen.ControlInternal; using Intersect.Client.Framework.Input; using Intersect.Client.General; using Intersect.Client.Interface.Game; @@ -23,6 +26,8 @@ namespace Intersect.Client.Interface.Shared; public partial class SettingsWindow : WindowControl { + private readonly GameFont? _defaultFont; + // Parent Window. private readonly MainMenu? _mainMenu; private readonly EscapeMenu? _escapeMenu; @@ -36,13 +41,16 @@ public partial class SettingsWindow : WindowControl private readonly ScrollControl _gameSettingsContainer; private readonly ScrollControl _videoSettingsContainer; private readonly ScrollControl _audioSettingsContainer; + private readonly Table _audioTable; + private readonly ScrollControl _keybindingSettingsContainer; + private readonly Table _controlsTable; // Tabs. - private readonly Button _gameSettingsTab; - private readonly Button _videoSettingsTab; - private readonly Button _audioSettingsTab; - private readonly Button _keybindingSettingsTab; + private readonly TabButton _gameSettingsTab; + private readonly TabButton _videoSettingsTab; + private readonly TabButton _audioSettingsTab; + private readonly TabButton _keybindingSettingsTab; // Game Settings - Interface. private readonly ScrollControl _interfaceSettings; @@ -84,12 +92,10 @@ public partial class SettingsWindow : WindowControl private readonly LabeledCheckBox _lightingEnabledCheckbox; // Audio Settings. - private readonly HorizontalSlider _musicSlider; + private readonly LabeledSlider _musicSlider; + private readonly LabeledSlider _soundEffectsSlider; private int _previousMusicVolume; - private readonly Label _musicLabel; - private readonly HorizontalSlider _soundSlider; private int _previousSoundVolume; - private readonly Label _soundLabel; // Keybinding Settings. private Control _keybindingEditControl; @@ -100,24 +106,24 @@ public partial class SettingsWindow : WindowControl private int _keyEdit = -1; private readonly Dictionary mKeybindingBtns = []; - private ImagePanel _bottomBar; + private Panel _bottomBar; private TabControl _tabs; // Open Settings. private bool _returnToMenu; // Initialize. - public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : base(parent, Strings.Settings.Title, modal: false, nameof(SettingsWindow)) + public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : base(parent: parent, title: Strings.Settings.Title, modal: false, name: nameof(SettingsWindow)) { _mainMenu = mainMenu; _escapeMenu = escapeMenu; - Interface.InputBlockingElements.Add(this); + Interface.InputBlockingElements.Add(item: this); IconName = "SettingsWindow.icon.png"; - MinimumSize = new Point(600, 400); - DisableResizing(); + MinimumSize = new Point(x: 640, y: 400); + IsResizable = false; IsClosable = false; Titlebar.MouseInputEnabled = false; @@ -125,201 +131,201 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : TitleLabel.FontSize = 14; TitleLabel.TextColorOverride = Color.White; - var fontNormal = Current?.GetFont(TitleLabel.FontName, 12); + var fontNormal = Current?.GetFont(name: TitleLabel.FontName, size: 12); + _defaultFont = Current?.GetFont(name: TitleLabel.FontName, 10); - _bottomBar = new ImagePanel(this, "BottomBar") + _bottomBar = new Panel(parent: this, name: "BottomBar") { Dock = Pos.Bottom, - MinimumSize = new Point(0, 40), - Padding = new Padding(8, 4), + MinimumSize = new Point(x: 0, y: 40), + Margin = Margin.Four, + Padding = new Padding(horizontal: 8, vertical: 4), }; - _tabs = new TabControl(this) + _tabs = new TabControl(parent: this) { Dock = Pos.Fill, - Margin = Margin.Four, + Margin = new Margin(left: 4, top: 4, right: 4, bottom: 0), }; // Apply Button. - _applyPendingChangesButton = new Button(_bottomBar, nameof(_applyPendingChangesButton)) + _applyPendingChangesButton = new Button(parent: _bottomBar, name: nameof(_applyPendingChangesButton)) { Alignment = [Alignments.Center], AutoSizeToContents = true, Font = fontNormal, - MinimumSize = new Point(96, 24), - TextPadding = new Padding(16, 2), + MinimumSize = new Point(x: 96, y: 24), + TextPadding = new Padding(horizontal: 16, vertical: 2), Text = Strings.Settings.Apply, }; _applyPendingChangesButton.Clicked += SettingsApplyBtn_Clicked; + _applyPendingChangesButton.SetHoverSound("octave-tap-resonant.wav"); // Cancel Button. - _cancelPendingChangesButton = new Button(_bottomBar, nameof(_cancelPendingChangesButton)) + _cancelPendingChangesButton = new Button(parent: _bottomBar, name: nameof(_cancelPendingChangesButton)) { Alignment = [Alignments.Right, Alignments.CenterV], AutoSizeToContents = true, Font = fontNormal, - MinimumSize = new Point(96, 24), - TextPadding = new Padding(16, 2), + MinimumSize = new Point(x: 96, y: 24), + TextPadding = new Padding(horizontal: 16, vertical: 2), Text = Strings.Settings.Cancel, }; _cancelPendingChangesButton.Clicked += CancelPendingChangesButton_Clicked; + _cancelPendingChangesButton.SetHoverSound("octave-tap-resonant.wav"); #region InitGameSettings - // Init GameSettings Tab. - _gameSettingsTab = new Button(this, "GameSettingsTab") + // Game Settings are stored in the GameSettings Scroll Control. + _gameSettingsContainer = new ScrollControl(parent: this, name: "GameSettingsContainer") { - Text = Strings.Settings.GameSettingsTab + Dock = Pos.Fill, }; - _gameSettingsTab.Clicked += GameSettingsTab_Clicked; - - // Game Settings are stored in the GameSettings Scroll Control. - _gameSettingsContainer = new ScrollControl(this, "GameSettingsContainer"); - _gameSettingsContainer.EnableScroll(false, false); + _gameSettingsContainer.EnableScroll(horizontal: false, vertical: false); // Game Settings subcategories are stored in the GameSettings List. - var gameSettingsList = new ListBox(_gameSettingsContainer, "GameSettingsList"); - _ = gameSettingsList.AddRow(Strings.Settings.InterfaceSettings); - _ = gameSettingsList.AddRow(Strings.Settings.InformationSettings); - _ = gameSettingsList.AddRow(Strings.Settings.TargetingSettings); - gameSettingsList.EnableScroll(false, true); + var gameSettingsList = new ListBox(parent: _gameSettingsContainer, name: "GameSettingsList"); + _ = gameSettingsList.AddRow(label: Strings.Settings.InterfaceSettings); + _ = gameSettingsList.AddRow(label: Strings.Settings.InformationSettings); + _ = gameSettingsList.AddRow(label: Strings.Settings.TargetingSettings); + gameSettingsList.EnableScroll(horizontal: false, vertical: true); gameSettingsList.SelectedRowIndex = 0; - gameSettingsList[0].Clicked += InterfaceSettings_Clicked; - gameSettingsList[1].Clicked += InformationSettings_Clicked; - gameSettingsList[2].Clicked += TargetingSettings_Clicked; + gameSettingsList[index: 0].Clicked += InterfaceSettings_Clicked; + gameSettingsList[index: 1].Clicked += InformationSettings_Clicked; + gameSettingsList[index: 2].Clicked += TargetingSettings_Clicked; // Game Settings - Interface. - _interfaceSettings = new ScrollControl(_gameSettingsContainer, "InterfaceSettings"); - _interfaceSettings.EnableScroll(false, true); + _interfaceSettings = new ScrollControl(parent: _gameSettingsContainer, name: "InterfaceSettings"); + _interfaceSettings.EnableScroll(horizontal: false, vertical: true); // Game Settings - Interface: Auto-close Windows. - _autoCloseWindowsCheckbox = new LabeledCheckBox(_interfaceSettings, "AutoCloseWindowsCheckbox") + _autoCloseWindowsCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: "AutoCloseWindowsCheckbox") { Text = Strings.Settings.AutoCloseWindows }; // Game Settings - Interface: Auto-toggle chat log visibility. - _autoToggleChatLogCheckbox = new LabeledCheckBox(_interfaceSettings, "AutoToggleChatLogCheckbox") + _autoToggleChatLogCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: "AutoToggleChatLogCheckbox") { Text = Strings.Settings.AutoToggleChatLog }; // Game Settings - Interface: Show EXP/HP/MP as Percentage. - _showExperienceAsPercentageCheckbox = new LabeledCheckBox(_interfaceSettings, "ShowExperienceAsPercentageCheckbox") + _showExperienceAsPercentageCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: "ShowExperienceAsPercentageCheckbox") { Text = Strings.Settings.ShowExperienceAsPercentage }; - _showHealthAsPercentageCheckbox = new LabeledCheckBox(_interfaceSettings, "ShowHealthAsPercentageCheckbox") + _showHealthAsPercentageCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: "ShowHealthAsPercentageCheckbox") { Text = Strings.Settings.ShowHealthAsPercentage }; - _showManaAsPercentageCheckbox = new LabeledCheckBox(_interfaceSettings, "ShowManaAsPercentageCheckbox") + _showManaAsPercentageCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: "ShowManaAsPercentageCheckbox") { Text = Strings.Settings.ShowManaAsPercentage }; // Game Settings - Interface: simplified escape menu. - _simplifiedEscapeMenu = new LabeledCheckBox(_interfaceSettings, "SimplifiedEscapeMenu") + _simplifiedEscapeMenu = new LabeledCheckBox(parent: _interfaceSettings, name: "SimplifiedEscapeMenu") { Text = Strings.Settings.SimplifiedEscapeMenu }; // Game Settings - Information. - _informationSettings = new ScrollControl(_gameSettingsContainer, "InformationSettings"); - _informationSettings.EnableScroll(false, true); + _informationSettings = new ScrollControl(parent: _gameSettingsContainer, name: "InformationSettings"); + _informationSettings.EnableScroll(horizontal: false, vertical: true); // Game Settings - Information: Friends. - _friendOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "FriendOverheadInfoCheckbox") + _friendOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "FriendOverheadInfoCheckbox") { Text = Strings.Settings.ShowFriendOverheadInformation }; // Game Settings - Information: Guild Members. - _guildMemberOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "GuildMemberOverheadInfoCheckbox") + _guildMemberOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "GuildMemberOverheadInfoCheckbox") { Text = Strings.Settings.ShowGuildOverheadInformation }; // Game Settings - Information: Myself. - _myOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "MyOverheadInfoCheckbox") + _myOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "MyOverheadInfoCheckbox") { Text = Strings.Settings.ShowMyOverheadInformation }; // Game Settings - Information: NPCs. - _npcOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "NpcOverheadInfoCheckbox") + _npcOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "NpcOverheadInfoCheckbox") { Text = Strings.Settings.ShowNpcOverheadInformation }; // Game Settings - Information: Party Members. - _partyMemberOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "PartyMemberOverheadInfoCheckbox") + _partyMemberOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "PartyMemberOverheadInfoCheckbox") { Text = Strings.Settings.ShowPartyOverheadInformation }; // Game Settings - Information: Players. - _playerOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "PlayerOverheadInfoCheckbox") + _playerOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "PlayerOverheadInfoCheckbox") { Text = Strings.Settings.ShowPlayerOverheadInformation }; // Game Settings - Information: friends overhead hp bar. - _friendOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "FriendOverheadHpBarCheckbox") + _friendOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "FriendOverheadHpBarCheckbox") { Text = Strings.Settings.ShowFriendOverheadHpBar }; // Game Settings - Information: guild members overhead hp bar. - _guildMemberOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "GuildMemberOverheadHpBarCheckbox") + _guildMemberOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "GuildMemberOverheadHpBarCheckbox") { Text = Strings.Settings.ShowGuildOverheadHpBar }; // Game Settings - Information: my overhead hp bar. - _myOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "MyOverheadHpBarCheckbox") + _myOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "MyOverheadHpBarCheckbox") { Text = Strings.Settings.ShowMyOverheadHpBar }; // Game Settings - Information: NPC overhead hp bar. - _mpcOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "NpcOverheadHpBarCheckbox") + _mpcOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "NpcOverheadHpBarCheckbox") { Text = Strings.Settings.ShowNpcOverheadHpBar }; // Game Settings - Information: party members overhead hp bar. - _partyMemberOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "PartyMemberOverheadHpBarCheckbox") + _partyMemberOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "PartyMemberOverheadHpBarCheckbox") { Text = Strings.Settings.ShowPartyOverheadHpBar }; // Game Settings - Information: players overhead hp bar. - _playerOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "PlayerOverheadHpBarCheckbox") + _playerOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: "PlayerOverheadHpBarCheckbox") { Text = Strings.Settings.ShowPlayerOverheadHpBar }; // Game Settings - Targeting. - _targetingSettings = new ScrollControl(_gameSettingsContainer, "TargetingSettings"); - _targetingSettings.EnableScroll(false, false); + _targetingSettings = new ScrollControl(parent: _gameSettingsContainer, name: "TargetingSettings"); + _targetingSettings.EnableScroll(horizontal: false, vertical: false); // Game Settings - Targeting: Sticky Target. - _stickyTarget = new LabeledCheckBox(_targetingSettings, "StickyTargetCheckbox") + _stickyTarget = new LabeledCheckBox(parent: _targetingSettings, name: "StickyTargetCheckbox") { Text = Strings.Settings.StickyTarget }; // Game Settings - Targeting: Auto-turn to Target. - _autoTurnToTarget = new LabeledCheckBox(_targetingSettings, "AutoTurnToTargetCheckbox") + _autoTurnToTarget = new LabeledCheckBox(parent: _targetingSettings, name: "AutoTurnToTargetCheckbox") { Text = Strings.Settings.AutoTurnToTarget, }; // Game Settings - Targeting: Auto-turn to Target. - _autoSoftRetargetOnSelfCast = new LabeledCheckBox(_targetingSettings, "AutoSoftRetargetOnSelfCast") + _autoSoftRetargetOnSelfCast = new LabeledCheckBox(parent: _targetingSettings, name: "AutoSoftRetargetOnSelfCast") { Text = Strings.Settings.AutoSoftRetargetOnSelfCast, TooltipText = Strings.Settings.AutoSoftRetargetOnSelfCastTooltip, @@ -329,7 +335,7 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : }; // Game Settings - Typewriter Text - _typewriterCheckbox = new LabeledCheckBox(_interfaceSettings, "TypewriterCheckbox") + _typewriterCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: "TypewriterCheckbox") { Text = Strings.Settings.TypewriterText }; @@ -338,38 +344,34 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : #region InitVideoSettings - // Init VideoSettings Tab. - _videoSettingsTab = new Button(this, "VideoSettingsTab") + // Video Settings Get Stored in the VideoSettings Scroll Control. + _videoSettingsContainer = new ScrollControl(parent: this, name: "VideoSettingsContainer") { - Text = Strings.Settings.VideoSettingsTab + Dock = Pos.Fill, }; - _videoSettingsTab.Clicked += VideoSettingsTab_Clicked; - - // Video Settings Get Stored in the VideoSettings Scroll Control. - _videoSettingsContainer = new ScrollControl(this, "VideoSettingsContainer"); - _videoSettingsContainer.EnableScroll(false, false); + _videoSettingsContainer.EnableScroll(horizontal: false, vertical: false); // Video Settings - Resolution Background. - var resolutionBackground = new ImagePanel(_videoSettingsContainer, "ResolutionPanel"); + var resolutionBackground = new ImagePanel(parent: _videoSettingsContainer, name: "ResolutionPanel"); // Video Settings - Resolution Label. - var resolutionLabel = new Label(resolutionBackground, "ResolutionLabel") + var resolutionLabel = new Label(parent: resolutionBackground, name: "ResolutionLabel") { Text = Strings.Settings.Resolution }; // Video Settings - Resolution List. - _resolutionList = new ComboBox(resolutionBackground, "ResolutionCombobox"); + _resolutionList = new ComboBox(parent: resolutionBackground, name: "ResolutionCombobox"); var myModes = Graphics.Renderer?.GetValidVideoModes(); myModes?.ForEach( - t => + action: t => { - var item = _resolutionList.AddItem(t); + var item = _resolutionList.AddItem(label: t); item.TextAlign = Pos.Left; } ); - _worldScale = new LabeledHorizontalSlider(_videoSettingsContainer, "WorldScale") + _worldScale = new LabeledHorizontalSlider(parent: _videoSettingsContainer, name: "WorldScale") { IsDisabled = !Options.IsLoaded, Label = Strings.Settings.WorldScale, @@ -377,31 +379,31 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : }; // Video Settings - FPS Background. - var fpsBackground = new ImagePanel(_videoSettingsContainer, "FPSPanel"); + var fpsBackground = new ImagePanel(parent: _videoSettingsContainer, name: "FPSPanel"); // Video Settings - FPS Label. - var fpsLabel = new Label(fpsBackground, "FPSLabel") + var fpsLabel = new Label(parent: fpsBackground, name: "FPSLabel") { Text = Strings.Settings.TargetFps }; // Video Settings - FPS List. - _fpsList = new ComboBox(fpsBackground, "FPSCombobox"); - _ = _fpsList.AddItem(Strings.Settings.Vsync); - _ = _fpsList.AddItem(Strings.Settings.Fps30); - _ = _fpsList.AddItem(Strings.Settings.Fps60); - _ = _fpsList.AddItem(Strings.Settings.Fps90); - _ = _fpsList.AddItem(Strings.Settings.Fps120); - _ = _fpsList.AddItem(Strings.Settings.UnlimitedFps); + _fpsList = new ComboBox(parent: fpsBackground, name: "FPSCombobox"); + _ = _fpsList.AddItem(label: Strings.Settings.Vsync); + _ = _fpsList.AddItem(label: Strings.Settings.Fps30); + _ = _fpsList.AddItem(label: Strings.Settings.Fps60); + _ = _fpsList.AddItem(label: Strings.Settings.Fps90); + _ = _fpsList.AddItem(label: Strings.Settings.Fps120); + _ = _fpsList.AddItem(label: Strings.Settings.UnlimitedFps); // Video Settings - Fullscreen Checkbox. - _fullscreenCheckbox = new LabeledCheckBox(_videoSettingsContainer, "FullscreenCheckbox") + _fullscreenCheckbox = new LabeledCheckBox(parent: _videoSettingsContainer, name: "FullscreenCheckbox") { Text = Strings.Settings.Fullscreen }; // Video Settings - Enable Lighting Checkbox - _lightingEnabledCheckbox = new LabeledCheckBox(_videoSettingsContainer, "EnableLightingCheckbox") + _lightingEnabledCheckbox = new LabeledCheckBox(parent: _videoSettingsContainer, name: "EnableLightingCheckbox") { Text = Strings.Settings.EnableLighting }; @@ -410,77 +412,117 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : #region InitAudioSettings - // Init AudioSettingsTab. - _audioSettingsTab = new Button(this, "AudioSettingsTab") + // Audio Settings Get Stored in the AudioSettings Scroll Control. + _audioSettingsContainer = new ScrollControl(parent: this, name: "AudioSettingsContainer") { - Text = Strings.Settings.AudioSettingsTab + Dock = Pos.Fill, }; - _audioSettingsTab.Clicked += AudioSettingsTab_Clicked; - - // Audio Settings Get Stored in the AudioSettings Scroll Control. - _audioSettingsContainer = new ScrollControl(this, "AudioSettingsContainer"); - _audioSettingsContainer.EnableScroll(false, false); + _audioSettingsContainer.EnableScroll(horizontal: false, vertical: false); - // Audio Settings - Sound Label - _soundLabel = new Label(_audioSettingsContainer, "SoundLabel") + _audioTable = new Table(parent: _audioSettingsContainer, name: nameof(_audioTable)) { - Text = Strings.Settings.SoundVolume.ToString(100) + ColumnCount = 2, + CellSpacing = new Point(4, 4), + Dock = Pos.Fill, + SizeToContents = true, }; - // Audio Settings - Sound Slider - _soundSlider = new HorizontalSlider(_audioSettingsContainer, "SoundSlider") - { + var textureVolumeSlider = Current?.GetTexture(TextureType.Gui, "volume_slider.png"); + var textureVolumeSliderHovered = Current?.GetTexture(TextureType.Gui, "volume_slider_hovered.png"); + + // Audio Settings - Music Slider + _musicSlider = new LabeledSlider(parent: _audioSettingsContainer, name: nameof(_musicSlider)) + { + BackgroundImageName = "volume_bar.png", + Dock = Pos.Top, + Font = _defaultFont, + Orientation = Orientation.LeftToRight, + Height = 35, + DraggerSize = new Point(9, 35), + SliderSize = new Point(200, 35), + Label = Strings.Settings.VolumeMusic, + LabelMinimumSize = new Point(100, 0), + Rounding = 0, Min = 0, - Max = 100 + Max = 100, + NotchCount = 5, + SnapToNotches = false, + ShouldDrawBackground = true, }; - _soundSlider.ValueChanged += SoundSlider_ValueChanged; - // Audio Settings - Music Label - _musicLabel = new Label(_audioSettingsContainer, "MusicLabel") - { - Text = Strings.Settings.MusicVolume.ToString(100) - }; + _musicSlider.ValueChanged += MusicSliderOnValueChanged; + _musicSlider.SetDraggerImage(textureVolumeSlider, Dragger.ControlState.Normal); + _musicSlider.SetDraggerImage(textureVolumeSliderHovered, Dragger.ControlState.Hovered); + _musicSlider.SetDraggerImage(textureVolumeSlider, Dragger.ControlState.Clicked); + _musicSlider.SetSound("octave-tap-resonant.wav", Dragger.ControlSoundState.Hover); + _musicSlider.SetSound("octave-tap-professional.wav", Dragger.ControlSoundState.MouseDown); + _musicSlider.SetSound("octave-tap-professional.wav", Dragger.ControlSoundState.MouseUp); - // Audio Settings - Music Slider - _musicSlider = new HorizontalSlider(_audioSettingsContainer, "MusicSlider") - { + // Audio Settings - Sound Slider + _soundEffectsSlider = new LabeledSlider(parent: _audioSettingsContainer, name: nameof(_soundEffectsSlider)) + { + BackgroundImageName = "volume_bar.png", + Dock = Pos.Top, + Font = _defaultFont, + Orientation = Orientation.LeftToRight, + Height = 35, + DraggerSize = new Point(9, 35), + SliderSize = new Point(200, 35), + Label = Strings.Settings.VolumeSoundEffects, + LabelMinimumSize = new Point(100, 0), + Rounding = 0, Min = 0, - Max = 100 + Max = 100, + NotchCount = 5, + SnapToNotches = false, + ShouldDrawBackground = true, }; - _musicSlider.ValueChanged += MusicSlider_ValueChanged; + + _soundEffectsSlider.ValueChanged += SoundEffectsSliderOnValueChanged; + _soundEffectsSlider.SetDraggerImage(textureVolumeSlider, Dragger.ControlState.Normal); + _soundEffectsSlider.SetDraggerImage(textureVolumeSliderHovered, Dragger.ControlState.Hovered); + _soundEffectsSlider.SetDraggerImage(textureVolumeSlider, Dragger.ControlState.Clicked); + _soundEffectsSlider.SetSound("octave-tap-resonant.wav", Dragger.ControlSoundState.Hover); + _soundEffectsSlider.SetSound("octave-tap-professional.wav", Dragger.ControlSoundState.MouseDown); + _soundEffectsSlider.SetSound("octave-tap-professional.wav", Dragger.ControlSoundState.MouseUp); #endregion #region InitKeybindingSettings - // Init KeybindingsSettings Tab. - _keybindingSettingsTab = new Button(this, "KeybindingSettingsTab") + // KeybindingSettings Get Stored in the KeybindingSettings Scroll Control + _keybindingSettingsContainer = new ScrollControl(parent: this, name: "KeybindingSettingsContainer") { - Text = Strings.Settings.KeyBindingSettingsTab + Dock = Pos.Fill, }; - _keybindingSettingsTab.Clicked += KeybindingSettingsTab_Clicked; + _keybindingSettingsContainer.EnableScroll(horizontal: false, vertical: true); - // KeybindingSettings Get Stored in the KeybindingSettings Scroll Control - _keybindingSettingsContainer = new ScrollControl(this, "KeybindingSettingsContainer"); - _keybindingSettingsContainer.EnableScroll(false, true); + _controlsTable = new Table(parent: _keybindingSettingsContainer, name: nameof(_controlsTable)) + { + Dock = Pos.Top, + ColumnCount = 3, + SizeToContents = true, + }; // Keybinding Settings - Restore Default Keys Button. - _restoreDefaultKeybindingsButton = new Button(_bottomBar, nameof(_restoreDefaultKeybindingsButton)) + _restoreDefaultKeybindingsButton = new Button(parent: _bottomBar, name: nameof(_restoreDefaultKeybindingsButton)) { Alignment = [Alignments.Left, Alignments.CenterV], AutoSizeToContents = true, Font = fontNormal, - MinimumSize = new Point(96, 24), - TextPadding = new Padding(16, 2), + IsVisible = false, + MinimumSize = new Point(x: 96, y: 24), + TextPadding = new Padding(horizontal: 16, vertical: 2), Text = Strings.Settings.Restore, }; _restoreDefaultKeybindingsButton.Clicked += RestoreDefaultKeybindingsButton_Clicked; + _restoreDefaultKeybindingsButton.SetHoverSound("octave-tap-resonant.wav"); // Keybinding Settings - Controls var row = 0; foreach (var control in (_keybindingEditControls ?? Controls.ActiveControls).Mappings.Keys) { - AddControlKeybindRow(control, ref row, out _); + AddControlKeybindRow(control: control, row: ref row, keyButtons: out _); } Input.KeyDown += OnKeyDown; @@ -490,15 +532,53 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : #endregion - _tabs.AddPage(Strings.Settings.GameSettingsTab, _gameSettingsContainer); - _tabs.AddPage(Strings.Settings.VideoSettingsTab, _videoSettingsContainer); - _tabs.AddPage(Strings.Settings.AudioSettingsTab, _audioSettingsContainer); - _tabs.AddPage(Strings.Settings.KeyBindingSettingsTab, _keybindingSettingsContainer); + _gameSettingsTab = _tabs.AddPage(label: Strings.Settings.GameSettingsTab, page: _gameSettingsContainer); + _videoSettingsTab = _tabs.AddPage(label: Strings.Settings.VideoSettingsTab, page: _videoSettingsContainer); + _audioSettingsTab = _tabs.AddPage(label: Strings.Settings.AudioSettingsTab, page: _audioSettingsContainer); + _keybindingSettingsTab = _tabs.AddPage(label: Strings.Settings.KeyBindingSettingsTab, page: _keybindingSettingsContainer); + _tabs.TabChanged += TabsOnTabChanged; - LoadJsonUi(UI.Shared, Graphics.Renderer?.GetResolutionString()); + LoadJsonUi(stage: UI.Shared, resolution: Graphics.Renderer?.GetResolutionString()); IsHidden = true; } + private void TabsOnTabChanged(Base @base, TabChangeEventArgs tabChangeEventArgs) + { + if (_keybindingSettingsTab.IsActive) + { + _restoreDefaultKeybindingsButton.IsVisible = true; + + bool controlsAdded = false; + + _controlsTable.SizeToContents = true; + + var row = mKeybindingBtns.Count; + foreach (var (control, mapping) in (_keybindingEditControls ?? Controls.ActiveControls).Mappings) + { + if (!mKeybindingBtns.TryGetValue(control, out var controlButtons)) + { + controlsAdded |= AddControlKeybindRow(control, ref row, out controlButtons); + } + + var bindings = mapping.Bindings; + for (var bindingIndex = 0; bindingIndex < bindings.Count; bindingIndex++) + { + var binding = bindings[bindingIndex]; + controlButtons[bindingIndex].Text = Strings.Keys.FormatKeyName(binding.Modifier, binding.Key); + } + } + + if (controlsAdded) + { + // Current.SaveUIJson(UI.Shared, Name, GetJsonUI(true), Graphics.Renderer?.GetResolutionString()); + } + } + else + { + _restoreDefaultKeybindingsButton.IsVisible = false; + } + } + private bool AddControlKeybindRow(Control control, ref int row, out Button[] keyButtons) { if (mKeybindingBtns.TryGetValue(control, out var existingButtons)) @@ -527,47 +607,54 @@ private bool AddControlKeybindRow(Control control, ref int row, out Button[] key } } - var prefix = $"Control{controlName}"; - var label = new Label(_keybindingSettingsContainer, $"{prefix}Label") - { + var controlRow = _controlsTable.AddRow(); + + var prefix = $"Control{controlName}"; + var label = new Label(controlRow, $"{prefix}Label") + { + Alignment = [Alignments.CenterV, Alignments.Right], + Margin = new Margin(0, 0, 8, 0), Text = localizedControlName, - AutoSizeToContents = true, - Font = defaultFont, - }; - _ = label.SetBounds(14, 11 + offset, 21, 16); - label.SetTextColor(new Color(255, 255, 255, 255), Label.ControlState.Normal); + AutoSizeToContents = true, + MouseInputEnabled = false, + Font = defaultFont, + }; - var key1 = new Button(_keybindingSettingsContainer, $"{prefix}Button1") - { - AutoSizeToContents = false, - Font = defaultFont, - Text = string.Empty, - UserData = new KeyValuePair(control, 0), - }; - key1.SetTextColor(Color.White, Label.ControlState.Normal); - key1.SetImage("control_button.png", Button.ControlState.Normal); - key1.SetImage("control_button_hovered.png", Button.ControlState.Hovered); - key1.SetImage("control_button_clicked.png", Button.ControlState.Clicked); - key1.SetHoverSound("octave-tap-resonant.wav"); - key1.SetMouseDownSound("octave-tap-warm.wav"); - _ = key1.SetBounds(181, 6 + offset, 120, 28); - key1.Clicked += Key_Clicked; - - var key2 = new Button(_keybindingSettingsContainer, $"{prefix}Button2") - { - AutoSizeToContents = false, - Font = defaultFont, - Text = string.Empty, - UserData = new KeyValuePair(control, 1), - }; - key2.SetTextColor(Color.White, Label.ControlState.Normal); - key2.SetImage("control_button.png", Button.ControlState.Normal); - key2.SetImage("control_button_hovered.png", Button.ControlState.Hovered); - key2.SetImage("control_button_clicked.png", Button.ControlState.Clicked); - key2.SetHoverSound("octave-tap-resonant.wav"); - key2.SetMouseDownSound("octave-tap-warm.wav"); - _ = key2.SetBounds(309, 6 + offset, 120, 28); - key2.Clicked += Key_Clicked; + controlRow.SetCellContents(0, label, enableMouseInput: false); + // _ = label.SetBounds(14, 11 + offset, 21, 16); + label.SetTextColor(new Color(255, 255, 255, 255), Label.ControlState.Normal); + + var key1 = new Button(controlRow, $"{prefix}Button1") + { + Alignment = [Alignments.Center], + AutoSizeToContents = false, + Font = defaultFont, + MinimumSize = new Point(150, 20), + Text = string.Empty, + UserData = new KeyValuePair(control, 0), + }; + key1.SetTextColor(Color.White, Label.ControlState.Normal); + key1.SetHoverSound("octave-tap-resonant.wav"); + key1.SetMouseDownSound("octave-tap-warm.wav"); + controlRow.SetCellContents(1, key1, enableMouseInput: true); + // _ = key1.SetBounds(181, 6 + offset, 120, 28); + key1.Clicked += Key_Clicked; + + var key2 = new Button(controlRow, $"{prefix}Button2") + { + Alignment = [Alignments.Center], + AutoSizeToContents = false, + Font = defaultFont, + MinimumSize = new Point(150, 20), + Text = string.Empty, + UserData = new KeyValuePair(control, 1), + }; + key2.SetTextColor(Color.White, Label.ControlState.Normal); + key2.SetHoverSound("octave-tap-resonant.wav"); + key2.SetMouseDownSound("octave-tap-warm.wav"); + controlRow.SetCellContents(2, key2, enableMouseInput: true); + // _ = key2.SetBounds(309, 6 + offset, 120, 28); + key2.Clicked += Key_Clicked; keyButtons = [key1, key2]; mKeybindingBtns.Add(control, keyButtons); @@ -614,28 +701,6 @@ private void UpdateWorldScaleControls() _worldScale.Value = Globals.Database.WorldZoom; } - private void GameSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) - { - // Determine if GameSettingsContainer is currently being shown or not. - if (!_gameSettingsContainer.IsVisible) - { - // Disable the GameSettingsTab to fake it being selected visually. - _gameSettingsTab.Disable(); - _videoSettingsTab.Enable(); - _audioSettingsTab.Enable(); - _keybindingSettingsTab.Enable(); - - // Containers. - _gameSettingsContainer.Show(); - _videoSettingsContainer.Hide(); - _audioSettingsContainer.Hide(); - _keybindingSettingsContainer.Hide(); - - // Restore Default KeybindingSettings Button. - _restoreDefaultKeybindingsButton.Hide(); - } - } - void InterfaceSettings_Clicked(Base sender, ClickedEventArgs arguments) { _interfaceSettings.Show(); @@ -660,121 +725,11 @@ void TargetingSettings_Clicked(Base sender, ClickedEventArgs arguments) !(Options.Instance?.Combat?.EnableAutoSelfCastFriendlySpellsWhenTargetingHostile ?? false); } - private void VideoSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) - { - // Determine if VideoSettingsContainer is currently being shown or not. - if (!_videoSettingsContainer.IsVisible) - { - // Disable the VideoSettingsTab to fake it being selected visually. - _gameSettingsTab.Enable(); - _videoSettingsTab.Disable(); - _audioSettingsTab.Enable(); - _keybindingSettingsTab.Enable(); - - // Containers. - _gameSettingsContainer.Hide(); - _videoSettingsContainer.Show(); - _audioSettingsContainer.Hide(); - _keybindingSettingsContainer.Hide(); - - // Restore Default KeybindingSettings Button. - _restoreDefaultKeybindingsButton.Hide(); - } - } - - private void AudioSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) - { - // Determine if AudioSettingsContainer is currently being shown or not. - if (!_audioSettingsContainer.IsVisible) - { - // Disable the AudioSettingsTab to fake it being selected visually. - _gameSettingsTab.Enable(); - _videoSettingsTab.Enable(); - _audioSettingsTab.Disable(); - _keybindingSettingsTab.Enable(); - - // Containers. - _gameSettingsContainer.Hide(); - _videoSettingsContainer.Hide(); - _audioSettingsContainer.Show(); - _keybindingSettingsContainer.Hide(); - - // Restore Default KeybindingSettings Button. - _restoreDefaultKeybindingsButton.Hide(); - } - } - - private void KeybindingSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) - { - // Determine if controls are currently being shown or not. - if (!_keybindingSettingsContainer.IsVisible) - { - // Disable the KeybindingSettingsTab to fake it being selected visually. - _gameSettingsTab.Enable(); - _videoSettingsTab.Enable(); - _audioSettingsTab.Enable(); - _keybindingSettingsTab.Disable(); - - // Containers. - _gameSettingsContainer.Hide(); - _videoSettingsContainer.Hide(); - _audioSettingsContainer.Hide(); - _keybindingSettingsContainer.Show(); - - // Restore Default KeybindingSettings Button. - _restoreDefaultKeybindingsButton.Show(); - - bool controlsAdded = false; - - var row = mKeybindingBtns.Count; - foreach (var (control, mapping) in (_keybindingEditControls ?? Controls.ActiveControls).Mappings) - { - if (!mKeybindingBtns.TryGetValue(control, out var controlButtons)) - { - controlsAdded |= AddControlKeybindRow(control, ref row, out controlButtons); - } - - var bindings = mapping.Bindings; - for (var bindingIndex = 0; bindingIndex < bindings.Count; bindingIndex++) - { - var binding = bindings[bindingIndex]; - controlButtons[bindingIndex].Text = Strings.Keys.FormatKeyName(binding.Modifier, binding.Key); - } - } - - if (controlsAdded) - { - Current.SaveUIJson(UI.Shared, Name, GetJsonUI(true), Graphics.Renderer?.GetResolutionString()); - } - } - } - - private void LoadSettingsWindow() + private void Reset() { Title = Strings.Settings.Title; - // Containers. - _gameSettingsContainer.Show(); - _videoSettingsContainer.Hide(); - _audioSettingsContainer.Hide(); - _keybindingSettingsContainer.Hide(); - - // Tabs. - _gameSettingsTab.Show(); - _videoSettingsTab.Show(); - _audioSettingsTab.Show(); - _keybindingSettingsTab.Show(); - - // Disable the GameSettingsTab to fake it being selected visually by default. - _gameSettingsTab.Disable(); - _videoSettingsTab.Enable(); - _audioSettingsTab.Enable(); - _keybindingSettingsTab.Enable(); - - // Buttons. - _applyPendingChangesButton.Show(); - _cancelPendingChangesButton.Show(); - _restoreDefaultKeybindingsButton.Hide(); + _gameSettingsTab.Select(); UpdateWorldScaleControls(); } @@ -942,9 +897,7 @@ public void Show(bool returnToMenu = false) _previousMusicVolume = Globals.Database.MusicVolume; _previousSoundVolume = Globals.Database.SoundVolume; _musicSlider.Value = Globals.Database.MusicVolume; - _soundSlider.Value = Globals.Database.SoundVolume; - _musicLabel.Text = Strings.Settings.MusicVolume.ToString((int)_musicSlider.Value); - _soundLabel.Text = Strings.Settings.SoundVolume.ToString((int)_soundSlider.Value); + _soundEffectsSlider.Value = Globals.Database.SoundVolume; // Control Settings. _keybindingEditControls = new Controls(Controls.ActiveControls); @@ -953,7 +906,7 @@ public void Show(bool returnToMenu = false) base.Show(); // Load every GUI element to their default state when showing up the settings window (pressed tabs, containers, etc.) - LoadSettingsWindow(); + Reset(); // Set up whether we're supposed to return to the previous menu. _returnToMenu = returnToMenu; @@ -987,17 +940,17 @@ public override void Hide() } // Input Handlers - private void MusicSlider_ValueChanged(Base sender, EventArgs arguments) + private void MusicSliderOnValueChanged(Base sender, ValueChangedEventArgs arguments) { - _musicLabel.Text = Strings.Settings.MusicVolume.ToString((int)_musicSlider.Value); - Globals.Database.MusicVolume = (int)_musicSlider.Value; + Globals.Database.MusicVolume = (int)arguments.Value; + ApplicationContext.CurrentContext.Logger.LogInformation("Music volume set to {MusicVolume}", arguments.Value); Audio.UpdateGlobalVolume(); } - private void SoundSlider_ValueChanged(Base sender, EventArgs arguments) + private void SoundEffectsSliderOnValueChanged(Base sender, ValueChangedEventArgs arguments) { - _soundLabel.Text = Strings.Settings.SoundVolume.ToString((int)_soundSlider.Value); - Globals.Database.SoundVolume = (int)_soundSlider.Value; + Globals.Database.SoundVolume = (int)arguments.Value; + ApplicationContext.CurrentContext.Logger.LogInformation("Sound volume set to {SoundVolume}", arguments.Value); Audio.UpdateGlobalVolume(); } @@ -1121,7 +1074,7 @@ private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) // Audio Settings. Globals.Database.MusicVolume = (int)_musicSlider.Value; - Globals.Database.SoundVolume = (int)_soundSlider.Value; + Globals.Database.SoundVolume = (int)_soundEffectsSlider.Value; Audio.UpdateGlobalVolume(); // Control Settings. diff --git a/Intersect.Client.Core/Localization/Strings.cs b/Intersect.Client.Core/Localization/Strings.cs index f8fbebae09..d934eb6a4d 100644 --- a/Intersect.Client.Core/Localization/Strings.cs +++ b/Intersect.Client.Core/Localization/Strings.cs @@ -1973,6 +1973,15 @@ public partial struct Settings [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString MusicVolume = @"Music Volume: {00}%"; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString SectionVolume = @"Volume"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString VolumeMusic = @"Music"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString VolumeSoundEffects = @"Sound Effects"; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString Resolution = @"Resolution:"; diff --git a/Intersect.Client.Framework/Gwen/Control/Base.cs b/Intersect.Client.Framework/Gwen/Control/Base.cs index 2cc34b6d5e..527b64b5ad 100644 --- a/Intersect.Client.Framework/Gwen/Control/Base.cs +++ b/Intersect.Client.Framework/Gwen/Control/Base.cs @@ -826,6 +826,10 @@ public int Height set => SetSize(Width, value); } + public int OuterWidth => Width + Margin.Left + Margin.Right; + + public int OuterHeight => Height + Margin.Top + Margin.Bottom; + public Point Size { get => mBounds.Size; @@ -1545,8 +1549,15 @@ protected virtual void InvalidateChildren(bool recursive = false) /// public virtual void Invalidate() { - mNeedsLayout = true; - mCacheTextureDirty = true; + if (!mNeedsLayout) + { + mNeedsLayout = true; + } + + if (!mCacheTextureDirty) + { + mCacheTextureDirty = true; + } } public virtual void MoveBefore(Base other) @@ -1920,47 +1931,59 @@ protected virtual void OnChildRemoved(Base child) public virtual void MoveTo(float x, float y) => MoveTo((int) x, (int) y); /// - /// Moves the control to a specific point, clamping on paren't bounds if RestrictToParent is set. + /// Moves the control to a specific point, clamping on parent bounds if RestrictToParent is set. /// /// Target x coordinate. /// Target y coordinate. + /// public virtual void MoveTo(int x, int y, bool aligning = false) { - if (RestrictToParent && Parent != null) + var ownWidth = Width; + var ownHeight = Height; + var ownMargin = Margin; + + if (RestrictToParent && Parent is {} parent) { - var parent = Parent; - if (x - Padding.Left - (aligning ? mAlignmentDistance.Left : 0) < parent.Margin.Left) + var ownPadding = Parent.Padding; + + var alignmentDistanceLeft = aligning ? mAlignmentDistance.Left : 0; + var alignmentDistanceTop = aligning ? mAlignmentDistance.Top : 0; + var alignmentDistanceRight = aligning ? mAlignmentDistance.Right : 0; + var alignmentDistanceBottom = aligning ? mAlignmentDistance.Bottom : 0; + var parentWidth = parent.Width; + var parentHeight = parent.Height; + + var xFromLeft = ownMargin.Left + ownPadding.Left + alignmentDistanceLeft; + var xFromRight = parentWidth - ownMargin.Right - ownWidth - ownPadding.Right - alignmentDistanceRight; + + if (xFromLeft > xFromRight) { - x = parent.Margin.Left + Padding.Left + (aligning ? mAlignmentDistance.Left : 0); + x = (int)((xFromLeft + xFromRight) / 2f); } - - if (y - Padding.Top - (aligning ? mAlignmentDistance.Top : 0) < parent.Margin.Top) + else { - y = parent.Margin.Top + Padding.Top + (aligning ? mAlignmentDistance.Top : 0); + x = Math.Clamp(x, xFromLeft, xFromRight); } - if (x + Width + Padding.Right + (aligning ? mAlignmentDistance.Right : 0) > - parent.Width - parent.Margin.Right) + var yFromTop = ownMargin.Top + ownPadding.Top + alignmentDistanceTop; + var yFromBottom = parentHeight - ownMargin.Bottom - ownHeight - ownPadding.Bottom - alignmentDistanceBottom; + + if (yFromTop > yFromBottom) { - x = parent.Width - - parent.Margin.Right - - Width - - Padding.Right - - (aligning ? mAlignmentDistance.Right : 0); + y = (int)((yFromTop + yFromBottom) / 2f); } - - if (y + Height + Padding.Bottom + (aligning ? mAlignmentDistance.Bottom : 0) > - parent.Height - parent.Margin.Bottom) + else { - y = parent.Height - - parent.Margin.Bottom - - Height - - Padding.Bottom - - (aligning ? mAlignmentDistance.Bottom : 0); + y = Math.Clamp(y, yFromTop, yFromBottom); } } + else + { + x = Math.Max(x, ownMargin.Left); + y = Math.Max(y, ownMargin.Top); + } - SetBounds(x, y, Width, Height); + SetBounds(x, y, ownWidth, ownHeight); } /// @@ -2041,6 +2064,9 @@ public virtual bool SetBounds(Point position, Point size) => SetBounds( /// public virtual bool SetBounds(int x, int y, int width, int height) { + width = Math.Max(0, width); + height = Math.Max(0, height); + if (mBounds.X == x && mBounds.Y == y && mBounds.Width == width && mBounds.Height == height) { return false; @@ -2048,14 +2074,24 @@ public virtual bool SetBounds(int x, int y, int width, int height) var oldBounds = Bounds; - mBounds.X = x; - mBounds.Y = y; + var newBounds = mBounds with + { + X = x, + Y = y, + }; var maximumSize = MaximumSize; - mBounds.Width = maximumSize.X > 0 ? Math.Min(MaximumSize.X, width) : width; - mBounds.Height = maximumSize.Y > 0 ? Math.Min(MaximumSize.Y, height) : height; + newBounds.Width = maximumSize.X > 0 ? Math.Min(MaximumSize.X, width) : width; + newBounds.Height = maximumSize.Y > 0 ? Math.Min(MaximumSize.Y, height) : height; + + if (newBounds.Width > 10000 || newBounds.Height > 10000) + { + newBounds.ToString(); + } - if (oldBounds.Size != mBounds.Size) + mBounds = newBounds; + + if (oldBounds.Size != newBounds.Size) { ProcessAlignments(); } @@ -2774,13 +2810,13 @@ protected virtual void RecurseLayout(Skin.Base skin) Layout(skin); } - var ownBounds = RenderBounds; + var remainingBounds = RenderBounds; // Adjust bounds for padding - ownBounds.X += mPadding.Left; - ownBounds.Width -= mPadding.Left + mPadding.Right; - ownBounds.Y += mPadding.Top; - ownBounds.Height -= mPadding.Top + mPadding.Bottom; + remainingBounds.X += mPadding.Left; + remainingBounds.Width -= mPadding.Left + mPadding.Right; + remainingBounds.Y += mPadding.Top; + remainingBounds.Height -= mPadding.Top + mPadding.Bottom; foreach (var child in mChildren) { @@ -2803,65 +2839,83 @@ protected virtual void RecurseLayout(Skin.Base skin) var childOuterWidth = childMarginH + child.Width; var childOuterHeight = childMarginV + child.Height; - if (childDock.HasFlag(Pos.Top)) - { - child.SetBounds( - ownBounds.X + childMargin.Left, - ownBounds.Y + childMargin.Top, - ownBounds.Width - childMarginH, - child.Height - ); + var availableWidth = remainingBounds.Width - childMarginH; + var availableHeight = remainingBounds.Height - childMarginV; - ownBounds.Y += childOuterHeight; - ownBounds.Height -= childOuterHeight; - } + var childFitsContents = child is IAutoSizeToContents { AutoSizeToContents: true }; if (childDock.HasFlag(Pos.Left)) { + var height = childFitsContents + ? child.Height + : availableHeight; + + var y = remainingBounds.Y + childMargin.Top; + if (childDock.HasFlag(Pos.Bottom)) + { + y = remainingBounds.Bottom - (childMargin.Bottom + child.Height); + } + else if (!childDock.HasFlag(Pos.Top)) + { + var extraY = Math.Max(0, availableHeight - height) / 2; + if (extraY != 0) + { + y += extraY; + } + } + child.SetBounds( - ownBounds.X + childMargin.Left, - ownBounds.Y + childMargin.Top, + remainingBounds.X + childMargin.Left, + y, child.Width, - ownBounds.Height - childMarginV + height ); - ownBounds.X += childOuterWidth; - ownBounds.Width -= childOuterWidth; + remainingBounds.X += childOuterWidth; + remainingBounds.Width -= childOuterWidth; } if (childDock.HasFlag(Pos.Right)) { child.SetBounds( - ownBounds.X + ownBounds.Width - child.Width - childMargin.Right, - ownBounds.Y + childMargin.Top, + remainingBounds.X + remainingBounds.Width - child.Width - childMargin.Right, + remainingBounds.Y + childMargin.Top, child.Width, - ownBounds.Height - childMarginV + availableHeight ); - ownBounds.Width -= childOuterWidth; + remainingBounds.Width -= childOuterWidth; } - if (childDock.HasFlag(Pos.Bottom)) + if (childDock.HasFlag(Pos.Top) && !childDock.HasFlag(Pos.Left) && !childDock.HasFlag(Pos.Right)) { - if (child.Name?.StartsWith("BottomBar") ?? false) - { - child.Margin.ToString(); - } + child.SetBounds( + remainingBounds.X + childMargin.Left, + remainingBounds.Y + childMargin.Top, + availableWidth, + child.Height + ); + remainingBounds.Y += childOuterHeight; + remainingBounds.Height -= childOuterHeight; + } + + if (childDock.HasFlag(Pos.Bottom) && !childDock.HasFlag(Pos.Left) && !childDock.HasFlag(Pos.Right)) + { child.SetBounds( - ownBounds.Left + childMargin.Left, - ownBounds.Bottom - (child.Height + childMargin.Bottom), - ownBounds.Width - childMarginH, + remainingBounds.Left + childMargin.Left, + remainingBounds.Bottom - (child.Height + childMargin.Bottom), + availableWidth, child.Height ); - ownBounds.Height -= childOuterHeight; + remainingBounds.Height -= childOuterHeight; } child.RecurseLayout(skin); } - mInnerBounds = ownBounds; + mInnerBounds = remainingBounds; // // Fill uses the left over space, so do that now. @@ -2885,30 +2939,30 @@ protected virtual void RecurseLayout(Skin.Base skin) var childMarginV = childMargin.Top + childMargin.Bottom; Point newPosition = new( - ownBounds.X + childMargin.Left, - ownBounds.Y + childMargin.Top + remainingBounds.X + childMargin.Left, + remainingBounds.Y + childMargin.Top ); if (child is IAutoSizeToContents { AutoSizeToContents: true }) { if (Pos.Right == (dock & (Pos.Right | Pos.Left))) { - newPosition.X = ownBounds.Right - (childMargin.Right + child.Width); + newPosition.X = remainingBounds.Right - (childMargin.Right + child.Width); } if (Pos.Bottom == (dock & (Pos.Bottom | Pos.Top))) { - newPosition.Y = ownBounds.Bottom - (childMargin.Bottom + child.Height); + newPosition.Y = remainingBounds.Bottom - (childMargin.Bottom + child.Height); } if (dock.HasFlag(Pos.CenterH)) { - newPosition.X = ownBounds.X + (ownBounds.Width - (childMarginH + child.Width)) / 2; + newPosition.X = remainingBounds.X + (remainingBounds.Width - (childMarginH + child.Width)) / 2; } if (dock.HasFlag(Pos.CenterV)) { - newPosition.Y = ownBounds.Y + (ownBounds.Height - (childMarginV + child.Height)) / 2; + newPosition.Y = remainingBounds.Y + (remainingBounds.Height - (childMarginV + child.Height)) / 2; } child.SetPosition(newPosition); @@ -2916,8 +2970,8 @@ protected virtual void RecurseLayout(Skin.Base skin) else { Point newSize = new( - ownBounds.Width - childMarginH, - ownBounds.Height - childMarginV + remainingBounds.Width - childMarginH, + remainingBounds.Height - childMarginV ); child.SetBounds(newPosition, newSize); @@ -2983,27 +3037,50 @@ public virtual Point LocalPosToCanvas(Point pnt) /// /// Converts canvas coordinates to local coordinates. /// - /// Canvas coordinates. + /// Canvas coordinates. /// Local coordinates. - public virtual Point CanvasPosToLocal(Point pnt) + public virtual Point CanvasPosToLocal(Point globalCoordinates) => ToLocal(globalCoordinates.X, globalCoordinates.Y); + + public virtual bool HasChild(Base component) => IsChild(component); + + public Point ToGlobal(Point point) => ToGlobal(point.X, point.Y); + + public virtual Point ToGlobal(int x, int y) { - if (mParent == null) + if (mParent is not { } parent) { - return pnt; + return new Point(x, y); } - var x = pnt.X - X; - var y = pnt.Y - Y; + x += X; + y += Y; - // If our parent has an innerpanel and we're a child of it - // add its offset onto us. - if (mParent._innerPanel != null && mParent._innerPanel.IsChild(this)) + if (parent._innerPanel is not { } innerPanel || !innerPanel.HasChild(this)) { - x -= mParent._innerPanel.X; - y -= mParent._innerPanel.Y; + return parent.ToGlobal(x, y); } - return mParent.CanvasPosToLocal(new Point(x, y)); + return innerPanel.ToGlobal(x, y); + } + + public Point ToLocal(Point point) => ToLocal(point.X, point.Y); + + public virtual Point ToLocal(int x, int y) + { + if (mParent is not {} parent) + { + return new Point(x, y); + } + + x -= X; + y -= Y; + + if (parent._innerPanel is not { } innerPanel || !innerPanel.HasChild(this)) + { + return parent.ToLocal(x, y); + } + + return innerPanel.ToLocal(x, y); } /// @@ -3149,20 +3226,42 @@ public virtual bool SizeToChildren(bool width = true, bool height = true) /// public virtual Point GetChildrenSize() { - var size = Point.Empty; + Point min = new(int.MaxValue, int.MaxValue); + Point max = default; - for (int i = 0; i < mChildren.Count; i++) + var children = mChildren.ToArray(); + foreach (var child in children) { - if (mChildren[i].IsHidden) + if (!child.IsVisible) { continue; } - size.X = Math.Max(size.X, mChildren[i].Right); - size.Y = Math.Max(size.Y, mChildren[i].Bottom); + var childBounds = child.Bounds; + min.X = Math.Min(min.X, childBounds.Left); + min.Y = Math.Min(min.Y, childBounds.Top); + max.X = Math.Max(max.X, childBounds.Right); + max.Y = Math.Max(max.Y, childBounds.Bottom); + } + + var delta = max - min; + return delta; + } + + public virtual Point MeasureContent() + { + var contentSize = GetChildrenSize(); + contentSize.X += Padding.Left + Padding.Right; + contentSize.Y += Padding.Top + Padding.Bottom; + + // ReSharper disable once InvertIf + if (_innerPanel is { } innerPanel) + { + contentSize.X += innerPanel.Padding.Left + innerPanel.Padding.Right; + contentSize.Y += innerPanel.Padding.Top + innerPanel.Padding.Bottom; } - return size; + return contentSize; } /// diff --git a/Intersect.Client.Framework/Gwen/Control/DockedTabControl.cs b/Intersect.Client.Framework/Gwen/Control/DockedTabControl.cs index 67364ec439..e8a08a405a 100644 --- a/Intersect.Client.Framework/Gwen/Control/DockedTabControl.cs +++ b/Intersect.Client.Framework/Gwen/Control/DockedTabControl.cs @@ -44,12 +44,12 @@ protected override void Layout(Skin.Base skin) private void UpdateTitleBar() { - if (CurrentButton == null) + if (SelectedTab == null) { return; } - mTitleBar.UpdateFromTab(CurrentButton); + mTitleBar.UpdateFromTab(SelectedTab); } public override void DragAndDrop_StartDragging(DragDrop.Package package, int x, int y) diff --git a/Intersect.Client.Framework/Gwen/Control/EventArguments/ValueChangedEventArgs.cs b/Intersect.Client.Framework/Gwen/Control/EventArguments/ValueChangedEventArgs.cs new file mode 100644 index 0000000000..fd482eea8e --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/EventArguments/ValueChangedEventArgs.cs @@ -0,0 +1,8 @@ +namespace Intersect.Client.Framework.Gwen.Control.EventArguments; + +public class ValueChangedEventArgs : EventArgs +{ + public ValueChangedEventArgs() { } + + public required TValue Value { get; init; } +} \ No newline at end of file diff --git a/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs b/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs index 01af80321f..453dfa23ad 100644 --- a/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs +++ b/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs @@ -81,7 +81,7 @@ protected override void Layout(Skin.Base skin) mScrollButton[1].Dock = Pos.Right; mBar.Height = ButtonSize; - mBar.Padding = new Padding(ButtonSize, 0, ButtonSize, 0); + mBar.Margin = new Margin(ButtonSize, 0, ButtonSize, 0); var barWidth = mViewableContentSize / mContentSize * (Width - ButtonSize * 2); diff --git a/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs b/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs index d863183923..68585045e5 100644 --- a/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs +++ b/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs @@ -1,29 +1,28 @@ namespace Intersect.Client.Framework.Gwen.Control; - /// /// Horizontal slider. /// public partial class HorizontalSlider : Slider { - /// /// Initializes a new instance of the class. /// /// Parent control. - public HorizontalSlider(Base parent, string name = "") : base(parent, name) + /// + public HorizontalSlider(Base parent, string? name = default) : base(parent, name) { - mSliderBar.IsHorizontal = true; + _sliderBar.IsHorizontal = true; } protected override float CalculateValue() { - return (float) mSliderBar.X / (Width - mSliderBar.Width); + return (float) _sliderBar.X / (Width - _sliderBar.Width); } protected override void UpdateBarFromValue() { - mSliderBar.MoveTo((int) ((Width - mSliderBar.Width) * mValue), mSliderBar.Y); + _sliderBar.MoveTo((int) ((Width - _sliderBar.Width) * _value), _sliderBar.Y); } /// @@ -35,9 +34,9 @@ protected override void UpdateBarFromValue() protected override void OnMouseClickedLeft(int x, int y, bool down, bool automated = false) { base.OnMouseClickedLeft(x, y, down); - mSliderBar.MoveTo((int) (CanvasPosToLocal(new Point(x, y)).X - mSliderBar.Width * 0.5), mSliderBar.Y); - mSliderBar.InputMouseClickedLeft(x, y, down, true); - OnMoved(mSliderBar, EventArgs.Empty); + _sliderBar.MoveTo((int) (CanvasPosToLocal(new Point(x, y)).X - _sliderBar.Width * 0.5), _sliderBar.Y); + _sliderBar.InputMouseClickedLeft(x, y, down, true); + SliderBarOnDragged(_sliderBar, EventArgs.Empty); } /// @@ -56,7 +55,7 @@ protected override void Layout(Skin.Base skin) /// Skin to use. protected override void Render(Skin.Base skin) { - skin.DrawSlider(this, true, Notches, mSnapToNotches ? mNotchCount : 0, mSliderBar.Width); + skin.DrawSlider(this, true, Notches, _snapToNotches ? _notchCount : 0, _sliderBar.Width); } } diff --git a/Intersect.Client.Framework/Gwen/Control/Label.cs b/Intersect.Client.Framework/Gwen/Control/Label.cs index 39330c303c..292a6fb125 100644 --- a/Intersect.Client.Framework/Gwen/Control/Label.cs +++ b/Intersect.Client.Framework/Gwen/Control/Label.cs @@ -99,6 +99,12 @@ public GameTexture? ToolTipBackground } } + public bool IsTextDisabled + { + get => _textElement.IsHidden; + set => _textElement.IsHidden = value; + } + /// /// Text alignment. /// diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs b/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs index 9aeeb2d693..68ee89e5df 100644 --- a/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs +++ b/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs @@ -3,7 +3,6 @@ namespace Intersect.Client.Framework.Gwen.Control; - /// /// Base slider. /// diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledSlider.cs b/Intersect.Client.Framework/Gwen/Control/LabeledSlider.cs new file mode 100644 index 0000000000..05d5eaf00c --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/LabeledSlider.cs @@ -0,0 +1,322 @@ +using System.Globalization; +using Intersect.Client.Framework.GenericClasses; +using Intersect.Client.Framework.Graphics; +using Intersect.Client.Framework.Gwen.Control.EventArguments; +using Intersect.Client.Framework.Gwen.ControlInternal; +using Intersect.Framework; +using Newtonsoft.Json.Linq; + +namespace Intersect.Client.Framework.Gwen.Control; + +public partial class LabeledSlider : Base +{ + private readonly Label _label; + private readonly Slider _slider; + private readonly TextBoxNumeric _sliderValue; + private double _scale = 1.0; + private int _rounding = -1; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + /// + public LabeledSlider(Base parent, string? name = default) : base(parent: parent, name: name) + { + _label = new Label(this, nameof(_label)) + { + Alignment = [Alignments.CenterV], + TextAlign = Pos.Right | Pos.CenterV, + }; + + _slider = new Slider(this, nameof(_slider)); + _sliderValue = new TextBoxNumeric(this, nameof(_sliderValue)) + { + Alignment = [Alignments.CenterV], + AutoSizeToContents = true, + MinimumSize = ComputeMinimumSizeForSliderValue(100), + }; + + _slider.ValueChanged += (sender, arguments) => + { + if (sender == _sliderValue) + { + return; + } + + var newValue = _slider.Value * _scale; + if (_rounding > -1) + { + newValue = Math.Round(newValue, _rounding); + } + + _sliderValue.Value = newValue; + ValueChanged?.Invoke(sender, arguments); + }; + + _sliderValue.TextChanged += (sender, _) => + { + if (sender == _slider) + { + return; + } + + var newValue = _sliderValue.Value / _scale; + _slider.Value = newValue; + ValueChanged?.Invoke( + sender, + new ValueChangedEventArgs + { + Value = newValue, + } + ); + }; + + KeyboardInputEnabled = true; + IsTabable = true; + } + + public GameTexture? BackgroundImage + { + get => _slider.BackgroundImage; + set => _slider.BackgroundImage = value; + } + + public string? BackgroundImageName + { + get => _slider.BackgroundImageName; + set => _slider.BackgroundImageName = value; + } + + public Point DraggerSize + { + get => _slider.DraggerSize; + set => _slider.DraggerSize = value; + } + + public GameFont? Font + { + get => _label.Font; + set + { + _label.Font = value; + _sliderValue.Font = value; + _sliderValue.MinimumSize = ComputeMinimumSizeForSliderValue(); + } + } + + public Point SliderSize + { + get => _slider.Size; + set => _slider.Size = value; + } + + public bool IsValueInputEnabled + { + get => _sliderValue.IsVisible; + set => _sliderValue.IsVisible = value; + } + + public string? Label + { + get => _label.Text; + set => _label.Text = value; + } + + public Orientation Orientation + { + get => _slider.Orientation; + set => _slider.Orientation = value; + } + + /// + /// Number of notches on the slider axis. + /// + public int NotchCount + { + get => _slider.NotchCount; + set => _slider.NotchCount = value; + } + + public double[]? Notches + { + get => _slider.Notches; + set => _slider.Notches = value; + } + + /// + /// Determines whether the slider should snap to notches. + /// + public bool SnapToNotches + { + get => _slider.SnapToNotches; + set => _slider.SnapToNotches = value; + } + + public int Rounding + { + get => _rounding; + set + { + _rounding = value; + if (_rounding > -1) + { + _sliderValue.Value = Math.Round(_sliderValue.Value, _rounding); + } + } + } + + /// + /// Minimum value. + /// + public double Min + { + get => _slider.Min; + set => _slider.Min = value; + } + + /// + /// Maximum value. + /// + public double Max + { + get => _slider.Max; + set + { + _slider.Max = value; + _sliderValue.MinimumSize = ComputeMinimumSizeForSliderValue(value); + } + } + + private Point ComputeMinimumSizeForSliderValue(double? value = null) + { + return Skin.Renderer.MeasureText(_sliderValue?.Font, (value ?? Max).ToString(CultureInfo.CurrentUICulture)) + + (_sliderValue?.Padding ?? Padding.Zero) + + (_sliderValue?.TextPadding ?? Padding.Zero); + } + + public double Scale + { + get => _scale; + set + { + _scale = value; + _sliderValue.Value = _slider.Value * _scale; + } + } + + /// + /// Current value. + /// + public double Value + { + get => _slider.Value; + set + { + _slider.Value = value; + _sliderValue.Value = _slider.Value * _scale; + } + } + + public override bool IsDisabled + { + get => base.IsDisabled; + set + { + base.IsDisabled = value; + _label.IsDisabled = value; + _slider.IsDisabled = value; + _sliderValue.IsDisabled = value; + } + } + + public Point LabelMinimumSize + { + get => _label.MinimumSize; + set => _label.MinimumSize = value; + } + + public void SetDraggerImage(GameTexture? texture, Dragger.ControlState state) + { + _slider.SetDraggerImage(texture, state); + } + + public GameTexture? GetDraggerImage(Dragger.ControlState state) + { + return _slider.GetDraggerImage(state); + } + + public void SetSound(string? sound, Dragger.ControlSoundState state) + { + _slider.SetSound(sound, state); + } + + /// + /// Invoked when the value has been changed. + /// + public event GwenEventHandler>? ValueChanged; + + protected override void Layout(Skin.Base skin) + { + SizeToChildren(); + + var orientation = _slider.Orientation; + switch (orientation) + { + case Orientation.LeftToRight: + _label.Dock = Pos.Left | Pos.Bottom; + _label.Alignment = [Alignments.CenterV]; + _slider.Dock = Pos.Left | Pos.Bottom; + _slider.Alignment = [Alignments.CenterV]; + _slider.Margin = new Margin(4, 0, 0, 0); + _sliderValue.Dock = Pos.Left | Pos.Bottom; + _sliderValue.Alignment = [Alignments.CenterV]; + _sliderValue.Margin = new Margin(4, 0, 0, 0); + break; + case Orientation.RightToLeft: + _label.Dock = Pos.Right | Pos.Bottom; + _slider.Dock = Pos.Right | Pos.Bottom; + _slider.Margin = new Margin(0, 0, 4, 0); + _sliderValue.Dock = Pos.Right | Pos.Bottom; + _sliderValue.Margin = new Margin(0, 0, 4, 0); + break; + case Orientation.TopToBottom: + _label.Dock = Pos.Top; + _slider.Dock = Pos.Top; + _slider.Margin = new Margin(0, 4, 0, 0); + _sliderValue.Dock = Pos.Top; + _sliderValue.Margin = new Margin(0, 4, 0, 0); + break; + case Orientation.BottomToTop: + _label.Dock = Pos.Bottom; + _slider.Dock = Pos.Bottom; + _slider.Margin = new Margin(0, 0, 0, 4); + _sliderValue.Dock = Pos.Bottom; + _sliderValue.Margin = new Margin(0, 0, 0, 4); + break; + default: + throw Exceptions.UnreachableInvalidEnum(orientation); + } + + base.Layout(skin); + } + + public override JObject GetJson(bool isRoot = default) + { + var obj = base.GetJson(isRoot); + + return base.FixJson(obj); + } + + public override void SetToolTipText(string? text) + { + _label.SetToolTipText(text); + _slider.SetToolTipText(text); + _sliderValue.SetToolTipText(text); + } + + protected override void OnBoundsChanged(Rectangle oldBounds) + { + base.OnBoundsChanged(oldBounds); + } +} \ No newline at end of file diff --git a/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs b/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs index 50131fde1b..3084cfb1cc 100644 --- a/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs +++ b/Intersect.Client.Framework/Gwen/Control/Layout/Table.cs @@ -1,5 +1,6 @@ using System.Globalization; using Intersect.Client.Framework.File_Management; +using Intersect.Client.Framework.GenericClasses; using Intersect.Client.Framework.Graphics; using Intersect.Client.Framework.Gwen.Control.Data; using Intersect.Configuration; @@ -14,61 +15,98 @@ namespace Intersect.Client.Framework.Gwen.Control.Layout; public partial class Table : Base, IColorableText { - private readonly List mColumnWidths; + private readonly List _columnWidths; + private readonly List _columnTextAlignments; - private readonly List mColumnAlignments; + private int _columnCount; + private int _defaultRowHeight; + private Point _cellSpacing; + private bool _sizeToContents; - private int mColumnCount; - - private int mDefaultRowHeight; - - private ITableDataProvider mDataProvider; - - private int mMaxWidth; // for autosizing, if nonzero - fills last cell up to this size - - // only children of this control should be TableRow. - - private bool mSizeToContents; + private ITableDataProvider? _dataProvider; private GameFont? _font; - private Color? _textColor; - private Color? _textColorOverride; + private bool _fitRowHeightToContents = true; /// /// Initializes a new instance of the class. /// /// Parent control. - public Table(Base parent, string name = default) : base(parent, name) + /// + public Table(Base parent, string? name = default) : base(parent, name) { - mColumnCount = 1; - mColumnAlignments = new List(ColumnCount); - mColumnWidths = new List(ColumnCount); - mDefaultRowHeight = 22; - mSizeToContents = false; + _cellSpacing = new Point(2, 4); + _columnCount = 1; + _defaultRowHeight = 22; + _sizeToContents = false; + + _columnTextAlignments = new List(_columnCount); + _columnWidths = new List(_columnCount); - for (var i = 0; i < ColumnCount; i++) + while (_columnWidths.Count < _columnCount) { - mColumnWidths.Add(20); + _columnWidths.Add(null); + } + + while (_columnTextAlignments.Count < _columnCount) + { + _columnTextAlignments.Add(Pos.Left | Pos.CenterV); } } - public IReadOnlyList ColumnAlignments => mColumnAlignments.ToList(); + public IReadOnlyList ColumnAlignments + { + get => _columnTextAlignments.ToList(); + set + { + var columnCount = Math.Min(value.Count, _columnTextAlignments.Count); + for (var column = 0; column < columnCount; ++column) + { + _columnTextAlignments[column] = value[column]; + } + + var tableRows = Children.OfType().ToArray(); + foreach (var row in tableRows) + { + row.ColumnTextAlignments = value; + } + } + } /// /// Column count (default 1). /// public int ColumnCount { - get => mColumnCount; - set => SetAndDoIfChanged(ref mColumnCount, value, SetColumnCount); + get => _columnCount; + set => SetAndDoIfChanged(ref _columnCount, value, SetColumnCount); } - public ITableDataProvider DataProvider + public Point CellSpacing { - get => mDataProvider; - set => SetAndDoIfChanged(ref mDataProvider, value, (oldValue, newValue) => + get => _cellSpacing; + set + { + if (value == _cellSpacing) + { + return; + } + + _cellSpacing = value; + var rows = Children.OfType().ToArray(); + foreach (var row in rows) + { + row.Margin = new Margin(0, 0, _cellSpacing.X, _cellSpacing.Y); + } + } + } + + public ITableDataProvider? DataProvider + { + get => _dataProvider; + set => SetAndDoIfChanged(ref _dataProvider, value, (oldValue, newValue) => { if (oldValue != default) { @@ -128,16 +166,28 @@ public Color? TextColorOverride /// public int DefaultRowHeight { - get => mDefaultRowHeight; - set => mDefaultRowHeight = value; + get => _defaultRowHeight; + set => _defaultRowHeight = value; + } + + public IReadOnlyList ColumnWidths + { + get => _columnWidths.ToArray(); + set + { + for (var columnIndex = 0; columnIndex < _columnWidths.Count; ++columnIndex) + { + _columnWidths[columnIndex] = columnIndex < value.Count ? value[columnIndex] : null; + } + } } /// /// Returns specific row of the table. /// - /// Row index. + /// Row index. /// Row at the specified index. - public TableRow this[int index] => Children[index] as TableRow; + public TableRow? this[int row] => Children.OfType().Skip(row).FirstOrDefault(); protected virtual void OnDataChanged(object sender, TableDataChangedEventArgs args) { @@ -153,9 +203,8 @@ protected virtual void OnDataChanged(object sender, TableDataChangedEventArgs ar public override JObject GetJson(bool isRoot = default) { var obj = base.GetJson(isRoot); - obj.Add("SizeToContents", mSizeToContents); - obj.Add("DefaultRowHeight", mDefaultRowHeight); - obj.Add(nameof(ColumnAlignments), new JArray(ColumnAlignments.Select(alignment => alignment.ToString() as object).ToArray())); + obj.Add("SizeToContents", _sizeToContents); + obj.Add("DefaultRowHeight", _defaultRowHeight); obj.Add(nameof(Font), Font?.ToString()); obj.Add(nameof(TextColor), TextColor.ToString()); obj.Add(nameof(TextColorOverride), TextColorOverride.ToString()); @@ -166,28 +215,14 @@ public override JObject GetJson(bool isRoot = default) public override void LoadJson(JToken obj, bool isRoot = default) { base.LoadJson(obj); - if (obj[nameof(SizeToContents)] != null) + if (obj[nameof(FitContents)] != null) { - mSizeToContents = (bool)obj[nameof(SizeToContents)]; + _sizeToContents = (bool)obj[nameof(FitContents)]; } if (obj[nameof(DefaultRowHeight)] != null) { - mDefaultRowHeight = (int)obj[nameof(DefaultRowHeight)]; - } - - if (obj[nameof(ColumnAlignments)] != null) - { - var jColumnAlignments = (JArray)obj[nameof(ColumnAlignments)]; - for (var columnIndex = 0; columnIndex < Math.Min(ColumnCount, jColumnAlignments.Count); columnIndex++) - { - while (mColumnAlignments.Count <= columnIndex) - { - mColumnAlignments.Add(default); - } - - mColumnAlignments[columnIndex] = (Pos)Enum.Parse(typeof(Pos), (string)jColumnAlignments[columnIndex]); - } + _defaultRowHeight = (int)obj[nameof(DefaultRowHeight)]; } if (obj[nameof(Font)] != null) @@ -219,14 +254,22 @@ public override void LoadJson(JToken obj, bool isRoot = default) /// Number of columns. protected virtual void SetColumnCount() { - while (mColumnWidths.Count < ColumnCount) + var columnCount = ColumnCount; + + while (_columnWidths.Count < columnCount) { - mColumnWidths.Add(20); + _columnWidths.Add(null); } - foreach (var row in Children.OfType()) + while (_columnTextAlignments.Count < columnCount) { - row.ColumnCount = Math.Min(row.ColumnCount, ColumnCount); + _columnTextAlignments.Add(Pos.Left | Pos.CenterV); + } + + var rows = Children.OfType().ToArray(); + foreach (var row in rows) + { + row.ColumnCount = Math.Min(row.ColumnCount, columnCount); } } @@ -237,44 +280,45 @@ protected virtual void SetColumnCount() /// Column width. public void SetColumnWidth(int column, int width) { - if (mColumnWidths[column] == width) + if (_columnWidths[column] == width) { return; } - mColumnWidths[column] = width; + _columnWidths[column] = width; Invalidate(); } - protected virtual void SynchronizeColumnAlignments() - { - InvalidateChildren(false); - } - - /// - /// Gets the column width (in pixels). - /// - /// Column index. - /// Column width. - public int GetColumnWidth(int column) - { - return mColumnWidths[column]; - } - /// /// Adds a new empty row. /// /// Newly created row. public TableRow AddRow() => AddRow(ColumnCount); + public TableRow AddRow(params Base[] cellContents) + { + var row = AddRow(); + var columnLimit = Math.Min(cellContents.Length, row.ColumnCount); + for (var columnIndex = 0; columnIndex < columnLimit; ++columnIndex) + { + var control = cellContents[columnIndex]; + row.SetCellContents(columnIndex, control); + } + + return row; + } + public TableRow AddRow(int columnCount) { var row = new TableRow(this) { + CellSpacing = _cellSpacing, ColumnCount = columnCount, + ColumnTextAlignments = _columnTextAlignments, Dock = Pos.Top, Font = Font, - Height = mDefaultRowHeight, + Height = _defaultRowHeight, + Margin = new Margin(0, 0, 0, _cellSpacing.Y), TextColor = TextColor, TextColorOverride = TextColorOverride, }; @@ -295,12 +339,12 @@ public void AddRow(TableRow row) row.Parent = this; - row.ColumnCount = Math.Min(mColumnCount, row.ColumnCount); + row.ColumnCount = Math.Min(_columnCount, row.ColumnCount); row.Dock = Pos.Top; row.Font = row.Font ?? Font; - row.Height = mDefaultRowHeight; + row.Height = _defaultRowHeight; - row.SetColumnWidths(mColumnWidths); + row.SetColumnWidths(_columnWidths); } /// @@ -373,10 +417,10 @@ protected override void Layout(Skin.Base skin) { base.Layout(skin); - if (mSizeToContents) + if (_sizeToContents) { DoSizeToContents(); - mSizeToContents = false; + _sizeToContents = false; } else { @@ -384,12 +428,11 @@ protected override void Layout(Skin.Base skin) } var even = false; - foreach (TableRow row in Children) + var rows = Children.OfType().ToArray(); + foreach (TableRow row in rows) { row.EvenRow = even; even = ClientConfiguration.Instance.EnableZebraStripedRows && !even; - - row.SetColumnWidths(mColumnWidths); } } @@ -398,57 +441,162 @@ protected override void PostLayout(Skin.Base skin) base.PostLayout(skin); } + public bool FitRowHeightToContents + { + get => _fitRowHeightToContents; + set + { + if (value == _fitRowHeightToContents) + { + return; + } + + _fitRowHeightToContents = value; + Invalidate(); + } + } + + public bool SizeToContents + { + get => _sizeToContents; + set + { + if (value == _sizeToContents) + { + return; + } + + _sizeToContents = true; + Invalidate(); + } + } + /// /// Sizes to fit contents. /// - public void SizeToContents(int maxWidth) + public void FitContents(int maxWidth) { - mMaxWidth = maxWidth; - mSizeToContents = true; + MaximumSize = MaximumSize with { X = maxWidth }; + _sizeToContents = true; Invalidate(); } - protected virtual (int width, int height) ComputeColumnWidths() + protected virtual Point ComputeColumnWidths(TableRow[]? rows = null) { - var height = 0; - var width = 0; - - foreach (TableRow row in Children) - { - row.SizeToContents(); // now all columns fit but only in this particular row - - for (var i = 0; i < ColumnCount; i++) - { - Base cell = row.GetColumn(i); - if (null != cell && row.ColumnCount == ColumnCount) + rows ??= Children.OfType().ToArray(); + + var columnCount = ColumnCount; + var measuredContentWidths = rows.Select(row => row.CalculateColumnContentWidths()) + .Aggregate( + new int[columnCount], + (widths, rowWidths) => widths.Select( + (columnWidth, columnIndex) => Math.Max( + columnWidth, + rowWidths.Length > columnIndex ? rowWidths[columnIndex] : 0 + ) + ) + .ToArray() + ); + + var availableWidth = InnerWidth - _cellSpacing.X * Math.Max(0, _columnCount - 1); + + var requestedWidths = _columnWidths.ToArray(); + requestedWidths = measuredContentWidths.Select( + (measuredWidth, columnIndex) => { - if (i < ColumnCount - 1 || mMaxWidth == 0) + if (requestedWidths.Length <= columnIndex) { - mColumnWidths[i] = Math.Max( - mColumnWidths[i], cell.Width + cell.Margin.Left + cell.Margin.Right - ); + return measuredWidth; } - else + + var requestedWidth = requestedWidths[columnIndex]; + if (!requestedWidth.HasValue) { - mColumnWidths[i] = mMaxWidth - width; // last cell - fill + return requestedWidth; } + + return requestedWidth.Value < 0 ? measuredWidth : requestedWidth.Value; + } + ) + .ToArray(); + + int flexColumnWidthSum = requestedWidths.Select( + (requestedWidth, columnIndex) => requestedWidth.HasValue ? 0 : measuredContentWidths[columnIndex] + ) + .Sum(); + var columnWidthRatios = requestedWidths.Select( + (requestedWidth, columnIndex) => + requestedWidth.HasValue ? float.NaN : measuredContentWidths[columnIndex] / (float)flexColumnWidthSum + ) + .ToArray(); + + var fixedColumnCount = requestedWidths.Count(requestedWidth => requestedWidth.HasValue); + var fixedColumnWidthSum = requestedWidths.Sum(requestedWidth => requestedWidth ?? 0); + availableWidth -= fixedColumnWidthSum; + + if (flexColumnWidthSum < availableWidth) + { + var extraSpace = availableWidth - flexColumnWidthSum; + var extraSpacePerColumn = (int)Math.Floor(extraSpace / (float)fixedColumnCount); + + for (var columnIndex = 0; columnIndex < requestedWidths.Length; ++columnIndex) + { + var requestedWidth = requestedWidths[columnIndex]; + if (requestedWidth is not < 1) + { + continue; } - } - height += row.Height; + requestedWidths[columnIndex] += extraSpacePerColumn; + availableWidth -= extraSpacePerColumn; + } } - // sum all column widths - width += mColumnWidths.Take(ColumnCount).Sum(); + var flexColumnWidths = columnWidthRatios.Select( + ratio => float.IsNaN(ratio) ? 0 : (int)MathF.Ceiling(Math.Max(10f / ratio, availableWidth * ratio)) + ) + .ToArray(); + + var actualWidth = 0; + var actualHeight = 0; + var columnLimit = Math.Min(columnCount, requestedWidths.Length); + foreach (var row in rows) + { + var rowWidth = 0; + for (var columnIndex = 0; columnIndex < columnLimit; ++columnIndex) + { + var requestedWidth = requestedWidths[columnIndex]; + var flexColumnWidth = flexColumnWidths[columnIndex]; + var cellWidth = requestedWidth ?? flexColumnWidth; + var cell = row.GetColumn(columnIndex); + if (cell is not null) + { + cell.Width = cellWidth; + } + rowWidth += cellWidth; + } + + actualWidth = Math.Max(actualWidth, rowWidth); + actualHeight += row.OuterHeight; + } - return (width, height); + return new Point(actualWidth, actualHeight); } public void DoSizeToContents() { - var (width, height) = ComputeColumnWidths(); + var rows = Children.OfType().ToArray(); + if (_fitRowHeightToContents) + { + foreach (var row in rows) + { + row.SizeToChildren(width: false, height: true); + } + } + + var size = ComputeColumnWidths(rows); - SetSize(width, height); + SetSize(size.X, size.Y); InvalidateParent(); } @@ -469,4 +617,13 @@ public void Invalidate(bool invalidateChildren, bool invalidateRecursive = true) } } + protected override void OnBoundsChanged(Rectangle oldBounds) + { + if (Bounds.Height == 800) + { + Bounds.ToString(); + } + + base.OnBoundsChanged(oldBounds); + } } diff --git a/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs b/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs index 2fec7f62cb..df46b9a11c 100644 --- a/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs +++ b/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs @@ -1,6 +1,7 @@ using Intersect.Client.Framework.Graphics; using Intersect.Client.Framework.Gwen.Control.Data; using Intersect.Client.Framework.Gwen.Control.EventArguments; +using Intersect.Client.Framework.Gwen.ControlInternal; namespace Intersect.Client.Framework.Gwen.Control.Layout; @@ -11,12 +12,12 @@ namespace Intersect.Client.Framework.Gwen.Control.Layout; public partial class TableRow : Base, IColorableText { private readonly List mDisposalActions; - - private readonly List