diff --git a/Framework/Intersect.Framework/Serialization/JObjectExtensions.cs b/Framework/Intersect.Framework/Serialization/JObjectExtensions.cs new file mode 100644 index 0000000000..4c9db3e6ff --- /dev/null +++ b/Framework/Intersect.Framework/Serialization/JObjectExtensions.cs @@ -0,0 +1,36 @@ +using Newtonsoft.Json.Linq; + +namespace Intersect.Framework.Serialization; + +public static class JObjectExtensions +{ + public static bool TryGet(this JObject @object, string propertyName, out TEnum propertyValue) + where TEnum : struct, Enum => + TryGet(@object, propertyName, default, out propertyValue); + + public static bool TryGet(this JObject @object, string propertyName, TEnum defaultValue, out TEnum propertyValue) + where TEnum : struct, Enum + { + if (!@object.TryGetValue(propertyName, out var token)) + { + propertyValue = defaultValue; + return false; + } + + if (token is not JValue { Type: JTokenType.String } value) + { + propertyValue = defaultValue; + return false; + } + + var rawEnum = value.Value(); + if (Enum.TryParse(rawEnum, out propertyValue)) + { + return true; + } + + propertyValue = defaultValue; + return false; + + } +} \ No newline at end of file diff --git a/Intersect.Client.Core/Core/Input.cs b/Intersect.Client.Core/Core/Input.cs index b4b0906187..1e7a6783fb 100644 --- a/Intersect.Client.Core/Core/Input.cs +++ b/Intersect.Client.Core/Core/Input.cs @@ -82,12 +82,12 @@ public static void OnKeyPressed(Keys modifier, Keys key) return; case Keys.Enter: - - for (int i = Interface.Interface.InputBlockingElements.Count - 1; i >= 0; i--) + for (int i = Interface.Interface.InputBlockingComponents.Count - 1; i >= 0; i--) { + var inputBlockingComponent = Interface.Interface.InputBlockingComponents[i]; try { - if (Interface.Interface.InputBlockingElements[i] is InputBox inputBox && !inputBox.IsHidden) + if (inputBlockingComponent is InputBox { IsHidden: false } inputBox) { inputBox.SubmitInput(); canFocusChat = false; @@ -98,8 +98,7 @@ public static void OnKeyPressed(Keys modifier, Keys key) try { - var eventWindow = (EventWindow)Interface.Interface.InputBlockingElements[i]; - if (eventWindow != null && !eventWindow.IsHidden && Globals.EventDialogs.Count > 0) + if (inputBlockingComponent is EventWindow { IsHidden: false } eventWindow && Globals.EventDialogs.Count > 0) { eventWindow.CloseEventResponse(EventResponseType.OneOption); canFocusChat = false; @@ -109,7 +108,6 @@ public static void OnKeyPressed(Keys modifier, Keys key) } catch { } } - break; } @@ -358,30 +356,30 @@ public static void OnKeyReleased(Keys modifier, Keys key) } } - public static void OnMouseDown(Keys modifier, MouseButtons btn) + public static void OnMouseDown(Keys modifier, MouseButton btn) { var key = Keys.None; switch (btn) { - case MouseButtons.Left: + case MouseButton.Left: key = Keys.LButton; break; - case MouseButtons.Right: + case MouseButton.Right: key = Keys.RButton; break; - case MouseButtons.Middle: + case MouseButton.Middle: key = Keys.MButton; break; - case MouseButtons.X1: + case MouseButton.X1: key = Keys.XButton1; break; - case MouseButtons.X2: + case MouseButton.X2: key = Keys.XButton2; break; @@ -408,7 +406,7 @@ public static void OnMouseDown(Keys modifier, MouseButtons btn) return; } - if (modifier == Keys.None && btn == MouseButtons.Left && Globals.Me.TryTarget()) + if (modifier == Keys.None && btn == MouseButton.Left && Globals.Me.TryTarget()) { return; } @@ -442,25 +440,25 @@ public static void OnMouseDown(Keys modifier, MouseButtons btn) } } - public static void OnMouseUp(Keys modifier, MouseButtons btn) + public static void OnMouseUp(Keys modifier, MouseButton btn) { var key = Keys.LButton; switch (btn) { - case MouseButtons.Right: + case MouseButton.Right: key = Keys.RButton; break; - case MouseButtons.Middle: + case MouseButton.Middle: key = Keys.MButton; break; - case MouseButtons.X1: + case MouseButton.X1: key = Keys.XButton1; break; - case MouseButtons.X2: + case MouseButton.X2: key = Keys.XButton2; break; @@ -487,7 +485,7 @@ public static void OnMouseUp(Keys modifier, MouseButtons btn) return; } - if (btn != MouseButtons.Right) + if (btn != MouseButton.Right) { return; } diff --git a/Intersect.Client.Core/Entities/Player.cs b/Intersect.Client.Core/Entities/Player.cs index e12ed57560..cb600f340d 100644 --- a/Intersect.Client.Core/Entities/Player.cs +++ b/Intersect.Client.Core/Entities/Player.cs @@ -1769,7 +1769,7 @@ private void AutoTurnToTarget(Entity en) private static void ToggleTargetContextMenu(Entity en) { - if (Globals.InputManager.MouseButtonDown(MouseButtons.Right)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Right)) { Interface.Interface.GameUi.TargetContextMenu.ToggleHidden(en); } diff --git a/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs b/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs index 6a0e82d3df..92688cebc3 100644 --- a/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs +++ b/Intersect.Client.Core/Interface/Debugging/DebugWindow.cs @@ -27,19 +27,23 @@ public DebugWindow(Base parent) : base(parent, Strings.Debug.Title, false, nameo _generators = []; DisableResizing(); MinimumSize = new Point(320, 320); + Size = new Point(400, 600); + MaximumSize = new Point(800, 600); + + InnerPanelPadding = new Padding(4); _defaultFont = Current?.GetFont("sourcesansproblack", 10); Tabs = CreateTabs(); - TabInfo = Tabs.AddPage(Strings.Debug.TabLabelInfo); + TabInfo = Tabs.AddPage(Strings.Debug.TabLabelInfo, nameof(TabInfo)); CheckboxDrawDebugOutlines = CreateInfoCheckboxDrawDebugOutlines(TabInfo.Page); CheckboxEnableLayoutHotReloading = CreateInfoCheckboxEnableLayoutHotReloading(TabInfo.Page); ButtonShutdownServer = CreateInfoButtonShutdownServer(TabInfo.Page); ButtonShutdownServerAndExit = CreateInfoButtonShutdownServerAndExit(TabInfo.Page); TableDebugStats = CreateInfoTableDebugStats(TabInfo.Page); - TabAssets = Tabs.AddPage(Strings.Debug.TabLabelAssets); + TabAssets = Tabs.AddPage(Strings.Debug.TabLabelAssets, nameof(TabAssets)); AssetsToolsTable = CreateAssetsToolsTable(TabAssets.Page); AssetsList = CreateAssetsList(TabAssets.Page); AssetsButtonReloadAsset = CreateAssetsButtonReloadAsset(AssetsToolsTable, AssetsList); @@ -52,12 +56,11 @@ public DebugWindow(Base parent) : base(parent, Strings.Debug.Title, false, nameo private SearchableTree CreateAssetsList(Base parent) { var dataProvider = new TexturesSearchableTreeDataProvider(Current, this); - SearchableTree assetList = new(parent, dataProvider) + SearchableTree assetList = new(parent, dataProvider, name: nameof(AssetsList)) { Dock = Pos.Fill, FontSearch = _defaultFont, FontTree = _defaultFont, - Margin = new Margin(0, 4, 0, 0), SearchPlaceholderText = Strings.Debug.AssetsSearchPlaceholder, }; @@ -68,8 +71,13 @@ private Table CreateAssetsToolsTable(Base assetsTabPage) { Table table = new(assetsTabPage, nameof(AssetsToolsTable)) { + AutoSizeToContentHeightOnChildResize = true, + AutoSizeToContentWidthOnChildResize = true, + CellSpacing = new Point(4, 4), ColumnCount = 2, + ColumnWidths = [null, null], Dock = Pos.Top, + SizeToContents = true, }; return table; @@ -77,7 +85,7 @@ private Table CreateAssetsToolsTable(Base assetsTabPage) private Button CreateAssetsButtonReloadAsset(Table table, SearchableTree assetList) { - var row = table.AddRow(); + var row = table.AddNamedRow($"{nameof(AssetsButtonReloadAsset)}Row"); Button buttonReloadAsset = new(row, name: nameof(AssetsButtonReloadAsset)) { @@ -85,6 +93,7 @@ private Button CreateAssetsButtonReloadAsset(Table table, SearchableTree assetLi Font = _defaultFont, Text = Strings.Debug.ReloadAsset, }; + row.SetCellContents(0, buttonReloadAsset); assetList.SelectionChanged += (_, _) => buttonReloadAsset.IsDisabled = assetList.SelectedNodes.All( @@ -143,7 +152,7 @@ private Button CreateAssetsButtonReloadAsset(Table table, SearchableTree assetLi protected override void EnsureInitialized() { - LoadJsonUi(UI.Debug, Graphics.Renderer?.GetResolutionString()); + // LoadJsonUi(UI.Debug, Graphics.Renderer?.GetResolutionString()); foreach (var generator in _generators) { @@ -231,7 +240,7 @@ private LabeledCheckBox CreateInfoCheckboxEnableLayoutHotReloading(Base parent) checkbox.CheckChanged += (sender, args) => Globals.ContentManager.ContentWatcher.Enabled = checkbox.IsChecked; checkbox.SetToolTipText(Strings.Internals.ExperimentalFeatureTooltip); - checkbox.ToolTipFont = Skin.DefaultFont; + checkbox.TooltipFont = Skin.DefaultFont; return checkbox; } @@ -274,23 +283,35 @@ private Button CreateInfoButtonShutdownServerAndExit(Base parent) private Table CreateInfoTableDebugStats(Base parent) { - var table = new Table(parent, nameof(TableDebugStats)) + ScrollControl debugStatsScroller = new(parent, nameof(debugStatsScroller)) { + Dock = Pos.Fill, + }; + + debugStatsScroller.VerticalScrollBar.BaseNudgeAmount *= 2; + + var table = new Table(debugStatsScroller, nameof(TableDebugStats)) + { + AutoSizeToContentHeightOnChildResize = true, + AutoSizeToContentWidthOnChildResize = true, + CellSpacing = new Point(8, 2), ColumnCount = 2, + ColumnWidths = [150, null], Dock = Pos.Fill, Font = _defaultFont, }; + table.BoundsChanged += (_, _) => table.SizeToChildren(width: false, height: true); var fpsProvider = new ValueTableCellDataProvider(() => Graphics.Renderer?.GetFps() ?? 0, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.Fps).Listen(fpsProvider, 1); + table.AddRow(Strings.Debug.Fps, name: "FPSRow").Listen(fpsProvider, 1); _generators.Add(fpsProvider.Generator); var pingProvider = new ValueTableCellDataProvider(() => Networking.Network.Ping, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.Ping).Listen(pingProvider, 1); + table.AddRow(Strings.Debug.Ping, name: "PingRow").Listen(pingProvider, 1); _generators.Add(pingProvider.Generator); var drawCallsProvider = new ValueTableCellDataProvider(() => Graphics.DrawCalls, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.Draws).Listen(drawCallsProvider, 1); + table.AddRow(Strings.Debug.Draws, name: "DrawsRow").Listen(drawCallsProvider, 1); _generators.Add(drawCallsProvider.Generator); var mapNameProvider = new ValueTableCellDataProvider(() => @@ -304,43 +325,43 @@ private Table CreateInfoTableDebugStats(Base parent) return MapInstance.Get(mapId)?.Name ?? Strings.Internals.NotApplicable; }, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.Map).Listen(mapNameProvider, 1); + table.AddRow(Strings.Debug.Map, name: "MapRow").Listen(mapNameProvider, 1); _generators.Add(mapNameProvider.Generator); var coordinateXProvider = new ValueTableCellDataProvider(() => Globals.Me?.X ?? -1, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Internals.CoordinateX).Listen(coordinateXProvider, 1); + table.AddRow(Strings.Internals.CoordinateX, name: "PlayerXRow").Listen(coordinateXProvider, 1); _generators.Add(coordinateXProvider.Generator); var coordinateYProvider = new ValueTableCellDataProvider(() => Globals.Me?.Y ?? -1, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Internals.CoordinateY).Listen(coordinateYProvider, 1); + table.AddRow(Strings.Internals.CoordinateY, name: "PlayerYRow").Listen(coordinateYProvider, 1); _generators.Add(coordinateYProvider.Generator); var coordinateZProvider = new ValueTableCellDataProvider(() => Globals.Me?.Z ?? -1, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Internals.CoordinateZ).Listen(coordinateZProvider, 1); + table.AddRow(Strings.Internals.CoordinateZ, name: "PlayerZRow").Listen(coordinateZProvider, 1); _generators.Add(coordinateZProvider.Generator); var knownEntitiesProvider = new ValueTableCellDataProvider(() => Graphics.DrawCalls, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.KnownEntities).Listen(knownEntitiesProvider, 1); + table.AddRow(Strings.Debug.KnownEntities, name: "KnownEntitiesRow").Listen(knownEntitiesProvider, 1); _generators.Add(knownEntitiesProvider.Generator); var knownMapsProvider = new ValueTableCellDataProvider(() => MapInstance.Lookup.Count, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.KnownMaps).Listen(knownMapsProvider, 1); + table.AddRow(Strings.Debug.KnownMaps, name: "KnownMapsRow").Listen(knownMapsProvider, 1); _generators.Add(knownMapsProvider.Generator); var mapsDrawnProvider = new ValueTableCellDataProvider(() => Graphics.MapsDrawn, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.MapsDrawn).Listen(mapsDrawnProvider, 1); + table.AddRow(Strings.Debug.MapsDrawn, name: "MapsDrawnRow").Listen(mapsDrawnProvider, 1); _generators.Add(mapsDrawnProvider.Generator); var entitiesDrawnProvider = new ValueTableCellDataProvider(() => Graphics.EntitiesDrawn, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.EntitiesDrawn).Listen(entitiesDrawnProvider, 1); + table.AddRow(Strings.Debug.EntitiesDrawn, name: "EntitiesDrawnRow").Listen(entitiesDrawnProvider, 1); _generators.Add(entitiesDrawnProvider.Generator); var lightsDrawnProvider = new ValueTableCellDataProvider(() => Graphics.LightsDrawn, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.LightsDrawn).Listen(lightsDrawnProvider, 1); + table.AddRow(Strings.Debug.LightsDrawn, name: "LightsDrawnRow").Listen(lightsDrawnProvider, 1); _generators.Add(lightsDrawnProvider.Generator); var timeProvider = new ValueTableCellDataProvider(Time.GetTime, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.Time).Listen(timeProvider, 1); + table.AddRow(Strings.Debug.Time, name: "TimeRow").Listen(timeProvider, 1); _generators.Add(timeProvider.Generator); var interfaceObjectsProvider = new ValueTableCellDataProvider(cancellationToken => @@ -361,27 +382,31 @@ private Table CreateInfoTableDebugStats(Base parent) return 0; } }, waitPredicate: () => Task.FromResult(IsVisible)); - table.AddRow(Strings.Debug.InterfaceObjects).Listen(interfaceObjectsProvider, 1); + table.AddRow(Strings.Debug.InterfaceObjects, name: "InterfaceObjectsRow").Listen(interfaceObjectsProvider, 1); _generators.Add(interfaceObjectsProvider.Generator); - _ = table.AddRow(Strings.Debug.ControlUnderCursor, 2); + _ = table.AddRow(Strings.Debug.ControlUnderCursor, columnCount: 2, name: "ControlUnderCursorRow"); var controlUnderCursorProvider = new ControlUnderCursorProvider(this); // var controlUnderCursorProvider = new ValueTableCellDataProvider( // Interface.FindControlAtCursor, // waitPredicate: () => Task.FromResult(IsVisible) // ); - table.AddRow(Strings.Internals.Type).Listen(controlUnderCursorProvider, 0); - table.AddRow(Strings.Internals.Name).Listen(controlUnderCursorProvider, 1); - table.AddRow(Strings.Internals.LocalItem.ToString(Strings.Internals.Bounds)).Listen(controlUnderCursorProvider, 2); - table.AddRow(Strings.Internals.GlobalItem.ToString(Strings.Internals.Bounds)).Listen(controlUnderCursorProvider, 3); - table.AddRow(Strings.Internals.Color).Listen(controlUnderCursorProvider, 4); - table.AddRow(Strings.Internals.ColorOverride).Listen(controlUnderCursorProvider, 5); - table.AddRow(Strings.Internals.InnerBounds).Listen(controlUnderCursorProvider, 6); - table.AddRow(Strings.Internals.Margin).Listen(controlUnderCursorProvider, 7); - table.AddRow(Strings.Internals.Padding).Listen(controlUnderCursorProvider, 8); - // table.AddRow(Strings.Internals.ColorOverride).Listen(controlUnderCursorProvider, 9); - // table.AddRow(Strings.Internals.ColorOverride).Listen(controlUnderCursorProvider, 10); + table.AddRow(Strings.Internals.Type, name: "TypeRow").Listen(controlUnderCursorProvider, 0); + table.AddRow(Strings.Internals.Name, name: "NameRow").Listen(controlUnderCursorProvider, 1); + table.AddRow(Strings.Internals.LocalItem.ToString(Strings.Internals.Bounds), name: "LocalBoundsRow").Listen(controlUnderCursorProvider, 2); + table.AddRow(Strings.Internals.GlobalItem.ToString(Strings.Internals.Bounds), name: "GlobalBoundsRow").Listen(controlUnderCursorProvider, 3); + table.AddRow(Strings.Internals.Color, name: "ColorRow").Listen(controlUnderCursorProvider, 4); + table.AddRow(Strings.Internals.ColorOverride, name: "ColorOverrideRow").Listen(controlUnderCursorProvider, 5); + table.AddRow(Strings.Internals.InnerBounds, name: "InnerBoundsRow").Listen(controlUnderCursorProvider, 6); + table.AddRow(Strings.Internals.Margin, name: "MarginRow").Listen(controlUnderCursorProvider, 7); + table.AddRow(Strings.Internals.Padding, name: "PaddingRow").Listen(controlUnderCursorProvider, 8); + table.AddRow(Strings.Internals.Dock, name: "Dock").Listen(controlUnderCursorProvider, 9); + table.AddRow(Strings.Internals.Alignment, name: "Alignment").Listen(controlUnderCursorProvider, 10); + table.AddRow(Strings.Internals.TextAlign, name: "TextAlign").Listen(controlUnderCursorProvider, 11); + table.AddRow(Strings.Internals.TextPadding, name: "TextPadding").Listen(controlUnderCursorProvider, 12); + table.AddRow(Strings.Internals.Font, name: "Font").Listen(controlUnderCursorProvider, 13); + table.AddRow(Strings.Internals.AutoSizeToContents, name: "AutoSizeToContents").Listen(controlUnderCursorProvider, 14); _generators.Add(controlUnderCursorProvider.Generator); var rows = table.Children.OfType().ToArray(); @@ -393,6 +418,8 @@ private Table CreateInfoTableDebugStats(Base parent) } } + table.SizeToChildren(); + return table; } @@ -403,19 +430,19 @@ private partial class ControlUnderCursorProvider : ITableDataProvider public ControlUnderCursorProvider(Base owner) { _owner = owner; - Generator = new CancellableGenerator(CreateControlUnderCursorGenerator); + Generator = new CancellableGenerator(CreateControlUnderCursorGenerator); } public event TableDataChangedEventHandler? DataChanged; - public CancellableGenerator Generator { get; } + public CancellableGenerator Generator { get; } public void Start() { _ = Generator; } - private AsyncValueGenerator CreateControlUnderCursorGenerator(CancellationToken cancellationToken) => new( + private AsyncValueGenerator CreateControlUnderCursorGenerator(CancellationToken cancellationToken) => new( () => WaitForOwnerVisible(cancellationToken).ContinueWith(CreateValue, TaskScheduler.Current), HandleValue, cancellationToken @@ -438,10 +465,11 @@ private async Task WaitForOwnerVisible(CancellationToken cancellationToken) cancellationToken.ThrowIfCancellationRequested(); } - private static Base CreateValue(Task _) => Interface.FindControlAtCursor(); + private static Base? CreateValue(Task _) => + Interface.FindComponentUnderCursor(ComponentStateFilters.IncludeMouseInputDisabled); - private void HandleValue(Base component) - { + private void HandleValue(Base? component) + { DataChanged?.Invoke( this, new TableDataChangedEventArgs( @@ -493,6 +521,32 @@ private void HandleValue(Base component) this, new TableDataChangedEventArgs(8, 1, default, component?.Padding.ToString() ?? string.Empty) ); + DataChanged?.Invoke( + this, + new TableDataChangedEventArgs(9, 1, default, component?.Dock.ToString() ?? string.Empty) + ); + DataChanged?.Invoke( + this, + new TableDataChangedEventArgs(10, 1, default, string.Join(", ", component?.Alignment ?? [])) + ); + + var label = component as Label; + DataChanged?.Invoke( + this, + new TableDataChangedEventArgs(11, 1, default, label?.TextAlign.ToString() ?? string.Empty) + ); + DataChanged?.Invoke( + this, + new TableDataChangedEventArgs(12, 1, default, label?.TextPadding.ToString() ?? string.Empty) + ); + DataChanged?.Invoke( + this, + new TableDataChangedEventArgs(13, 1, default, label?.Font?.ToString() ?? string.Empty) + ); + DataChanged?.Invoke( + this, + new TableDataChangedEventArgs(14, 1, default, (component as IAutoSizeToContents)?.AutoSizeToContents.ToString() ?? string.Empty) + ); } } } diff --git a/Intersect.Client.Core/Interface/Debugging/TexturesSearchableTreeDataProvider.cs b/Intersect.Client.Core/Interface/Debugging/TexturesSearchableTreeDataProvider.cs index 4f32458b56..858daecfad 100644 --- a/Intersect.Client.Core/Interface/Debugging/TexturesSearchableTreeDataProvider.cs +++ b/Intersect.Client.Core/Interface/Debugging/TexturesSearchableTreeDataProvider.cs @@ -56,7 +56,7 @@ public TexturesSearchableTreeDataProvider(GameContentManager contentManager, Bas new( Id: contentTypeId, DisplayText: contentTypeName, - DisplayColor: parent.Skin.Colors.Label.Default + DisplayColor: parent.Skin.Colors.Label.Normal ), ..assets.Select(asset => EntryForAsset(contentTypeId, asset)), ]; diff --git a/Intersect.Client.Core/Interface/Game/Admin/AdminWindow.cs b/Intersect.Client.Core/Interface/Game/Admin/AdminWindow.cs index 5a501fb0ec..4b18d4b48f 100644 --- a/Intersect.Client.Core/Interface/Game/Admin/AdminWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Admin/AdminWindow.cs @@ -25,7 +25,7 @@ public partial class AdminWindow : WindowControl public ImagePanel _spritePanel; private readonly ComboBox _dropdownFace; public ImagePanel _facePanel; - private readonly CheckBox _checkboxChronological; + private readonly Checkbox _checkboxChronological; private TreeControl? _mapList; public AdminWindow(Base gameCanvas) : base( @@ -46,7 +46,7 @@ public AdminWindow(Base gameCanvas) : base( Text = Strings.Admin.Name, }; _textboxName = new TextBox(this, "TextboxName"); - Interface.FocusElements.Add(_textboxName); + Interface.FocusComponents.Add(_textboxName); // Access label, dropdown and set power button _ = new Label(this, "LabelAccess") @@ -261,7 +261,7 @@ public AdminWindow(Base gameCanvas) : base( { Text = Strings.Admin.MapList, }; - _checkboxChronological = new CheckBox(this, "CheckboxChronological"); + _checkboxChronological = new Checkbox(this, "CheckboxChronological"); _checkboxChronological.SetToolTipText(Strings.Admin.ChronologicalTip); _checkboxChronological.CheckChanged += (s, e) => UpdateMapList(); _ = new Label(this, "LabelChronological") @@ -336,7 +336,7 @@ private void AddMapListToTree(MapList mapList, TreeNode? parent) #region Action Handlers - private void banButton_Clicked(Base sender, ClickedEventArgs arguments) + private void banButton_Clicked(Base sender, MouseButtonState arguments) { if (string.IsNullOrWhiteSpace(_textboxName.Text)) { @@ -368,7 +368,7 @@ private void banButton_Clicked(Base sender, ClickedEventArgs arguments) ); } - private void muteButton_Clicked(Base sender, ClickedEventArgs arguments) + private void muteButton_Clicked(Base sender, MouseButtonState arguments) { if (string.IsNullOrWhiteSpace(_textboxName.Text)) { diff --git a/Intersect.Client.Core/Interface/Game/Admin/BanMuteBox.cs b/Intersect.Client.Core/Interface/Game/Admin/BanMuteBox.cs index 4432c2018d..fd95ca360a 100644 --- a/Intersect.Client.Core/Interface/Game/Admin/BanMuteBox.cs +++ b/Intersect.Client.Core/Interface/Game/Admin/BanMuteBox.cs @@ -59,7 +59,7 @@ public BanMuteBox(string title, string prompt, EventHandler okayHandler) : base( ) { DisableResizing(); - Interface.InputBlockingElements.Add(this); + Interface.InputBlockingComponents.Add(this); // Prompt label var promptContainer = new ScrollControl(this, "PromptContainer"); @@ -74,7 +74,7 @@ public BanMuteBox(string title, string prompt, EventHandler okayHandler) : base( // Reason textbox _textboxReason = new TextBox(this, "TextboxReason"); - Interface.FocusElements.Add(_textboxReason); + Interface.FocusComponents.Add(_textboxReason); // Duration label _ = new Label(this, "LabelDuration") diff --git a/Intersect.Client.Core/Interface/Game/Bag/BagItem.cs b/Intersect.Client.Core/Interface/Game/Bag/BagItem.cs index 7420d7143b..c51c786479 100644 --- a/Intersect.Client.Core/Interface/Game/Bag/BagItem.cs +++ b/Intersect.Client.Core/Interface/Game/Bag/BagItem.cs @@ -61,24 +61,11 @@ public void Setup() Pnl = new ImagePanel(Container, "BagItemIcon"); Pnl.HoverEnter += pnl_HoverEnter; Pnl.HoverLeave += pnl_HoverLeave; - Pnl.RightClicked += Pnl_RightClicked; Pnl.DoubleClicked += Pnl_DoubleClicked; Pnl.Clicked += pnl_Clicked; } - private void Pnl_RightClicked(Base sender, ClickedEventArgs arguments) - { - if (ClientConfiguration.Instance.EnableContextMenus) - { - mBagWindow.OpenContextMenu(mMySlot); - } - else - { - Pnl_DoubleClicked(sender, arguments); - } - } - - private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_DoubleClicked(Base sender, MouseButtonState arguments) { if (Globals.InBag) { @@ -86,9 +73,26 @@ private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) } } - void pnl_Clicked(Base sender, ClickedEventArgs arguments) + void pnl_Clicked(Base sender, MouseButtonState arguments) { - mClickTime = Timing.Global.MillisecondsUtc + 500; + // ReSharper disable once SwitchStatementMissingSomeEnumCasesNoDefault + switch (arguments.MouseButton) + { + case MouseButton.Right: + if (ClientConfiguration.Instance.EnableContextMenus) + { + mBagWindow.OpenContextMenu(mMySlot); + } + else + { + Pnl_DoubleClicked(sender, arguments); + } + break; + + case MouseButton.Left: + mClickTime = Timing.Global.MillisecondsUtc + 500; + break; + } } void pnl_HoverLeave(Base sender, EventArgs arguments) @@ -112,7 +116,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) mMouseOver = true; mCanDrag = true; - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = false; @@ -182,7 +186,7 @@ public void Update() { if (mMouseOver) { - if (!Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (!Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = true; mMouseX = -1; diff --git a/Intersect.Client.Core/Interface/Game/Bag/BagWindow.cs b/Intersect.Client.Core/Interface/Game/Bag/BagWindow.cs index 6b40e8dbc7..311a98b655 100644 --- a/Intersect.Client.Core/Interface/Game/Bag/BagWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Bag/BagWindow.cs @@ -35,7 +35,7 @@ public BagWindow(Canvas gameCanvas) { mBagWindow = new WindowControl(gameCanvas, Strings.Bags.Title, false, "BagWindow"); mBagWindow.DisableResizing(); - Interface.InputBlockingElements.Add(mBagWindow); + Interface.InputBlockingComponents.Add(mBagWindow); mItemContainer = new ScrollControl(mBagWindow, "ItemContainer"); mItemContainer.EnableScroll(false, true); @@ -74,7 +74,7 @@ public void OpenContextMenu(int slot) mContextMenu.Open(Framework.Gwen.Pos.None); } - private void MWithdrawContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MWithdrawContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { if (Globals.InBag) { diff --git a/Intersect.Client.Core/Interface/Game/Bank/BankItem.cs b/Intersect.Client.Core/Interface/Game/Bank/BankItem.cs index 1b6f30e79b..8daa132030 100644 --- a/Intersect.Client.Core/Interface/Game/Bank/BankItem.cs +++ b/Intersect.Client.Core/Interface/Game/Bank/BankItem.cs @@ -64,24 +64,11 @@ public void Setup() Pnl = new ImagePanel(Container, "BankItemIcon"); Pnl.HoverEnter += pnl_HoverEnter; Pnl.HoverLeave += pnl_HoverLeave; - Pnl.RightClicked += Pnl_RightClicked; Pnl.DoubleClicked += Pnl_DoubleClicked; Pnl.Clicked += pnl_Clicked; } - private void Pnl_RightClicked(Base sender, ClickedEventArgs arguments) - { - if (ClientConfiguration.Instance.EnableContextMenus) - { - mBankWindow.OpenContextMenu(mMySlot); - } - else - { - Pnl_DoubleClicked(sender, arguments); - } - } - - private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_DoubleClicked(Base sender, MouseButtonState arguments) { if (Globals.InBank) { @@ -105,9 +92,25 @@ private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) } } - void pnl_Clicked(Base sender, ClickedEventArgs arguments) + void pnl_Clicked(Base sender, MouseButtonState arguments) { - mClickTime = Timing.Global.MillisecondsUtc + 500; + switch (arguments.MouseButton) + { + case MouseButton.Left: + mClickTime = Timing.Global.MillisecondsUtc + 500; + break; + + case MouseButton.Right: + if (ClientConfiguration.Instance.EnableContextMenus) + { + mBankWindow.OpenContextMenu(mMySlot); + } + else + { + Pnl_DoubleClicked(sender, arguments); + } + break; + } } void pnl_HoverLeave(Base sender, EventArgs arguments) @@ -131,7 +134,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) mMouseOver = true; mCanDrag = true; - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = false; @@ -201,7 +204,7 @@ public void Update() { if (mMouseOver) { - if (!Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (!Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = true; mMouseX = -1; diff --git a/Intersect.Client.Core/Interface/Game/Bank/BankWindow.cs b/Intersect.Client.Core/Interface/Game/Bank/BankWindow.cs index 5b3866917c..a41239cffa 100644 --- a/Intersect.Client.Core/Interface/Game/Bank/BankWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Bank/BankWindow.cs @@ -49,7 +49,7 @@ public BankWindow(Canvas gameCanvas) // Disable resizing and add to the list of input-blocking elements. mBankWindow.DisableResizing(); - Interface.InputBlockingElements.Add(mBankWindow); + Interface.InputBlockingComponents.Add(mBankWindow); // Create a new scroll control for the items in the bank. mItemContainer = new ScrollControl(mBankWindow, "ItemContainer"); @@ -93,7 +93,7 @@ public void OpenContextMenu(int slot) mContextMenu.Open(Framework.Gwen.Pos.None); } - private void MWithdrawContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MWithdrawContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { var slot = (int)sender.Parent.UserData; Globals.Me.TryWithdrawItem(slot); diff --git a/Intersect.Client.Core/Interface/Game/Character/CharacterWindow.cs b/Intersect.Client.Core/Interface/Game/Character/CharacterWindow.cs index cc003d1fd1..a1493a0817 100644 --- a/Intersect.Client.Core/Interface/Game/Character/CharacterWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Character/CharacterWindow.cs @@ -112,7 +112,7 @@ public CharacterWindow(Canvas gameCanvas) mCharacterWindow.DisableResizing(); mCharacterName = new Label(mCharacterWindow, "CharacterNameLabel"); - mCharacterName.SetTextColor(Color.White, Label.ControlState.Normal); + mCharacterName.SetTextColor(Color.White, ComponentState.Normal); mCharacterLevelAndClass = new Label(mCharacterWindow, "ChatacterInfoLabel"); mCharacterLevelAndClass.SetText(""); @@ -183,27 +183,27 @@ public CharacterWindow(Canvas gameCanvas) } //Update Button Event Handlers - void _addMagicResistBtn_Clicked(Base sender, ClickedEventArgs arguments) + void _addMagicResistBtn_Clicked(Base sender, MouseButtonState arguments) { PacketSender.SendUpgradeStat((int) Stat.MagicResist); } - void _addAbilityPwrBtn_Clicked(Base sender, ClickedEventArgs arguments) + void _addAbilityPwrBtn_Clicked(Base sender, MouseButtonState arguments) { PacketSender.SendUpgradeStat((int) Stat.AbilityPower); } - void _addSpeedBtn_Clicked(Base sender, ClickedEventArgs arguments) + void _addSpeedBtn_Clicked(Base sender, MouseButtonState arguments) { PacketSender.SendUpgradeStat((int) Stat.Speed); } - void _addDefenseBtn_Clicked(Base sender, ClickedEventArgs arguments) + void _addDefenseBtn_Clicked(Base sender, MouseButtonState arguments) { PacketSender.SendUpgradeStat((int) Stat.Defense); } - void _addAttackBtn_Clicked(Base sender, ClickedEventArgs arguments) + void _addAttackBtn_Clicked(Base sender, MouseButtonState arguments) { PacketSender.SendUpgradeStat((int) Stat.Attack); } diff --git a/Intersect.Client.Core/Interface/Game/Character/EquipmentItem.cs b/Intersect.Client.Core/Interface/Game/Character/EquipmentItem.cs index a5ce0521d3..a4e7c1e3ad 100644 --- a/Intersect.Client.Core/Interface/Game/Character/EquipmentItem.cs +++ b/Intersect.Client.Core/Interface/Game/Character/EquipmentItem.cs @@ -42,15 +42,20 @@ public void Setup() { Pnl.HoverEnter += pnl_HoverEnter; Pnl.HoverLeave += pnl_HoverLeave; - Pnl.RightClicked += pnl_RightClicked; + Pnl.Clicked += pnl_RightClicked; ContentPanel = new ImagePanel(Pnl, "EquipmentIcon"); ContentPanel.MouseInputEnabled = false; Pnl.SetToolTipText(Options.Instance.Equipment.Slots[mYindex]); } - void pnl_RightClicked(Base sender, ClickedEventArgs arguments) + void pnl_RightClicked(Base sender, MouseButtonState arguments) { + if (arguments.MouseButton != MouseButton.Right) + { + return; + } + if (ClientConfiguration.Instance.EnableContextMenus) { var window = Interface.GameUi.GameMenu.GetInventoryWindow(); @@ -67,7 +72,7 @@ void pnl_RightClicked(Base sender, ClickedEventArgs arguments) { PacketSender.SendUnequipItem(mYindex); } - + } void pnl_HoverLeave(Base sender, EventArgs arguments) @@ -86,7 +91,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) return; } - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { return; } diff --git a/Intersect.Client.Core/Interface/Game/Chat/Chatbox.cs b/Intersect.Client.Core/Interface/Game/Chat/Chatbox.cs index 33995c9257..e480440c1f 100644 --- a/Intersect.Client.Core/Interface/Game/Chat/Chatbox.cs +++ b/Intersect.Client.Core/Interface/Game/Chat/Chatbox.cs @@ -126,7 +126,7 @@ public Chatbox(Canvas gameCanvas, GameInterface gameUi) mChatboxInput.Clicked += ChatboxInput_Clicked; mChatboxInput.IsTabable = false; mChatboxInput.SetMaxLength(Options.Instance.Chat.MaxChatLength); - Interface.FocusElements.Add(mChatboxInput); + Interface.FocusComponents.Add(mChatboxInput); mChannelLabel = new Label(mChatboxWindow, "ChannelLabel"); mChannelLabel.Text = Strings.Chatbox.Channel; @@ -242,25 +242,25 @@ public void OpenContextMenu(string name) mContextMenu.Open(Framework.Gwen.Pos.None); } - private void MGuildInviteContextItem_Clicked(Base sender, ClickedEventArgs arguments) + private void MGuildInviteContextItem_Clicked(Base sender, MouseButtonState arguments) { var name = (string)sender.Parent.UserData; PacketSender.SendInviteGuild(name); } - private void MPartyInviteContextItem_Clicked(Base sender, ClickedEventArgs arguments) + private void MPartyInviteContextItem_Clicked(Base sender, MouseButtonState arguments) { var name = (string)sender.Parent.UserData; PacketSender.SendPartyInvite(name); } - private void MFriendInviteContextItem_Clicked(Base sender, ClickedEventArgs arguments) + private void MFriendInviteContextItem_Clicked(Base sender, MouseButtonState arguments) { var name = (string)sender.Parent.UserData; PacketSender.SendAddFriend(name); } - private void MPMContextItem_Clicked(Base sender, ClickedEventArgs arguments) + private void MPMContextItem_Clicked(Base sender, MouseButtonState arguments) { var name = (string)sender.Parent.UserData; SetChatboxText($"/pm {name} "); @@ -295,7 +295,7 @@ private void EnableChatTabs() /// /// The button that was clicked. /// The arguments passed by the event. - private void TabButtonClicked(Base sender, ClickedEventArgs arguments) + private void TabButtonClicked(Base sender, MouseButtonState arguments) { // Enable all buttons again! EnableChatTabs(); @@ -381,7 +381,6 @@ public void Update() row.ShouldDrawBackground = false; row.UserData = msg.Target; row.Clicked += ChatboxRow_Clicked; - row.RightClicked += ChatboxRow_RightClicked; mReceivedMessage = true; while (mChatboxMessages.RowCount > ClientConfiguration.Instance.ChatLines) @@ -396,29 +395,12 @@ public void Update() // ReSharper disable once InvertIf if (mReceivedMessage) { - mChatboxMessages.InnerPanel.SizeToChildren(false, true); - mChatboxMessages.UpdateScrollBars(); - vScrollBar.SetScrollAmount(scrollToBottom ? 1 : scrollAmount); - mReceivedMessage = false; - } - } - - private void ChatboxRow_RightClicked(Base sender, ClickedEventArgs arguments) - { - var rw = (ListBoxRow)sender; - var target = (string)rw.UserData; - - if (!string.IsNullOrWhiteSpace(target) && target != Globals.Me.Name) - { - if (ClientConfiguration.Instance.EnableContextMenus) - { - OpenContextMenu(target); - } - else + if (scrollToBottom) { - SetChatboxText($"/pm {target} "); + mChatboxMessages.ScrollToBottom(); } - + // vScrollBar.SetScrollAmount(scrollToBottom ? 1 : scrollAmount); + mReceivedMessage = false; } } @@ -430,10 +412,39 @@ public void SetChatboxText(string msg) mChatboxInput.CursorPos = mChatboxInput.Text.Length; } - private void ChatboxRow_Clicked(Base sender, ClickedEventArgs arguments) + private void ChatboxRow_Clicked(Base sender, MouseButtonState arguments) { - var rw = (ListBoxRow)sender; - var target = (string)rw.UserData; + if (sender is not ListBoxRow row) + { + return; + } + + if (row.UserData is not string target || string.IsNullOrWhiteSpace(target)) + { + return; + } + + switch (arguments.MouseButton) + { + case MouseButton.Left: + if (mGameUi.IsAdminWindowOpen) + { + mGameUi.AdminWindowSelectName(target); + } + break; + + case MouseButton.Right: + if (ClientConfiguration.Instance.EnableContextMenus) + { + OpenContextMenu(target); + } + else + { + SetChatboxText($"/pm {target} "); + } + break; + } + if (!string.IsNullOrWhiteSpace(target)) { if (mGameUi.IsAdminWindowOpen) @@ -475,7 +486,7 @@ public void UnFocus() //Input Handlers //Chatbox Window - void ChatboxInput_Clicked(Base sender, ClickedEventArgs arguments) + void ChatboxInput_Clicked(Base sender, MouseButtonState arguments) { if (mChatboxInput.Text == GetDefaultInputText()) { @@ -488,12 +499,12 @@ void ChatboxInput_SubmitPressed(Base sender, EventArgs arguments) TrySendMessage(); } - void ChatboxSendBtn_Clicked(Base sender, ClickedEventArgs arguments) + void ChatboxSendBtn_Clicked(Base sender, MouseButtonState arguments) { TrySendMessage(); } - void ChatboxClearLogBtn_Clicked(Base sender, ClickedEventArgs arguments) + void ChatboxClearLogBtn_Clicked(Base sender, MouseButtonState arguments) { ChatboxMsg.ClearMessages(); mChatboxMessages.Clear(); @@ -503,7 +514,7 @@ void ChatboxClearLogBtn_Clicked(Base sender, ClickedEventArgs arguments) mLastTab = mCurrentTab; } - void ChatboxToggleLogBtn_Clicked(Base sender, ClickedEventArgs arguments) + void ChatboxToggleLogBtn_Clicked(Base sender, MouseButtonState arguments) { if (mChatboxWindow.Texture != null) { diff --git a/Intersect.Client.Core/Interface/Game/Crafting/CraftingWindow.cs b/Intersect.Client.Core/Interface/Game/Crafting/CraftingWindow.cs index 5b72ddf5a4..8d7b6f8a5c 100644 --- a/Intersect.Client.Core/Interface/Game/Crafting/CraftingWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Crafting/CraftingWindow.cs @@ -123,7 +123,7 @@ public CraftingWindow(Canvas gameCanvas, bool journalMode) mCraftAll.Hide(); } - Interface.InputBlockingElements.Add(mCraftWindow); + Interface.InputBlockingComponents.Add(mCraftWindow); Globals.Me.InventoryUpdatedDelegate = () => { @@ -328,7 +328,7 @@ public void Hide() } //Load new recepie - void tmpNode_DoubleClicked(Base sender, ClickedEventArgs arguments) + void tmpNode_DoubleClicked(Base sender, MouseButtonState arguments) { if (IsCrafting == false) { @@ -414,10 +414,10 @@ void DoCraft(int count) } //Craft the item - void craft_Clicked(Base sender, ClickedEventArgs arguments) => DoCraft(1); + void craft_Clicked(Base sender, MouseButtonState arguments) => DoCraft(1); //Craft all the items - void craftAll_Clicked(Base sender, ClickedEventArgs arguments) + void craftAll_Clicked(Base sender, MouseButtonState arguments) { if (CanCraft()) { diff --git a/Intersect.Client.Core/Interface/Game/Crafting/RecipeItem.cs b/Intersect.Client.Core/Interface/Game/Crafting/RecipeItem.cs index bc7a44f080..6c9ba1d434 100644 --- a/Intersect.Client.Core/Interface/Game/Crafting/RecipeItem.cs +++ b/Intersect.Client.Core/Interface/Game/Crafting/RecipeItem.cs @@ -101,7 +101,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) mMouseOver = true; mCanDrag = true; - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = false; diff --git a/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/HeaderComponent.cs b/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/HeaderComponent.cs index e0ccae40cb..0fadf052c9 100644 --- a/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/HeaderComponent.cs +++ b/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/HeaderComponent.cs @@ -53,7 +53,7 @@ public void SetIcon(GameTexture texture, Color color) public void SetTitle(string title, Color color) { mTitle.SetText(title); - mTitle.SetTextColor(color, Label.ControlState.Normal); + mTitle.SetTextColor(color, ComponentState.Normal); } /// @@ -64,7 +64,7 @@ public void SetTitle(string title, Color color) public void SetSubtitle(string subtitle, Color color) { mSubtitle.SetText(subtitle); - mSubtitle.SetTextColor(color, Label.ControlState.Normal); + mSubtitle.SetTextColor(color, ComponentState.Normal); } /// @@ -75,6 +75,6 @@ public void SetSubtitle(string subtitle, Color color) public void SetDescription(string description, Color color) { mDescription.SetText(description); - mDescription.SetTextColor(color, Label.ControlState.Normal); + mDescription.SetTextColor(color, ComponentState.Normal); } } \ No newline at end of file diff --git a/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/RowContainerComponent.cs b/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/RowContainerComponent.cs index 5d47492982..121f1206b4 100644 --- a/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/RowContainerComponent.cs +++ b/Intersect.Client.Core/Interface/Game/DescriptionWindows/Components/RowContainerComponent.cs @@ -108,13 +108,13 @@ protected override void GenerateComponents() /// Set the of the key text. /// /// The to draw the key text in. - public void SetKeyTextColor(Color color) => mKeyLabel.SetTextColor(color, Label.ControlState.Normal); + public void SetKeyTextColor(Color color) => mKeyLabel.SetTextColor(color, ComponentState.Normal); /// /// Set the of the value text. /// /// The to draw the value text in. - public void SetValueTextColor(Color color) => mValueLabel.SetTextColor(color, Label.ControlState.Normal); + public void SetValueTextColor(Color color) => mValueLabel.SetTextColor(color, ComponentState.Normal); /// /// Get the Json layout of the current component. diff --git a/Intersect.Client.Core/Interface/Game/Draggable.cs b/Intersect.Client.Core/Interface/Game/Draggable.cs index 056d49a5ca..45c99a3c1b 100644 --- a/Intersect.Client.Core/Interface/Game/Draggable.cs +++ b/Intersect.Client.Core/Interface/Game/Draggable.cs @@ -47,7 +47,7 @@ public bool Update() InputHandler.MousePosition.X - mPnl.Width / 2, InputHandler.MousePosition.Y - mPnl.Height / 2 ); - if (!Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (!Globals.InputManager.MouseButtonDown(MouseButton.Left)) { return true; } diff --git a/Intersect.Client.Core/Interface/Game/EscapeMenu.cs b/Intersect.Client.Core/Interface/Game/EscapeMenu.cs index de36e5172f..33ef92ef5a 100644 --- a/Intersect.Client.Core/Interface/Game/EscapeMenu.cs +++ b/Intersect.Client.Core/Interface/Game/EscapeMenu.cs @@ -16,7 +16,7 @@ public partial class EscapeMenu : ImagePanel public EscapeMenu(Canvas gameCanvas) : base(gameCanvas, nameof(EscapeMenu)) { - Interface.InputBlockingElements?.Add(this); + Interface.InputBlockingComponents?.Add(this); Width = gameCanvas.Width; Height = gameCanvas.Height; @@ -31,7 +31,10 @@ public EscapeMenu(Canvas gameCanvas) : base(gameCanvas, nameof(EscapeMenu)) }; // Settings Window and Button - _settingsWindow = new SettingsWindow(gameCanvas, null, this); + _settingsWindow = new SettingsWindow(gameCanvas, null, this) + { + IsVisible = false, + }; var buttonSettings = new Button(container, "SettingsButton") { @@ -120,7 +123,7 @@ public void OpenSettingsWindow(bool returnToMenu = false) Interface.GameUi?.EscapeMenu?.Hide(); } - private void _buttonCharacterSelect_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonCharacterSelect_Clicked(Base sender, MouseButtonState arguments) { ToggleHidden(); if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) @@ -149,7 +152,7 @@ private void LogoutToCharacterSelect(object? sender, EventArgs? e) Main.Logout(true); } - private void buttonLogout_Clicked(Base sender, ClickedEventArgs arguments) + private void buttonLogout_Clicked(Base sender, MouseButtonState arguments) { ToggleHidden(); if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) @@ -178,7 +181,7 @@ private void LogoutToMainMenu(object? sender, EventArgs? e) Main.Logout(false); } - private void buttonQuit_Clicked(Base sender, ClickedEventArgs arguments) + private void buttonQuit_Clicked(Base sender, MouseButtonState arguments) { ToggleHidden(); if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) diff --git a/Intersect.Client.Core/Interface/Game/EventWindow.cs b/Intersect.Client.Core/Interface/Game/EventWindow.cs index 15fe87aa89..91abe9251b 100644 --- a/Intersect.Client.Core/Interface/Game/EventWindow.cs +++ b/Intersect.Client.Core/Interface/Game/EventWindow.cs @@ -73,13 +73,13 @@ public void Update() { if (IsHidden) { - _ = Interface.InputBlockingElements.Remove(this); + _ = Interface.InputBlockingComponents.Remove(this); } else { - if (!Interface.InputBlockingElements.Contains(this)) + if (!Interface.InputBlockingComponents.Contains(this)) { - Interface.InputBlockingElements.Add(this); + Interface.InputBlockingComponents.Add(this); } } diff --git a/Intersect.Client.Core/Interface/Game/Friends/FriendsRow.cs b/Intersect.Client.Core/Interface/Game/Friends/FriendsRow.cs index 6efb71e97e..20dfc360b0 100644 --- a/Intersect.Client.Core/Interface/Game/Friends/FriendsRow.cs +++ b/Intersect.Client.Core/Interface/Game/Friends/FriendsRow.cs @@ -87,7 +87,7 @@ private void UpdateControls() } - private void MRemove_Clicked(Base sender, ClickedEventArgs arguments) + private void MRemove_Clicked(Base sender, MouseButtonState arguments) { var iBox = new InputBox( title: Strings.Friends.RemoveFriend, @@ -97,7 +97,7 @@ private void MRemove_Clicked(Base sender, ClickedEventArgs arguments) ); } - private void MTell_Clicked(Base sender, ClickedEventArgs arguments) + private void MTell_Clicked(Base sender, MouseButtonState arguments) { Interface.GameUi.SetChatboxText($"/pm {mMyName} "); } diff --git a/Intersect.Client.Core/Interface/Game/Friends/FriendsWindow.cs b/Intersect.Client.Core/Interface/Game/Friends/FriendsWindow.cs index 586730e45a..e8c76dcd51 100644 --- a/Intersect.Client.Core/Interface/Game/Friends/FriendsWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Friends/FriendsWindow.cs @@ -97,7 +97,7 @@ public void UpdateList() } } - void addButton_Clicked(Base sender, ClickedEventArgs arguments) + void addButton_Clicked(Base sender, MouseButtonState arguments) { _ = new InputBox( title: Strings.Friends.AddFriend, diff --git a/Intersect.Client.Core/Interface/Game/GuildWindow.cs b/Intersect.Client.Core/Interface/Game/GuildWindow.cs index 91cf467acc..67bb0cca92 100644 --- a/Intersect.Client.Core/Interface/Game/GuildWindow.cs +++ b/Intersect.Client.Core/Interface/Game/GuildWindow.cs @@ -2,6 +2,7 @@ using Intersect.Client.Framework.File_Management; using Intersect.Client.Framework.Gwen.Control; using Intersect.Client.Framework.Gwen.Control.EventArguments; +using Intersect.Client.Framework.Input; using Intersect.Client.General; using Intersect.Client.Interface.Shared; using Intersect.Client.Localization; @@ -36,7 +37,7 @@ public GuildWindow(Canvas gameCanvas) : base(gameCanvas, Globals.Me?.Guild, fals // Textbox Search _textboxContainer = new ImagePanel(this, "SearchContainer"); _textboxSearch = new TextBox(_textboxContainer, "SearchTextbox"); - Interface.FocusElements.Add(_textboxSearch); + Interface.FocusComponents.Add(_textboxSearch); // List of Guild Members _listGuildMembers = new ListBox(this, "GuildMembers"); @@ -192,7 +193,6 @@ public void UpdateList() row.SetToolTipText(Strings.Guilds.Tooltip.ToString(member.Level, member.ClassName)); row.UserData = member; row.Clicked += member_Clicked; - row.RightClicked += member_RightClicked; //Row Render color (red = offline, green = online) if (member.Online == true) @@ -216,8 +216,19 @@ public void UpdateList() _buttonLeave.IsHidden = Globals.Me != null && Globals.Me.Rank == 0; } - private void member_Clicked(Base sender, ClickedEventArgs arguments) + private void member_Clicked(Base sender, MouseButtonState arguments) { + if (arguments.MouseButton == MouseButton.Right) + { + member_RightClicked(sender, arguments); + return; + } + + if (arguments.MouseButton != MouseButton.Left) + { + return; + } + if (sender is ListBoxRow { UserData: GuildMember { Online: true } member } && member.Id != Globals.Me?.Id ) @@ -226,7 +237,7 @@ private void member_Clicked(Base sender, ClickedEventArgs arguments) } } - private void member_RightClicked(Base sender, ClickedEventArgs arguments) + private void member_RightClicked(Base sender, MouseButtonState arguments) { if (sender is not ListBoxRow row || row.UserData is not GuildMember member) { @@ -304,7 +315,7 @@ private void member_RightClicked(Base sender, ClickedEventArgs arguments) #region Guild Actions - private void promoteOption_Clicked(Base sender, ClickedEventArgs arguments) + private void promoteOption_Clicked(Base sender, MouseButtonState arguments) { if (Globals.Me == default || Globals.Me.GuildRank == default || _selectedMember == default) { @@ -337,7 +348,7 @@ private void promoteOption_Clicked(Base sender, ClickedEventArgs arguments) ); } - private void demoteOption_Clicked(Base sender, ClickedEventArgs arguments) + private void demoteOption_Clicked(Base sender, MouseButtonState arguments) { if (Globals.Me == default || Globals.Me.GuildRank == default || _selectedMember == default) { @@ -370,7 +381,7 @@ private void demoteOption_Clicked(Base sender, ClickedEventArgs arguments) ); } - private void kickOption_Clicked(Base sender, ClickedEventArgs arguments) + private void kickOption_Clicked(Base sender, MouseButtonState arguments) { if (Globals.Me == default || Globals.Me.GuildRank == default || _selectedMember == default) { @@ -401,7 +412,7 @@ private void kickOption_Clicked(Base sender, ClickedEventArgs arguments) ); } - private void transferOption_Clicked(Base sender, ClickedEventArgs arguments) + private void transferOption_Clicked(Base sender, MouseButtonState arguments) { if (Globals.Me == default || Globals.Me.GuildRank == default || _selectedMember == default) { diff --git a/Intersect.Client.Core/Interface/Game/Hotbar/HotbarItem.cs b/Intersect.Client.Core/Interface/Game/Hotbar/HotbarItem.cs index 007686cada..feb3f3c9aa 100644 --- a/Intersect.Client.Core/Interface/Game/Hotbar/HotbarItem.cs +++ b/Intersect.Client.Core/Interface/Game/Hotbar/HotbarItem.cs @@ -79,7 +79,6 @@ public HotbarItem(int hotbarSlotIndex, Base hotbarWindow) }; _contentPanel.HoverEnter += hotbarIcon_HoverEnter; _contentPanel.HoverLeave += hotbarIcon_HoverLeave; - _contentPanel.RightClicked += hotbarIcon_RightClicked; _contentPanel.Clicked += hotbarIcon_Clicked; var font = GameContentManager.Current.GetFont("sourcesansproblack", 8); @@ -162,19 +161,18 @@ public void Activate() } } - private void hotbarIcon_RightClicked(Base sender, ClickedEventArgs arguments) + private void hotbarIcon_Clicked(Base sender, MouseButtonState arguments) { - if (Globals.Me == null) + switch (arguments.MouseButton) { - return; - } - - Globals.Me.AddToHotbar(_hotbarSlotIndex, -1, -1); - } + case MouseButton.Left: + _clickTime = Timing.Global.MillisecondsUtc + 500; + break; - private void hotbarIcon_Clicked(Base sender, ClickedEventArgs arguments) - { - _clickTime = Timing.Global.MillisecondsUtc + 500; + case MouseButton.Right: + Globals.Me?.AddToHotbar(_hotbarSlotIndex, -1, -1); + break; + } } private void hotbarIcon_HoverLeave(Base sender, EventArgs arguments) @@ -204,7 +202,7 @@ private void hotbarIcon_HoverEnter(Base sender, EventArgs arguments) _mouseOver = true; _canDrag = true; - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { _canDrag = false; @@ -529,7 +527,7 @@ public void Update() if (_mouseOver) { - if (!Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (!Globals.InputManager.MouseButtonDown(MouseButton.Left)) { _canDrag = true; _mouseX = -1; diff --git a/Intersect.Client.Core/Interface/Game/Inventory/InventoryItem.cs b/Intersect.Client.Core/Interface/Game/Inventory/InventoryItem.cs index c09ef2fda5..49a661ab61 100644 --- a/Intersect.Client.Core/Interface/Game/Inventory/InventoryItem.cs +++ b/Intersect.Client.Core/Interface/Game/Inventory/InventoryItem.cs @@ -73,7 +73,6 @@ public void Setup() Pnl = new ImagePanel(Container, "InventoryItemIcon"); Pnl.HoverEnter += pnl_HoverEnter; Pnl.HoverLeave += pnl_HoverLeave; - Pnl.RightClicked += pnl_RightClicked; Pnl.Clicked += pnl_Clicked; Pnl.DoubleClicked += Pnl_DoubleClicked; EquipPanel = new ImagePanel(Pnl, "InventoryItemEquippedIcon"); @@ -87,7 +86,7 @@ public void Setup() mCooldownLabel.TextColor = new Color(0, 255, 255, 255); } - private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_DoubleClicked(Base sender, MouseButtonState arguments) { if (Globals.GameShop != null) { @@ -127,39 +126,43 @@ private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) } } - void pnl_Clicked(Base sender, ClickedEventArgs arguments) + void pnl_Clicked(Base sender, MouseButtonState arguments) { - mClickTime = Timing.Global.MillisecondsUtc + 500; - } - - void pnl_RightClicked(Base sender, ClickedEventArgs arguments) - { - if (ClientConfiguration.Instance.EnableContextMenus) - { - mInventoryWindow.OpenContextMenu(mMySlot); - } - else + switch (arguments.MouseButton) { - if (Globals.GameShop != null) - { - Globals.Me.TrySellItem(mMySlot); - } - else if (Globals.InBank) - { - Globals.Me.TryDepositItem(mMySlot); - } - else if (Globals.InBag) - { - Globals.Me.TryStoreBagItem(mMySlot, -1); - } - else if (Globals.InTrade) - { - Globals.Me.TryTradeItem(mMySlot); - } - else - { - Globals.Me.TryDropItem(mMySlot); - } + case MouseButton.Left: + mClickTime = Timing.Global.MillisecondsUtc + 500; + break; + + case MouseButton.Right: + if (ClientConfiguration.Instance.EnableContextMenus) + { + mInventoryWindow.OpenContextMenu(mMySlot); + } + else + { + if (Globals.GameShop != null) + { + Globals.Me?.TrySellItem(mMySlot); + } + else if (Globals.InBank) + { + Globals.Me?.TryDepositItem(mMySlot); + } + else if (Globals.InBag) + { + Globals.Me?.TryStoreBagItem(mMySlot, -1); + } + else if (Globals.InTrade) + { + Globals.Me?.TryTradeItem(mMySlot); + } + else + { + Globals.Me?.TryDropItem(mMySlot); + } + } + break; } } @@ -184,7 +187,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) mMouseOver = true; mCanDrag = true; - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = false; @@ -355,7 +358,7 @@ public void Update() { if (mMouseOver) { - if (!Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (!Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = true; mMouseX = -1; diff --git a/Intersect.Client.Core/Interface/Game/Inventory/InventoryWindow.cs b/Intersect.Client.Core/Interface/Game/Inventory/InventoryWindow.cs index 7831d0212a..244b78b3fd 100644 --- a/Intersect.Client.Core/Interface/Game/Inventory/InventoryWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Inventory/InventoryWindow.cs @@ -154,13 +154,13 @@ public void OpenContextMenu(int slot) } } - private void MUseItemContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MUseItemContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { var slot = (int)sender.Parent.UserData; Globals.Me.TryUseItem(slot); } - private void MActionItemContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MActionItemContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { var slot = (int)sender.Parent.UserData; if (Globals.GameShop != null) @@ -181,7 +181,7 @@ private void MActionItemContextItem_Clicked(Base sender, Framework.Gwen.Control. } } - private void MDropItemContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MDropItemContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { var slot = (int) sender.Parent.UserData; Globals.Me.TryDropItem(slot); diff --git a/Intersect.Client.Core/Interface/Game/MapItem/MapItemIcon.cs b/Intersect.Client.Core/Interface/Game/MapItem/MapItemIcon.cs index 7dd0da6b3a..37abbad4f1 100644 --- a/Intersect.Client.Core/Interface/Game/MapItem/MapItemIcon.cs +++ b/Intersect.Client.Core/Interface/Game/MapItem/MapItemIcon.cs @@ -42,7 +42,7 @@ public void Setup() Pnl.Clicked += pnl_Clicked; } - void pnl_Clicked(Base sender, ClickedEventArgs arguments) + void pnl_Clicked(Base sender, MouseButtonState arguments) { if (MyItem == null || TileIndex < 0 || TileIndex >= Options.Instance.Map.MapWidth * Options.Instance.Map.MapHeight) { @@ -73,7 +73,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) return; } - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { return; } diff --git a/Intersect.Client.Core/Interface/Game/MapItem/MapItemWindow.cs b/Intersect.Client.Core/Interface/Game/MapItem/MapItemWindow.cs index f28c69fd1e..cae75486f0 100644 --- a/Intersect.Client.Core/Interface/Game/MapItem/MapItemWindow.cs +++ b/Intersect.Client.Core/Interface/Game/MapItem/MapItemWindow.cs @@ -191,7 +191,7 @@ private void CreateItemContainer() } } - private void MBtnLootAll_Clicked(Base sender, ClickedEventArgs arguments) + private void MBtnLootAll_Clicked(Base sender, MouseButtonState arguments) { if (Globals.Me.MapInstance == null) { diff --git a/Intersect.Client.Core/Interface/Game/Menu.cs b/Intersect.Client.Core/Interface/Game/Menu.cs index 55cfac1f28..962a8c5e3b 100644 --- a/Intersect.Client.Core/Interface/Game/Menu.cs +++ b/Intersect.Client.Core/Interface/Game/Menu.cs @@ -360,7 +360,7 @@ public bool HasWindowsOpen() } //Input Handlers - private void MenuButtonClicked(Base sender, ClickedEventArgs arguments) + private void MenuButtonClicked(Base sender, MouseButtonState arguments) { var simplifiedEscapeMenuSetting = Globals.Database.SimplifiedEscapeMenu; @@ -374,17 +374,17 @@ private void MenuButtonClicked(Base sender, ClickedEventArgs arguments) } } - private void PartyBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void PartyBtn_Clicked(Base sender, MouseButtonState arguments) { TogglePartyWindow(); } - private void FriendsBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void FriendsBtn_Clicked(Base sender, MouseButtonState arguments) { ToggleFriendsWindow(); } - private void GuildBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void GuildBtn_Clicked(Base sender, MouseButtonState arguments) { if (!string.IsNullOrEmpty(Globals.Me.Guild)) { @@ -396,22 +396,22 @@ private void GuildBtn_Clicked(Base sender, ClickedEventArgs arguments) } } - private void QuestBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void QuestBtn_Clicked(Base sender, MouseButtonState arguments) { ToggleQuestsWindow(); } - private void InventoryButton_Clicked(Base sender, ClickedEventArgs arguments) + private void InventoryButton_Clicked(Base sender, MouseButtonState arguments) { ToggleInventoryWindow(); } - private void SpellsButton_Clicked(Base sender, ClickedEventArgs arguments) + private void SpellsButton_Clicked(Base sender, MouseButtonState arguments) { ToggleSpellsWindow(); } - private void CharacterButton_Clicked(Base sender, ClickedEventArgs arguments) + private void CharacterButton_Clicked(Base sender, MouseButtonState arguments) { ToggleCharacterWindow(); } diff --git a/Intersect.Client.Core/Interface/Game/PartyWindow.cs b/Intersect.Client.Core/Interface/Game/PartyWindow.cs index e35bb6ca09..0bd002905f 100644 --- a/Intersect.Client.Core/Interface/Game/PartyWindow.cs +++ b/Intersect.Client.Core/Interface/Game/PartyWindow.cs @@ -54,7 +54,7 @@ public PartyWindow(Canvas gameCanvas) mLeader.Hide(); mLeaderText = new Label(mPartyWindow, "LeaderText"); - mLeaderText.SetTextColor(new Color(0, 0, 0, 0), Label.ControlState.Normal); + mLeaderText.SetTextColor(new Color(0, 0, 0, 0), ComponentState.Normal); mLeaderText.Text = Strings.Parties.Leader; mLeaderText.Hide(); @@ -89,11 +89,11 @@ public PartyWindow(Canvas gameCanvas) mHpBarContainer[i].Hide(); mHpLabel.Add(new Label(mPartyWindow, "HealthLabel" + i)); mHpLabel[i].Hide(); - mHpLabel[i].SetTextColor(new Color(0, 0, 0, 0), Label.ControlState.Normal); + mHpLabel[i].SetTextColor(new Color(0, 0, 0, 0), ComponentState.Normal); mHpLabel[i].Text = Strings.Parties.Vital0; mHpValue.Add(new Label(mPartyWindow, "HealthValue" + i)); mHpValue[i].Hide(); - mHpValue[i].SetTextColor(new Color(0, 0, 0, 0), Label.ControlState.Normal); + mHpValue[i].SetTextColor(new Color(0, 0, 0, 0), ComponentState.Normal); if (i < Globals.Me.Party.Count) { mHpBarContainer[i].Show(); @@ -107,11 +107,11 @@ public PartyWindow(Canvas gameCanvas) mMpBarContainer[i].RenderColor = new Color(0, 0, 0, 0); mMpLabel.Add(new Label(mPartyWindow, "ManaLabel" + i)); mMpLabel[i].Hide(); - mMpLabel[i].SetTextColor(new Color(0, 0, 0, 0), Label.ControlState.Normal); + mMpLabel[i].SetTextColor(new Color(0, 0, 0, 0), ComponentState.Normal); mMpLabel[i].Text = Strings.Parties.Vital1; mMpValue.Add(new Label(mPartyWindow, "ManaValue" + i)); mMpValue[i].Hide(); - mMpValue[i].SetTextColor(new Color(0, 0, 0, 0), Label.ControlState.Normal); + mMpValue[i].SetTextColor(new Color(0, 0, 0, 0), ComponentState.Normal); mMpBar.Add(new ImagePanel(mMpBarContainer[i], "ManaBar" + i)); mMpBar[i].RenderColor = new Color(0, 0, 0, 0); @@ -293,7 +293,7 @@ public void Update() } } - private void PartyWindow_Clicked(Base sender, ClickedEventArgs arguments) + private void PartyWindow_Clicked(Base sender, MouseButtonState arguments) { var memberId = (Guid)((Label)sender).UserData; @@ -319,7 +319,7 @@ public void Hide() } //Input Handlers - void kick_Clicked(Base sender, ClickedEventArgs arguments) + void kick_Clicked(Base sender, MouseButtonState arguments) { for (var i = 1; i < Globals.Me.Party.Count; i++) { @@ -332,7 +332,7 @@ void kick_Clicked(Base sender, ClickedEventArgs arguments) } } - void leave_Clicked(Base sender, ClickedEventArgs arguments) + void leave_Clicked(Base sender, MouseButtonState arguments) { if (Globals.Me.Party.Count > 0) { diff --git a/Intersect.Client.Core/Interface/Game/PictureWindow.cs b/Intersect.Client.Core/Interface/Game/PictureWindow.cs index b37e2ea238..e260907c4f 100644 --- a/Intersect.Client.Core/Interface/Game/PictureWindow.cs +++ b/Intersect.Client.Core/Interface/Game/PictureWindow.cs @@ -91,7 +91,7 @@ public void Setup(string picture, int size, bool clickable) } } - private void MPicture_Clicked(Base sender, ClickedEventArgs arguments) + private void MPicture_Clicked(Base sender, MouseButtonState arguments) { if (Clickable) { diff --git a/Intersect.Client.Core/Interface/Game/QuestOfferWindow.cs b/Intersect.Client.Core/Interface/Game/QuestOfferWindow.cs index c28d22cd9c..d34106b48e 100644 --- a/Intersect.Client.Core/Interface/Game/QuestOfferWindow.cs +++ b/Intersect.Client.Core/Interface/Game/QuestOfferWindow.cs @@ -56,10 +56,10 @@ public QuestOfferWindow(Canvas gameCanvas) mDeclineButton.Clicked += _declineButton_Clicked; mQuestOfferWindow.LoadJsonUi(GameContentManager.UI.InGame, Graphics.Renderer.GetResolutionString()); - Interface.InputBlockingElements.Add(mQuestOfferWindow); + Interface.InputBlockingComponents.Add(mQuestOfferWindow); } - private void _declineButton_Clicked(Base sender, ClickedEventArgs arguments) + private void _declineButton_Clicked(Base sender, MouseButtonState arguments) { if (Globals.QuestOffers.Count > 0) { @@ -68,7 +68,7 @@ private void _declineButton_Clicked(Base sender, ClickedEventArgs arguments) } } - private void _acceptButton_Clicked(Base sender, ClickedEventArgs arguments) + private void _acceptButton_Clicked(Base sender, MouseButtonState arguments) { if (Globals.QuestOffers.Count > 0) { diff --git a/Intersect.Client.Core/Interface/Game/QuestsWindow.cs b/Intersect.Client.Core/Interface/Game/QuestsWindow.cs index 0806942fa4..a6dd8642c9 100644 --- a/Intersect.Client.Core/Interface/Game/QuestsWindow.cs +++ b/Intersect.Client.Core/Interface/Game/QuestsWindow.cs @@ -69,7 +69,7 @@ public QuestsWindow(Canvas gameCanvas) mQuestsWindow.LoadJsonUi(GameContentManager.UI.InGame, Graphics.Renderer.GetResolutionString()); } - private void _quitButton_Clicked(Base sender, ClickedEventArgs arguments) + private void _quitButton_Clicked(Base sender, MouseButtonState arguments) { if (mSelectedQuest != null) { @@ -94,7 +94,7 @@ void AbandonQuest(object sender, EventArgs e) PacketSender.SendAbandonQuest((Guid) ((InputBox) sender).UserData); } - private void _backButton_Clicked(Base sender, ClickedEventArgs arguments) + private void _backButton_Clicked(Base sender, MouseButtonState arguments) { mSelectedQuest = null; UpdateSelectedQuest(); @@ -281,7 +281,7 @@ private void Item_Selected(Base sender, ItemSelectedEventArgs arguments) mQuestList.UnselectAll(); } - private void QuestListItem_Clicked(Base sender, ClickedEventArgs arguments) + private void QuestListItem_Clicked(Base sender, MouseButtonState arguments) { var questNum = (Guid) ((ListBoxRow) sender).UserData; var quest = QuestBase.Get(questNum); @@ -318,8 +318,8 @@ private void UpdateSelectedQuest() { //In Progress mQuestStatus.SetText(Strings.QuestLog.InProgress); - mQuestStatus.SetTextColor(CustomColors.QuestWindow.InProgress, Label.ControlState.Normal); - mQuestDescTemplateLabel.SetTextColor(CustomColors.QuestWindow.QuestDesc, Label.ControlState.Normal); + mQuestStatus.SetTextColor(CustomColors.QuestWindow.InProgress, ComponentState.Normal); + mQuestDescTemplateLabel.SetTextColor(CustomColors.QuestWindow.QuestDesc, ComponentState.Normal); if (mSelectedQuest.InProgressDescription.Length > 0) { @@ -377,7 +377,7 @@ private void UpdateSelectedQuest() if (mSelectedQuest.LogAfterComplete) { mQuestStatus.SetText(Strings.QuestLog.Completed); - mQuestStatus.SetTextColor(CustomColors.QuestWindow.Completed, Label.ControlState.Normal); + mQuestStatus.SetTextColor(CustomColors.QuestWindow.Completed, ComponentState.Normal); mQuestDescLabel.AddText(mSelectedQuest.EndDescription, mQuestDescTemplateLabel); } } @@ -387,7 +387,7 @@ private void UpdateSelectedQuest() if (mSelectedQuest.LogBeforeOffer) { mQuestStatus.SetText(Strings.QuestLog.NotStarted); - mQuestStatus.SetTextColor(CustomColors.QuestWindow.NotStarted, Label.ControlState.Normal); + mQuestStatus.SetTextColor(CustomColors.QuestWindow.NotStarted, ComponentState.Normal); mQuestDescLabel.AddText(mSelectedQuest.BeforeDescription, mQuestDescTemplateLabel); mQuitButton?.Hide(); @@ -401,7 +401,7 @@ private void UpdateSelectedQuest() if (mSelectedQuest.LogBeforeOffer) { mQuestStatus.SetText(Strings.QuestLog.NotStarted); - mQuestStatus.SetTextColor(CustomColors.QuestWindow.NotStarted, Label.ControlState.Normal); + mQuestStatus.SetTextColor(CustomColors.QuestWindow.NotStarted, ComponentState.Normal); mQuestDescLabel.AddText(mSelectedQuest.BeforeDescription, mQuestDescTemplateLabel); } } diff --git a/Intersect.Client.Core/Interface/Game/Shop/ShopItem.cs b/Intersect.Client.Core/Interface/Game/Shop/ShopItem.cs index 46a31e9507..b7f92ea724 100644 --- a/Intersect.Client.Core/Interface/Game/Shop/ShopItem.cs +++ b/Intersect.Client.Core/Interface/Game/Shop/ShopItem.cs @@ -54,17 +54,22 @@ public void Setup() Pnl = new ImagePanel(Container, "ShopItemIcon"); Pnl.HoverEnter += pnl_HoverEnter; Pnl.HoverLeave += pnl_HoverLeave; - Pnl.RightClicked += Pnl_RightClicked; + Pnl.Clicked += Pnl_RightClicked; Pnl.DoubleClicked += Pnl_DoubleClicked; } - private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_DoubleClicked(Base sender, MouseButtonState arguments) { - Globals.Me.TryBuyItem(mMySlot); + Globals.Me?.TryBuyItem(mMySlot); } - private void Pnl_RightClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_RightClicked(Base sender, MouseButtonState arguments) { + if (arguments.MouseButton != MouseButton.Right) + { + return; + } + if (ClientConfiguration.Instance.EnableContextMenus) { mShopWindow.OpenContextMenu(mMySlot); @@ -89,7 +94,7 @@ public void LoadItem() } } - + void pnl_HoverLeave(Base sender, EventArgs arguments) { @@ -110,7 +115,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) return; } - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { return; } diff --git a/Intersect.Client.Core/Interface/Game/Shop/ShopWindow.cs b/Intersect.Client.Core/Interface/Game/Shop/ShopWindow.cs index 390beebea9..d67a670a48 100644 --- a/Intersect.Client.Core/Interface/Game/Shop/ShopWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Shop/ShopWindow.cs @@ -32,7 +32,7 @@ public ShopWindow(Canvas gameCanvas) { mShopWindow = new WindowControl(gameCanvas, Globals.GameShop.Name, false, "ShopWindow"); mShopWindow.DisableResizing(); - Interface.InputBlockingElements.Add(mShopWindow); + Interface.InputBlockingComponents.Add(mShopWindow); mItemContainer = new ScrollControl(mShopWindow, "ItemContainer"); mItemContainer.EnableScroll(false, true); @@ -71,7 +71,7 @@ public void OpenContextMenu(int slot) mContextMenu.Open(Framework.Gwen.Pos.None); } - private void MBuyContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MBuyContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { var slot = (int) sender.Parent.UserData; Globals.Me.TryBuyItem(slot); diff --git a/Intersect.Client.Core/Interface/Game/SimplifiedEscapeMenu.cs b/Intersect.Client.Core/Interface/Game/SimplifiedEscapeMenu.cs index 3e315befc0..ededf0e4c6 100644 --- a/Intersect.Client.Core/Interface/Game/SimplifiedEscapeMenu.cs +++ b/Intersect.Client.Core/Interface/Game/SimplifiedEscapeMenu.cs @@ -22,7 +22,10 @@ public SimplifiedEscapeMenu(Canvas gameCanvas) : base(gameCanvas, nameof(Simplif { IsHidden = true; IconMarginDisabled = true; - _settingsWindow = new SettingsWindow(gameCanvas, null, null); + _settingsWindow = new SettingsWindow(gameCanvas, null, null) + { + IsVisible = false, + }; Children.Clear(); @@ -74,7 +77,7 @@ public void ToggleHidden(Button? target) } } - private void LogoutToCharacterSelectSelectClicked(Base sender, ClickedEventArgs arguments) + private void LogoutToCharacterSelectSelectClicked(Base sender, MouseButtonState arguments) { if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) { @@ -91,7 +94,7 @@ private void LogoutToCharacterSelectSelectClicked(Base sender, ClickedEventArgs } } - private void LogoutToMainToMainMenuClicked(Base sender, ClickedEventArgs arguments) + private void LogoutToMainToMainMenuClicked(Base sender, MouseButtonState arguments) { if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) { @@ -108,7 +111,7 @@ private void LogoutToMainToMainMenuClicked(Base sender, ClickedEventArgs argumen } } - private void ExitToDesktopToDesktopClicked(Base sender, ClickedEventArgs arguments) + private void ExitToDesktopToDesktopClicked(Base sender, MouseButtonState arguments) { if (Globals.Me?.CombatTimer > Timing.Global.Milliseconds) { diff --git a/Intersect.Client.Core/Interface/Game/Spells/SpellItem.cs b/Intersect.Client.Core/Interface/Game/Spells/SpellItem.cs index 5b2a2f8ea9..22468b180c 100644 --- a/Intersect.Client.Core/Interface/Game/Spells/SpellItem.cs +++ b/Intersect.Client.Core/Interface/Game/Spells/SpellItem.cs @@ -59,7 +59,6 @@ public void Setup() Pnl = new ImagePanel(Container, "SpellIcon"); Pnl.HoverEnter += pnl_HoverEnter; Pnl.HoverLeave += pnl_HoverLeave; - Pnl.RightClicked += pnl_RightClicked; Pnl.Clicked += pnl_Clicked; Pnl.DoubleClicked += Pnl_DoubleClicked; mCooldownLabel = new Label(Pnl, "SpellCooldownLabel"); @@ -67,26 +66,31 @@ public void Setup() mCooldownLabel.TextColor = new Color(0, 255, 255, 255); } - private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_DoubleClicked(Base sender, MouseButtonState arguments) { Globals.Me.TryUseSpell(mYindex); } - void pnl_Clicked(Base sender, ClickedEventArgs arguments) + void pnl_Clicked(Base sender, MouseButtonState arguments) { - mClickTime = Timing.Global.MillisecondsUtc + 500; - } - - void pnl_RightClicked(Base sender, ClickedEventArgs arguments) - { - if (ClientConfiguration.Instance.EnableContextMenus) + switch (arguments.MouseButton) { - mSpellWindow.OpenContextMenu(mYindex); - } - else - { - Globals.Me.TryForgetSpell(mYindex); + case MouseButton.Left: + mClickTime = Timing.Global.MillisecondsUtc + 500; + break; + + case MouseButton.Right: + if (ClientConfiguration.Instance.EnableContextMenus) + { + mSpellWindow.OpenContextMenu(mYindex); + } + else + { + Globals.Me?.TryForgetSpell(mYindex); + } + break; } + } void pnl_HoverLeave(Base sender, EventArgs arguments) @@ -110,7 +114,7 @@ void pnl_HoverEnter(Base sender, EventArgs arguments) mMouseOver = true; mCanDrag = true; - if (Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = false; @@ -201,7 +205,7 @@ public void Update() { if (mMouseOver) { - if (!Globals.InputManager.MouseButtonDown(MouseButtons.Left)) + if (!Globals.InputManager.MouseButtonDown(MouseButton.Left)) { mCanDrag = true; mMouseX = -1; diff --git a/Intersect.Client.Core/Interface/Game/Spells/SpellsWindow.cs b/Intersect.Client.Core/Interface/Game/Spells/SpellsWindow.cs index 1d7be63c88..4c5a16206e 100644 --- a/Intersect.Client.Core/Interface/Game/Spells/SpellsWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Spells/SpellsWindow.cs @@ -92,13 +92,13 @@ public void OpenContextMenu(int slot) mContextMenu.Open(Framework.Gwen.Pos.None); } - private void MForgetSpellContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MForgetSpellContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { var slot = (int)sender.Parent.UserData; Globals.Me.TryForgetSpell(slot); } - private void MUseSpellContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.ClickedEventArgs arguments) + private void MUseSpellContextItem_Clicked(Base sender, Framework.Gwen.Control.EventArguments.MouseButtonState arguments) { var slot = (int)sender.Parent.UserData; Globals.Me.TryUseSpell(slot); diff --git a/Intersect.Client.Core/Interface/Game/TargetContextMenu.cs b/Intersect.Client.Core/Interface/Game/TargetContextMenu.cs index 702c9fa53f..a89421efa9 100644 --- a/Intersect.Client.Core/Interface/Game/TargetContextMenu.cs +++ b/Intersect.Client.Core/Interface/Game/TargetContextMenu.cs @@ -127,7 +127,7 @@ public void ToggleHidden(object? target) Open(Pos.None); SetPosition(newX, newY); } - else if (!Globals.InputManager.MouseButtonDown(MouseButtons.Right)) + else if (!Globals.InputManager.MouseButtonDown(MouseButton.Right)) { Close(); } @@ -157,7 +157,7 @@ private void TryShowTargetButton(bool shouldShow) } } - void invite_Clicked(Base sender, ClickedEventArgs arguments) + void invite_Clicked(Base sender, MouseButtonState arguments) { if (_me == null || _entity is not Player || _entity == _me) { @@ -174,7 +174,7 @@ void invite_Clicked(Base sender, ClickedEventArgs arguments) } } - void tradeRequest_Clicked(Base sender, ClickedEventArgs arguments) + void tradeRequest_Clicked(Base sender, MouseButtonState arguments) { if (_me == null || _entity is not Player || _entity == _me) { @@ -191,7 +191,7 @@ void tradeRequest_Clicked(Base sender, ClickedEventArgs arguments) } } - void friendRequest_Clicked(Base sender, ClickedEventArgs arguments) + void friendRequest_Clicked(Base sender, MouseButtonState arguments) { if (_me == null || _entity is not Player || _entity == _me) { @@ -208,7 +208,7 @@ void friendRequest_Clicked(Base sender, ClickedEventArgs arguments) } } - void guildRequest_Clicked(Base sender, ClickedEventArgs arguments) + void guildRequest_Clicked(Base sender, MouseButtonState arguments) { if (_me == null || _entity is not Player plyr || _entity == _me) { @@ -237,7 +237,7 @@ void guildRequest_Clicked(Base sender, ClickedEventArgs arguments) } } - void privateMessageRequest_Clicked(Base sender, ClickedEventArgs arguments) + void privateMessageRequest_Clicked(Base sender, MouseButtonState arguments) { if (_me == null || _entity is not Player || _entity == _me) { diff --git a/Intersect.Client.Core/Interface/Game/Trades/TradeItem.cs b/Intersect.Client.Core/Interface/Game/Trades/TradeItem.cs index e710e10d50..ea47bc3e55 100644 --- a/Intersect.Client.Core/Interface/Game/Trades/TradeItem.cs +++ b/Intersect.Client.Core/Interface/Game/Trades/TradeItem.cs @@ -2,6 +2,7 @@ using Intersect.Client.Framework.Gwen.Control; using Intersect.Client.Framework.Gwen.Control.EventArguments; using Intersect.Client.Framework.Gwen.Input; +using Intersect.Client.Framework.Input; using Intersect.Client.General; using Intersect.Client.Interface.Game.DescriptionWindows; using Intersect.Configuration; @@ -54,12 +55,17 @@ public void Setup() Pnl = new ImagePanel(Container, "TradeIcon"); Pnl.HoverEnter += pnl_HoverEnter; Pnl.HoverLeave += pnl_HoverLeave; - Pnl.RightClicked += Pnl_RightClicked; + Pnl.Clicked += Pnl_RightClicked; Pnl.DoubleClicked += Pnl_DoubleClicked; } - private void Pnl_RightClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_RightClicked(Base sender, MouseButtonState arguments) { + if (arguments.MouseButton != MouseButton.Right) + { + return; + } + // Are we operating our own side? if not ignore it. if (mMySide != 0) { @@ -76,7 +82,7 @@ private void Pnl_RightClicked(Base sender, ClickedEventArgs arguments) } } - private void Pnl_DoubleClicked(Base sender, ClickedEventArgs arguments) + private void Pnl_DoubleClicked(Base sender, MouseButtonState arguments) { // Are we operating our own side? if not ignore it. if (mMySide != 0) diff --git a/Intersect.Client.Core/Interface/Game/Trades/TradingWindow.cs b/Intersect.Client.Core/Interface/Game/Trades/TradingWindow.cs index 1d274cf6d8..08f0221731 100644 --- a/Intersect.Client.Core/Interface/Game/Trades/TradingWindow.cs +++ b/Intersect.Client.Core/Interface/Game/Trades/TradingWindow.cs @@ -43,7 +43,7 @@ public TradingWindow(Canvas gameCanvas, string traderName) ); mTradeWindow.DisableResizing(); - Interface.InputBlockingElements.Add(mTradeWindow); + Interface.InputBlockingComponents.Add(mTradeWindow); mYourOffer = new Label(mTradeWindow, "YourOfferLabel") { @@ -82,7 +82,7 @@ public TradingWindow(Canvas gameCanvas, string traderName) mContextMenu.LoadJsonUi(GameContentManager.UI.InGame, Graphics.Renderer.GetResolutionString()); } - private void MWithdrawContextItem_Clicked(Base sender, ClickedEventArgs arguments) + private void MWithdrawContextItem_Clicked(Base sender, MouseButtonState arguments) { var slot = (int) sender.Parent.UserData; Globals.Me.TryRevokeItem(slot); @@ -193,7 +193,7 @@ public FloatRect RenderBounds() } //Trade the item - void trade_Clicked(Base sender, ClickedEventArgs arguments) + void trade_Clicked(Base sender, MouseButtonState arguments) { mTrade.Text = Strings.Trading.Pending; mTrade.IsDisabled = true; diff --git a/Intersect.Client.Core/Interface/Interface.cs b/Intersect.Client.Core/Interface/Interface.cs index ef510fe8e0..83184e7844 100644 --- a/Intersect.Client.Core/Interface/Interface.cs +++ b/Intersect.Client.Core/Interface/Interface.cs @@ -54,9 +54,9 @@ public static void ShowError(string message, string? header = default) public static TexturedBase Skin { get; set; } //Input Handling - public static List FocusElements { get; set; } + public static readonly List FocusComponents = []; - public static List InputBlockingElements { get; set; } + public static readonly List InputBlockingComponents = []; #region "Gwen Setup and Input" @@ -115,8 +115,8 @@ public static void InitGwen() GwenInput.Initialize(sGameCanvas); } - FocusElements = new List(); - InputBlockingElements = new List(); + FocusComponents.Clear(); + InputBlockingComponents.Clear(); ErrorMsgHandler = new ErrorHandler(); if (Globals.GameState == GameStates.Intro || Globals.GameState == GameStates.Menu) @@ -166,15 +166,9 @@ public static void DestroyGwen(bool exiting = false) } } - public static bool HasInputFocus() - { - if (FocusElements == null || InputBlockingElements == null) - { - return false; - } - - return FocusElements.Any(t => t.MouseInputEnabled && (t?.HasFocus ?? false)) || InputBlockingElements.Any(t => t?.IsHidden == false); - } + public static bool HasInputFocus() => + FocusComponents.Any(component => component is { MouseInputEnabled: true, HasFocus: true }) || + InputBlockingComponents.Any(component => component is { IsVisible: true, IsBlockingInput: true }); #endregion @@ -277,22 +271,10 @@ public static void EnqueueInGame(Action action) _onCreatedGameUi.Enqueue(action); } - public static Framework.Gwen.Control.Base FindControlAtCursor() + public static Framework.Gwen.Control.Base? FindComponentUnderCursor(ComponentStateFilters filters = default) { - var currentElement = CurrentInterface?.Root; var cursor = new Point(InputHandler.MousePosition.X, InputHandler.MousePosition.Y); - - while (default != currentElement) - { - var elementAt = currentElement.GetControlAt(cursor); - if (elementAt == currentElement || elementAt == default) - { - break; -} - - currentElement = elementAt; - } - - return currentElement; + var componentUnderCursor = CurrentInterface?.Root.GetComponentAt(cursor, filters); + return componentUnderCursor; } } diff --git a/Intersect.Client.Core/Interface/Menu/CreateCharacterWindow.cs b/Intersect.Client.Core/Interface/Menu/CreateCharacterWindow.cs index cf67b6e647..63a8e993ec 100644 --- a/Intersect.Client.Core/Interface/Menu/CreateCharacterWindow.cs +++ b/Intersect.Client.Core/Interface/Menu/CreateCharacterWindow.cs @@ -126,7 +126,7 @@ public CreateCharacterWindow(Canvas parent, MainMenu mainMenu, SelectCharacterWi IsChecked = true }; _chkMale.Checked += maleChk_Checked; - _chkMale.UnChecked += femaleChk_Checked; + _chkMale.Unchecked += femaleChk_Checked; // Female Checkbox _chkFemale = new LabeledCheckBox(genderBackground, "FemaleCheckbox") @@ -135,7 +135,7 @@ public CreateCharacterWindow(Canvas parent, MainMenu mainMenu, SelectCharacterWi }; _chkFemale.Checked += femaleChk_Checked; - _chkFemale.UnChecked += maleChk_Checked; + _chkFemale.Unchecked += maleChk_Checked; // Register - Send Registration Button _createButton = new Button(this, "CreateButton") @@ -316,7 +316,7 @@ private void ResetSprite() } } - private void _prevSpriteButton_Clicked(Base sender, ClickedEventArgs arguments) + private void _prevSpriteButton_Clicked(Base sender, MouseButtonState arguments) { _displaySpriteIndex--; if (_chkMale.IsChecked) @@ -351,7 +351,7 @@ private void _prevSpriteButton_Clicked(Base sender, ClickedEventArgs arguments) UpdateDisplay(); } - private void _nextSpriteButton_Clicked(Base sender, ClickedEventArgs arguments) + private void _nextSpriteButton_Clicked(Base sender, MouseButtonState arguments) { _displaySpriteIndex++; if (_chkMale.IsChecked) @@ -432,7 +432,7 @@ void femaleChk_Checked(Base sender, EventArgs arguments) UpdateDisplay(); } - void CreateButton_Clicked(Base sender, ClickedEventArgs arguments) + void CreateButton_Clicked(Base sender, MouseButtonState arguments) { if (Globals.WaitingOnServer) { @@ -442,7 +442,7 @@ void CreateButton_Clicked(Base sender, ClickedEventArgs arguments) TryCreateCharacter(); } - private void BackButton_Clicked(Base sender, ClickedEventArgs arguments) + private void BackButton_Clicked(Base sender, MouseButtonState arguments) { Hide(); if (Options.Instance.Player.MaxCharacters <= 1) diff --git a/Intersect.Client.Core/Interface/Menu/CreditsWindow.cs b/Intersect.Client.Core/Interface/Menu/CreditsWindow.cs index 783d45f40d..d44946df6c 100644 --- a/Intersect.Client.Core/Interface/Menu/CreditsWindow.cs +++ b/Intersect.Client.Core/Interface/Menu/CreditsWindow.cs @@ -36,7 +36,7 @@ public CreditsWindow(Canvas parent, MainMenu mainMenu) : base(parent, "CreditsWi LoadJsonUi(GameContentManager.UI.Menu, Graphics.Renderer?.GetResolutionString()); } - private void BackBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void BackBtn_Clicked(Base sender, MouseButtonState arguments) { Hide(); _mainMenu.Show(); diff --git a/Intersect.Client.Core/Interface/Menu/ForgotPasswordWindow.cs b/Intersect.Client.Core/Interface/Menu/ForgotPasswordWindow.cs index 347e522a86..786e65c691 100644 --- a/Intersect.Client.Core/Interface/Menu/ForgotPasswordWindow.cs +++ b/Intersect.Client.Core/Interface/Menu/ForgotPasswordWindow.cs @@ -85,7 +85,7 @@ public ForgotPasswordWindow(Canvas parent, MainMenu mainMenu) public bool IsHidden => mResetWindow.IsHidden; - private void Textbox_Clicked(Base sender, ClickedEventArgs arguments) + private void Textbox_Clicked(Base sender, MouseButtonState arguments) { Globals.InputManager.OpenKeyboard(KeyboardType.Normal, mInputTextbox.Text, false, false, false); } @@ -117,7 +117,7 @@ public void Show() mInputTextbox.Text = string.Empty; } - void BackBtn_Clicked(Base sender, ClickedEventArgs arguments) + void BackBtn_Clicked(Base sender, MouseButtonState arguments) { Hide(); Interface.MenuUi.MainMenu.NotifyOpenLogin(); @@ -128,7 +128,7 @@ void Textbox_SubmitPressed(Base sender, EventArgs arguments) TrySendCode(); } - void SubmitBtn_Clicked(Base sender, ClickedEventArgs arguments) + void SubmitBtn_Clicked(Base sender, MouseButtonState arguments) { if (Globals.WaitingOnServer) { diff --git a/Intersect.Client.Core/Interface/Menu/LoginWindow.cs b/Intersect.Client.Core/Interface/Menu/LoginWindow.cs index 453e6fdd67..bc04c0a659 100644 --- a/Intersect.Client.Core/Interface/Menu/LoginWindow.cs +++ b/Intersect.Client.Core/Interface/Menu/LoginWindow.cs @@ -1,5 +1,7 @@ using Intersect.Client.Core; using Intersect.Client.Framework.File_Management; +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.Input; @@ -14,14 +16,20 @@ namespace Intersect.Client.Interface.Menu; public partial class LoginWindow : ImagePanel, IMainMenuWindow { + private readonly GameFont? _defaultFont; + private readonly MainMenu _mainMenu; - private readonly TextBox _txtUsername; - private readonly TextBoxPassword _txtPassword; - private readonly LabeledCheckBox _chkSavePass; + private readonly ImagePanel _usernamePanel; + private readonly Label _usernameLabel; + private readonly TextBox _usernameInput; + private readonly ImagePanel _passwordPanel; + private readonly TextBoxPassword _passwordInput; + private readonly LabeledCheckBox _savePasswordCheckbox; private readonly Button _btnForgotPassword; private readonly Button _btnLogin; private bool _useSavedPass; private string _savedPass = string.Empty; + private readonly Label _passwordLabel; //Init public LoginWindow(Canvas parent, MainMenu mainMenu) : base(parent, "LoginWindow") @@ -29,6 +37,8 @@ public LoginWindow(Canvas parent, MainMenu mainMenu) : base(parent, "LoginWindow //Assign References _mainMenu = mainMenu; + _defaultFont = GameContentManager.Current.GetFont(name: "sourcesansproblack", 10); + //Menu Header _ = new Label(this, "LoginHeader") { @@ -36,36 +46,84 @@ public LoginWindow(Canvas parent, MainMenu mainMenu) : base(parent, "LoginWindow }; //Login Username Label/Textbox - var usernameBackground = new ImagePanel(this, "UsernamePanel"); - _ = new Label(usernameBackground, "UsernameLabel") + _usernamePanel = new ImagePanel(this, nameof(_usernamePanel)) + { + X = 14, + Y = 61, + Width = 308, + Height = 22, + Margin = new Margin(14, 0, 0, 0), + }; + _usernameLabel = new Label(_usernamePanel, nameof(_usernameLabel)) { + AutoSizeToContents = false, + Dock = Pos.Left, + Font = _defaultFont, Text = Strings.LoginWindow.Username, + TextAlign = Pos.Right | Pos.CenterV, + TextPadding = new Padding(0, 0, 10, 0), + }; + _usernameInput = new TextBox(_usernamePanel, nameof(_usernameInput)) + { + Dock = Pos.Fill, + Font = _defaultFont, + TextAlign = Pos.Left | Pos.CenterV, + TextPadding = new Padding(2, 0), }; - _txtUsername = new TextBox(usernameBackground, "UsernameField"); - _txtUsername.SubmitPressed += (s, e) => TryLogin(); - _txtUsername.Clicked += _txtUsername_Clicked; + _usernameInput.SubmitPressed += (s, e) => TryLogin(); + _usernameInput.Clicked += UsernameInputClicked; + _usernameInput.SetSound(TextBox.Sounds.AddText, "octave-tap-resonant.wav"); + _usernameInput.SetSound(TextBox.Sounds.RemoveText, "octave-tap-professional.wav"); + _usernameInput.SetSound(TextBox.Sounds.Submit, "octave-tap-warm.wav"); //Login Password Label/Textbox - var passwordBackground = new ImagePanel(this, "PasswordPanel"); - _ = new Label(passwordBackground, "PasswordLabel") + _passwordPanel = new ImagePanel(this, nameof(_passwordPanel)) { + X = 14, + Y = 96, + Width = 308, + Height = 22, + Margin = new Margin(14, 0, 0, 0), + }; + _passwordLabel = new Label(_passwordPanel, nameof(_passwordLabel)) + { + AutoSizeToContents = false, + Dock = Pos.Left, + Font = _defaultFont, Text = Strings.LoginWindow.Password, + TextAlign = Pos.Right | Pos.CenterV, + TextPadding = new Padding(0, 0, 10, 0), }; - _txtPassword = new TextBoxPassword(passwordBackground, "PasswordField"); - _txtPassword.SubmitPressed += (s, e) => TryLogin(); - _txtPassword.TextChanged += _txtPassword_TextChanged; - _txtPassword.Clicked += _txtPassword_Clicked; + _passwordInput = new TextBoxPassword(_passwordPanel, nameof(_passwordInput)) + { + Dock = Pos.Fill, + Font = _defaultFont, + TextAlign = Pos.Left | Pos.CenterV, + TextPadding = new Padding(2, 0), + }; + _passwordInput.SubmitPressed += (s, e) => TryLogin(); + _passwordInput.TextChanged += PasswordInputTextChanged; + _passwordInput.Clicked += PasswordInputClicked; + _passwordInput.SetSound(TextBox.Sounds.AddText, "octave-tap-resonant.wav"); + _passwordInput.SetSound(TextBox.Sounds.RemoveText, "octave-tap-professional.wav"); + _passwordInput.SetSound(TextBox.Sounds.Submit, "octave-tap-warm.wav"); //Login Save Pass Checkbox - _chkSavePass = new LabeledCheckBox(this, "SavePassCheckbox") + _savePasswordCheckbox = new LabeledCheckBox(this, nameof(_savePasswordCheckbox)) { + X = 13, + Y = 124, + Width = 160, + Height = 24, + Font = _defaultFont, Text = Strings.LoginWindow.SavePassword, }; //Forgot Password Button _btnForgotPassword = new Button(this, "ForgotPasswordButton") { - IsHidden = true, Text = Strings.LoginWindow.ForgotPassword, + IsHidden = true, + Text = Strings.LoginWindow.ForgotPassword, }; _btnForgotPassword.Clicked += _btnForgotPassword_Clicked; @@ -89,38 +147,38 @@ public LoginWindow(Canvas parent, MainMenu mainMenu) : base(parent, "LoginWindow #region Input Handling - private void _txtUsername_Clicked(Base sender, ClickedEventArgs arguments) + private void UsernameInputClicked(Base sender, MouseButtonState arguments) { Globals.InputManager.OpenKeyboard( KeyboardType.Normal, - text => _txtUsername.Text = text ?? string.Empty, + text => _usernameInput.Text = text ?? string.Empty, Strings.LoginWindow.Username, - _txtUsername.Text, - inputBounds: _txtUsername.BoundsGlobal + _usernameInput.Text, + inputBounds: _usernameInput.BoundsGlobal ); } - private void _txtPassword_TextChanged(Base sender, EventArgs arguments) + private void PasswordInputTextChanged(Base sender, EventArgs arguments) { _useSavedPass = false; } - private void _txtPassword_Clicked(Base sender, ClickedEventArgs arguments) + private void PasswordInputClicked(Base sender, MouseButtonState arguments) { Globals.InputManager.OpenKeyboard( KeyboardType.Password, - text => _txtPassword.Text = text ?? string.Empty, + text => _passwordInput.Text = text ?? string.Empty, Strings.LoginWindow.Password, - _txtPassword.Text + _passwordInput.Text ); } - private static void _btnForgotPassword_Clicked(Base sender, ClickedEventArgs arguments) + private static void _btnForgotPassword_Clicked(Base sender, MouseButtonState arguments) { Interface.MenuUi.MainMenu.NotifyOpenForgotPassword(); } - private void _btnBack_Clicked(Base sender, ClickedEventArgs arguments) + private void _btnBack_Clicked(Base sender, MouseButtonState arguments) { Hide(); _mainMenu.Show(); @@ -154,13 +212,13 @@ public override void Show() } // Set focus to the appropriate elements. - if (!string.IsNullOrWhiteSpace(_txtUsername.Text)) + if (!string.IsNullOrWhiteSpace(_usernameInput.Text)) { - _txtPassword.Focus(); + _passwordInput.Focus(); } else { - _txtUsername.Focus(); + _usernameInput.Focus(); } } @@ -177,13 +235,13 @@ private void TryLogin() return; } - if (!FieldChecking.IsValidUsername(_txtUsername.Text, Strings.Regex.Username)) + if (!FieldChecking.IsValidUsername(_usernameInput.Text, Strings.Regex.Username)) { Interface.ShowError(Strings.Errors.UsernameInvalid); return; } - if (!FieldChecking.IsValidPassword(_txtPassword.Text, Strings.Regex.Password)) + if (!FieldChecking.IsValidPassword(_passwordInput.Text, Strings.Regex.Password)) { if (!_useSavedPass) { @@ -195,17 +253,19 @@ private void TryLogin() var password = _savedPass; if (!_useSavedPass) { - password = PasswordUtils.ComputePasswordHash(_txtPassword.Text.Trim()); + password = PasswordUtils.ComputePasswordHash(_passwordInput.Text.Trim()); } Globals.WaitingOnServer = true; _btnLogin.Disable(); - PacketSender.SendLogin(_txtUsername.Text, password); + PacketSender.SendLogin(_usernameInput.Text, password); SaveCredentials(); ChatboxMsg.ClearMessages(); } + private const string DefaultPasswordInputMask = "****************"; + private void LoadCredentials() { var name = Globals.Database.LoadPreference("Username"); @@ -214,27 +274,28 @@ private void LoadCredentials() return; } - _txtUsername.Text = name; + _usernameInput.Text = name; var pass = Globals.Database.LoadPreference("Password"); if (string.IsNullOrEmpty(pass)) { return; } - _txtPassword.Text = "****************"; + _passwordInput.Text = DefaultPasswordInputMask; + _passwordInput.CursorPos = DefaultPasswordInputMask.Length; _savedPass = pass; _useSavedPass = true; - _chkSavePass.IsChecked = true; + _savePasswordCheckbox.IsChecked = true; } private void SaveCredentials() { - string username = string.Empty, password = string.Empty; + string? username = default, password = default; - if (_chkSavePass.IsChecked) + if (_savePasswordCheckbox.IsChecked) { - username = _txtUsername.Text.Trim(); - password = _useSavedPass ? _savedPass : PasswordUtils.ComputePasswordHash(_txtPassword.Text.Trim()); + username = _usernameInput.Text?.Trim(); + password = _useSavedPass ? _savedPass : PasswordUtils.ComputePasswordHash(_passwordInput.Text?.Trim()); } Globals.Database.SavePreference("Username", username); diff --git a/Intersect.Client.Core/Interface/Menu/MainMenu.cs b/Intersect.Client.Core/Interface/Menu/MainMenu.cs index 6bf6db6cf6..6222aef317 100644 --- a/Intersect.Client.Core/Interface/Menu/MainMenu.cs +++ b/Intersect.Client.Core/Interface/Menu/MainMenu.cs @@ -52,7 +52,8 @@ 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), + IsVisible = false, }; _creditsWindow = new CreditsWindow(_menuCanvas, this); } diff --git a/Intersect.Client.Core/Interface/Menu/MainMenuWindow.Designer.cs b/Intersect.Client.Core/Interface/Menu/MainMenuWindow.Designer.cs index 297186534e..408560f8e5 100644 --- a/Intersect.Client.Core/Interface/Menu/MainMenuWindow.Designer.cs +++ b/Intersect.Client.Core/Interface/Menu/MainMenuWindow.Designer.cs @@ -69,21 +69,21 @@ protected override void EnsureInitialized() button.SetHoverSound("octave-tap-resonant.wav"); var buttonName = button.Name; - button.SetImage( + button.SetStateTexture( $"mainmenu{buttonName}.png", - Button.ControlState.Normal + ComponentState.Normal ); - button.SetImage( + button.SetStateTexture( $"mainmenu{buttonName}_clicked.png", - Button.ControlState.Clicked + ComponentState.Active ); - button.SetImage( + button.SetStateTexture( $"mainmenu{buttonName}_disabled.png", - Button.ControlState.Disabled + ComponentState.Disabled ); - button.SetImage( + button.SetStateTexture( $"mainmenu{buttonName}_hovered.png", - Button.ControlState.Hovered + ComponentState.Hovered ); } diff --git a/Intersect.Client.Core/Interface/Menu/MainMenuWindow.cs b/Intersect.Client.Core/Interface/Menu/MainMenuWindow.cs index cfb780dffd..e05fdfe823 100644 --- a/Intersect.Client.Core/Interface/Menu/MainMenuWindow.cs +++ b/Intersect.Client.Core/Interface/Menu/MainMenuWindow.cs @@ -81,9 +81,9 @@ public MainMenuWindow(Canvas canvas, MainMenu mainMenu) : base(canvas, Strings.M _buttonStart.Clicked += _buttonStart_Clicked; } - private void _buttonCredits_Clicked(Base sender, ClickedEventArgs arguments) => _mainMenu.SwitchToWindow(); + private void _buttonCredits_Clicked(Base sender, MouseButtonState arguments) => _mainMenu.SwitchToWindow(); - private static void _buttonExit_Clicked(Base sender, ClickedEventArgs arguments) + private static void _buttonExit_Clicked(Base sender, MouseButtonState arguments) { ApplicationContext.Context.Value?.Logger.LogInformation("User clicked exit button."); Globals.IsRunning = false; @@ -91,7 +91,7 @@ private static void _buttonExit_Clicked(Base sender, ClickedEventArgs arguments) #region Login - private void _buttonLogin_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonLogin_Clicked(Base sender, MouseButtonState arguments) { if (Networking.Network.InterruptDisconnectsIfConnected()) { @@ -133,7 +133,7 @@ private void _loginConnected(object? sender, EventArgs eventArgs) #region Register - private void _buttonRegister_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonRegister_Clicked(Base sender, MouseButtonState arguments) { if (Networking.Network.InterruptDisconnectsIfConnected()) { @@ -173,9 +173,9 @@ private void _registerConnected(object? sender, EventArgs eventArgs) #endregion Register - private void _buttonSettings_Clicked(Base sender, ClickedEventArgs arguments) => _mainMenu.SettingsButton_Clicked(); + private void _buttonSettings_Clicked(Base sender, MouseButtonState arguments) => _mainMenu.SettingsButton_Clicked(); - private void _buttonStart_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonStart_Clicked(Base sender, MouseButtonState arguments) { Hide(); Networking.Network.TryConnect(); diff --git a/Intersect.Client.Core/Interface/Menu/ResetPasswordWindow.cs b/Intersect.Client.Core/Interface/Menu/ResetPasswordWindow.cs index 123c42dddd..4384d9a721 100644 --- a/Intersect.Client.Core/Interface/Menu/ResetPasswordWindow.cs +++ b/Intersect.Client.Core/Interface/Menu/ResetPasswordWindow.cs @@ -125,7 +125,7 @@ public ResetPasswordWindow(Canvas parent, MainMenu mainMenu) //The username or email of the acc we are resetting the pass for public string Target { set; get; } = string.Empty; - private void Textbox_Clicked(Base sender, ClickedEventArgs arguments) + private void Textbox_Clicked(Base sender, MouseButtonState arguments) { Globals.InputManager.OpenKeyboard( KeyboardType.Normal, mCodeInputTextbox.Text, false, false, false @@ -156,7 +156,7 @@ public void Show() mPasswordTextbox2.Text = string.Empty; } - void BackBtn_Clicked(Base sender, ClickedEventArgs arguments) + void BackBtn_Clicked(Base sender, MouseButtonState arguments) { Hide(); Interface.MenuUi.MainMenu.NotifyOpenLogin(); @@ -167,7 +167,7 @@ void Textbox_SubmitPressed(Base sender, EventArgs arguments) TrySendCode(); } - void SubmitBtn_Clicked(Base sender, ClickedEventArgs arguments) + void SubmitBtn_Clicked(Base sender, MouseButtonState arguments) { TrySendCode(); } diff --git a/Intersect.Client.Core/Interface/Menu/SelectCharacterWindow.cs b/Intersect.Client.Core/Interface/Menu/SelectCharacterWindow.cs index bb6d546172..3f5004f85d 100644 --- a/Intersect.Client.Core/Interface/Menu/SelectCharacterWindow.cs +++ b/Intersect.Client.Core/Interface/Menu/SelectCharacterWindow.cs @@ -245,13 +245,13 @@ public override void Show() base.Show(); } - private void _buttonLogout_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonLogout_Clicked(Base sender, MouseButtonState arguments) { Main.Logout(false, skipFade: true); _mainMenu.Reset(); } - private void _buttonPrevChar_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonPrevChar_Clicked(Base sender, MouseButtonState arguments) { mSelectedChar--; if (mSelectedChar < 0) @@ -262,7 +262,7 @@ private void _buttonPrevChar_Clicked(Base sender, ClickedEventArgs arguments) UpdateDisplay(); } - private void _buttonNextChar_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonNextChar_Clicked(Base sender, MouseButtonState arguments) { mSelectedChar++; if (mSelectedChar >= Characters!.Length) @@ -273,7 +273,7 @@ private void _buttonNextChar_Clicked(Base sender, ClickedEventArgs arguments) UpdateDisplay(); } - private void _buttonDelete_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonDelete_Clicked(Base sender, MouseButtonState arguments) { if (Globals.WaitingOnServer || Characters == default) { @@ -306,7 +306,7 @@ private void DeleteCharacter(object? sender, EventArgs e) } } - private void _buttonNew_Clicked(Base sender, ClickedEventArgs arguments) + private void _buttonNew_Clicked(Base sender, MouseButtonState arguments) { if (Globals.WaitingOnServer) { @@ -322,7 +322,7 @@ private void _buttonNew_Clicked(Base sender, ClickedEventArgs arguments) _buttonLogout.Disable(); } - public void ButtonPlay_Clicked(Base? sender, ClickedEventArgs? arguments) + public void ButtonPlay_Clicked(Base? sender, MouseButtonState? arguments) { if (Globals.WaitingOnServer || Characters == default) { diff --git a/Intersect.Client.Core/Interface/Shared/InputBox.cs b/Intersect.Client.Core/Interface/Shared/InputBox.cs index 4b6f0872d7..b8748dd088 100644 --- a/Intersect.Client.Core/Interface/Shared/InputBox.cs +++ b/Intersect.Client.Core/Interface/Shared/InputBox.cs @@ -47,7 +47,7 @@ public enum InputType private readonly ImagePanel _textboxBg; private readonly TextBox _textbox; private readonly ImagePanel _numericSliderBg; - private readonly HorizontalSlider _numericSlider; + private readonly Slider _numericSlider; private readonly TextBoxNumeric _txtNumericSlider; private readonly Button _btnYes; private readonly Button _btnNo; @@ -90,8 +90,9 @@ public InputBox( _textbox.SubmitPressed += (sender, e) => SubmitInput(); _numericSliderBg = new ImagePanel(this, "Sliderbox"); - _numericSlider = new HorizontalSlider(_numericSliderBg, "Slider") + _numericSlider = new Slider(_numericSliderBg, "Slider") { + Orientation = Orientation.LeftToRight, NotchCount = maxQuantity, SnapToNotches = true, Min = 1, @@ -161,14 +162,14 @@ public InputBox( _btnOk.Clicked += (sender, e) => SubmitInput(); _promptLabel = new Label(this, "PromptLabel"); - Interface.InputBlockingElements.Add(this); + Interface.InputBlockingComponents.Add(this); Value = quantity; } private void _numericSliderTextbox_TextChanged(Base sender, EventArgs arguments) { - if (sender is HorizontalSlider box && box == _numericSlider) + if (sender == _numericSlider) { return; } @@ -295,7 +296,7 @@ private void _beforeDraw(Base sender, EventArgs arguments) } } - void btnNo_Clicked(Base sender, ClickedEventArgs arguments) + void btnNo_Clicked(Base sender, MouseButtonState arguments) { if (_inputType == InputType.YesNoCancel) { @@ -305,7 +306,7 @@ void btnNo_Clicked(Base sender, ClickedEventArgs arguments) SubmitInput(); } - void btnYes_Clicked(Base sender, ClickedEventArgs arguments) + void btnYes_Clicked(Base sender, MouseButtonState arguments) { if (_inputType == InputType.YesNoCancel) { @@ -315,7 +316,7 @@ void btnYes_Clicked(Base sender, ClickedEventArgs arguments) SubmitInput(); } - void btnCancel_Clicked(Base sender, ClickedEventArgs arguments) + void btnCancel_Clicked(Base sender, MouseButtonState arguments) { if (_inputType == InputType.NumericInput) { diff --git a/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs b/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs index 6b22d3e811..b9eca11473 100644 --- a/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs +++ b/Intersect.Client.Core/Interface/Shared/SettingsWindow.cs @@ -1,46 +1,48 @@ 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; 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; namespace Intersect.Client.Interface.Shared; -public partial class SettingsWindow : ImagePanel +using BottomBarItems = (Panel BottomBar, Button RestoreDefaultControlsButton, Button ApplyPendingChangesButton, Button CancelPendingChangesButton); + +public partial class SettingsWindow : WindowControl { + private readonly GameFont? _defaultFont; + // Parent Window. private readonly MainMenu? _mainMenu; private readonly EscapeMenu? _escapeMenu; - // Settings Window. - private readonly Label _settingsHeader; - private readonly Button _settingsApplyBtn; - private readonly Button _settingsCancelBtn; - - // Settings Containers. - private readonly ScrollControl _gameSettingsContainer; - private readonly ScrollControl _videoSettingsContainer; - private readonly ScrollControl _audioSettingsContainer; - private readonly ScrollControl _keybindingSettingsContainer; + // Bottom Bar + private readonly Button _restoreDefaultsButton; + private readonly Button _applyPendingChangesButton; + private readonly Button _cancelPendingChangesButton; - // Tabs. - private readonly Button _gameSettingsTab; - private readonly Button _videoSettingsTab; - private readonly Button _audioSettingsTab; - private readonly Button _keybindingSettingsTab; + // Game Settings + private readonly TabButton _gameSettingsTab; + private readonly TabControl _gameContainer; - // Game Settings - Interface. + // Game Settings - Interface + private readonly TabButton _gameSettingsTabInterface; private readonly ScrollControl _interfaceSettings; private readonly LabeledCheckBox _autoCloseWindowsCheckbox; private readonly LabeledCheckBox _autoToggleChatLogCheckbox; @@ -49,7 +51,8 @@ public partial class SettingsWindow : ImagePanel private readonly LabeledCheckBox _showManaAsPercentageCheckbox; private readonly LabeledCheckBox _simplifiedEscapeMenu; - // Game Settings - Information. + // Game Settings - Information + private readonly TabButton _gameSettingsTabInformation; private readonly ScrollControl _informationSettings; private readonly LabeledCheckBox _friendOverheadInfoCheckbox; private readonly LabeledCheckBox _guildMemberOverheadInfoCheckbox; @@ -60,387 +63,461 @@ public partial class SettingsWindow : ImagePanel private readonly LabeledCheckBox _friendOverheadHpBarCheckbox; private readonly LabeledCheckBox _guildMemberOverheadHpBarCheckbox; private readonly LabeledCheckBox _myOverheadHpBarCheckbox; - private readonly LabeledCheckBox _mpcOverheadHpBarCheckbox; + private readonly LabeledCheckBox _npcOverheadHpBarCheckbox; private readonly LabeledCheckBox _partyMemberOverheadHpBarCheckbox; private readonly LabeledCheckBox _playerOverheadHpBarCheckbox; private readonly LabeledCheckBox _typewriterCheckbox; - // Game Settings - Targeting. + // Game Settings - Targeting + private readonly TabButton _gameSettingsTabTargeting; private readonly ScrollControl _targetingSettings; private readonly LabeledCheckBox _stickyTarget; private readonly LabeledCheckBox _autoTurnToTarget; private readonly LabeledCheckBox _autoSoftRetargetOnSelfCast; - // Video Settings. - private readonly ComboBox _resolutionList; - private MenuItem? _customResolutionMenuItem; - private readonly ComboBox _fpsList; - private readonly LabeledHorizontalSlider _worldScale; + // Video Settings + private readonly TabButton _videoSettingsTab; + private readonly ScrollControl _videoContainer; + private readonly LabeledComboBox _resolutionList; + private readonly LabeledComboBox _fpsList; + private readonly LabeledSlider _worldScale; private readonly LabeledCheckBox _fullscreenCheckbox; private readonly LabeledCheckBox _lightingEnabledCheckbox; + private MenuItem? _customResolutionMenuItem; + + // Audio Settings + private readonly TabButton _audioSettingsTab; + private readonly ScrollControl _audioContainer; + private readonly LabeledSlider _musicSlider; + private readonly LabeledSlider _soundEffectsSlider; - // Audio Settings. - private readonly HorizontalSlider _musicSlider; private int _previousMusicVolume; - private readonly Label _musicLabel; - private readonly HorizontalSlider _soundSlider; private int _previousSoundVolume; - private readonly Label _soundLabel; - // Keybinding Settings. + // Controls + private readonly TabButton _controlsTab; + private readonly ScrollControl _controlsContainer; + private readonly Table _controlsTable; + private readonly Dictionary _controlBindingButtons = []; + private readonly Panel _bottomBar; + private readonly TabControl _tabs; + + private readonly HashSet _keysDown = []; private Control _keybindingEditControl; private Controls? _keybindingEditControls; private Button? _keybindingEditBtn; - private readonly Button _keybindingRestoreBtn; private long _keybindingListeningTimer; private int _keyEdit = -1; - private readonly Dictionary mKeybindingBtns = []; - // Open Settings. + // 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: parent, title: Strings.Settings.Title, modal: false, name: nameof(SettingsWindow)) { - // Assign References. _mainMenu = mainMenu; _escapeMenu = escapeMenu; - // Main Menu Window. - Interface.InputBlockingElements.Add(this); + Interface.InputBlockingComponents.Add(item: this); + + IconName = "SettingsWindow.icon.png"; + + Alignment = [Alignments.Center]; + MinimumSize = new Point(x: 640, y: 400); + IsResizable = false; + IsClosable = false; + + Titlebar.MouseInputEnabled = false; + + TitleLabel.FontSize = 14; + TitleLabel.TextColorOverride = Color.White; - // Menu Header. - _settingsHeader = new Label(this, "SettingsHeader") + _defaultFont = Current.GetFont(name: TitleLabel.FontName, 12); + +#region Game + + // Game Settings are stored in the GameSettings Scroll Control. + _gameContainer = new TabControl(parent: this, name: nameof(_gameContainer)) { - Text = Strings.Settings.Title + Dock = Pos.Fill, + DockChildSpacing = new Padding(0, 4, 0, 0), + Font = _defaultFont, + TabStripPosition = Pos.Left, }; + _gameContainer.TabChanged += GameContainerOnTabChanged; - // Apply Button. - _settingsApplyBtn = new Button(this, "SettingsApplyBtn") + _interfaceSettings = new ScrollControl(parent: _gameContainer, name: nameof(_interfaceSettings)) { - Text = Strings.Settings.Apply + Dock = Pos.Fill, + DockChildSpacing = new Padding(0, 4, 0, 0), + InnerPanelPadding = new Padding(4), }; - _settingsApplyBtn.Clicked += SettingsApplyBtn_Clicked; + _gameSettingsTabInterface = _gameContainer.AddPage( + label: Strings.Settings.InterfaceSettings, + tabName: nameof(_gameSettingsTabInterface), + page: _interfaceSettings + ); - // Cancel Button. - _settingsCancelBtn = new Button(this, "SettingsCancelBtn") + _informationSettings = new ScrollControl(parent: _gameContainer, name: nameof(_informationSettings)) { - Text = Strings.Settings.Cancel + Dock = Pos.Fill, + DockChildSpacing = new Padding(0, 4, 0, 0), + InnerPanelPadding = new Padding(4), }; - _settingsCancelBtn.Clicked += SettingsCancelBtn_Clicked; - - #region InitGameSettings + _gameSettingsTabInformation = _gameContainer.AddPage( + label: Strings.Settings.InformationSettings, + tabName: nameof(_gameSettingsTabInformation), + page: _informationSettings + ); - // Init GameSettings Tab. - _gameSettingsTab = new Button(this, "GameSettingsTab") + _targetingSettings = new ScrollControl(parent: _gameContainer, name: nameof(_targetingSettings)) { - Text = Strings.Settings.GameSettingsTab + Dock = Pos.Fill, + DockChildSpacing = new Padding(0, 4, 0, 0), + InnerPanelPadding = new Padding(4), }; - _gameSettingsTab.Clicked += GameSettingsTab_Clicked; - - // Game Settings are stored in the GameSettings Scroll Control. - _gameSettingsContainer = new ScrollControl(this, "GameSettingsContainer"); - _gameSettingsContainer.EnableScroll(false, 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); - gameSettingsList.SelectedRowIndex = 0; - gameSettingsList[0].Clicked += InterfaceSettings_Clicked; - gameSettingsList[1].Clicked += InformationSettings_Clicked; - gameSettingsList[2].Clicked += TargetingSettings_Clicked; + _gameSettingsTabTargeting = _gameContainer.AddPage( + label: Strings.Settings.TargetingSettings, + tabName: nameof(_gameSettingsTabTargeting), + page: _targetingSettings + ); // Game Settings - Interface. - _interfaceSettings = new ScrollControl(_gameSettingsContainer, "InterfaceSettings"); - _interfaceSettings.EnableScroll(false, true); // Game Settings - Interface: Auto-close Windows. - _autoCloseWindowsCheckbox = new LabeledCheckBox(_interfaceSettings, "AutoCloseWindowsCheckbox") + _autoCloseWindowsCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: nameof(_autoCloseWindowsCheckbox)) { - Text = Strings.Settings.AutoCloseWindows + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.AutoCloseWindows, }; // Game Settings - Interface: Auto-toggle chat log visibility. - _autoToggleChatLogCheckbox = new LabeledCheckBox(_interfaceSettings, "AutoToggleChatLogCheckbox") + _autoToggleChatLogCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: nameof(_autoToggleChatLogCheckbox)) { - Text = Strings.Settings.AutoToggleChatLog + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.AutoToggleChatLog, }; // Game Settings - Interface: Show EXP/HP/MP as Percentage. - _showExperienceAsPercentageCheckbox = new LabeledCheckBox(_interfaceSettings, "ShowExperienceAsPercentageCheckbox") + _showExperienceAsPercentageCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: nameof(_showExperienceAsPercentageCheckbox)) { - Text = Strings.Settings.ShowExperienceAsPercentage + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowExperienceAsPercentage, }; - _showHealthAsPercentageCheckbox = new LabeledCheckBox(_interfaceSettings, "ShowHealthAsPercentageCheckbox") + _showHealthAsPercentageCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: nameof(_showHealthAsPercentageCheckbox)) { - Text = Strings.Settings.ShowHealthAsPercentage + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowHealthAsPercentage, }; - _showManaAsPercentageCheckbox = new LabeledCheckBox(_interfaceSettings, "ShowManaAsPercentageCheckbox") + _showManaAsPercentageCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: nameof(_showManaAsPercentageCheckbox)) { - Text = Strings.Settings.ShowManaAsPercentage + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowManaAsPercentage, }; // Game Settings - Interface: simplified escape menu. - _simplifiedEscapeMenu = new LabeledCheckBox(_interfaceSettings, "SimplifiedEscapeMenu") + _simplifiedEscapeMenu = new LabeledCheckBox(parent: _interfaceSettings, name: nameof(_simplifiedEscapeMenu)) { - Text = Strings.Settings.SimplifiedEscapeMenu + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.SimplifiedEscapeMenu, }; - // Game Settings - Information. - _informationSettings = new ScrollControl(_gameSettingsContainer, "InformationSettings"); - _informationSettings.EnableScroll(false, true); + // Game Settings - Typewriter Text + _typewriterCheckbox = new LabeledCheckBox(parent: _interfaceSettings, name: nameof(_typewriterCheckbox)) + { + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.TypewriterText, + }; // Game Settings - Information: Friends. - _friendOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "FriendOverheadInfoCheckbox") + _friendOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_friendOverheadInfoCheckbox)) { - Text = Strings.Settings.ShowFriendOverheadInformation + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowFriendOverheadInformation, }; // Game Settings - Information: Guild Members. - _guildMemberOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "GuildMemberOverheadInfoCheckbox") + _guildMemberOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_guildMemberOverheadInfoCheckbox)) { - Text = Strings.Settings.ShowGuildOverheadInformation + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowGuildOverheadInformation, }; // Game Settings - Information: Myself. - _myOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "MyOverheadInfoCheckbox") + _myOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_myOverheadInfoCheckbox)) { - Text = Strings.Settings.ShowMyOverheadInformation + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowMyOverheadInformation, }; // Game Settings - Information: NPCs. - _npcOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "NpcOverheadInfoCheckbox") + _npcOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_npcOverheadInfoCheckbox)) { - Text = Strings.Settings.ShowNpcOverheadInformation + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowNpcOverheadInformation, }; // Game Settings - Information: Party Members. - _partyMemberOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "PartyMemberOverheadInfoCheckbox") + _partyMemberOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_partyMemberOverheadInfoCheckbox)) { - Text = Strings.Settings.ShowPartyOverheadInformation + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowPartyOverheadInformation, }; // Game Settings - Information: Players. - _playerOverheadInfoCheckbox = new LabeledCheckBox(_informationSettings, "PlayerOverheadInfoCheckbox") + _playerOverheadInfoCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_playerOverheadInfoCheckbox)) { - Text = Strings.Settings.ShowPlayerOverheadInformation + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowPlayerOverheadInformation, }; // Game Settings - Information: friends overhead hp bar. - _friendOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "FriendOverheadHpBarCheckbox") + _friendOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_friendOverheadHpBarCheckbox)) { - Text = Strings.Settings.ShowFriendOverheadHpBar + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowFriendOverheadHpBar, }; // Game Settings - Information: guild members overhead hp bar. - _guildMemberOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "GuildMemberOverheadHpBarCheckbox") + _guildMemberOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_guildMemberOverheadHpBarCheckbox)) { - Text = Strings.Settings.ShowGuildOverheadHpBar + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowGuildOverheadHpBar, }; // Game Settings - Information: my overhead hp bar. - _myOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "MyOverheadHpBarCheckbox") + _myOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_myOverheadHpBarCheckbox)) { - Text = Strings.Settings.ShowMyOverheadHpBar + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowMyOverheadHpBar, }; // Game Settings - Information: NPC overhead hp bar. - _mpcOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "NpcOverheadHpBarCheckbox") + _npcOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_npcOverheadHpBarCheckbox)) { - Text = Strings.Settings.ShowNpcOverheadHpBar + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowNpcOverheadHpBar, }; // Game Settings - Information: party members overhead hp bar. - _partyMemberOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "PartyMemberOverheadHpBarCheckbox") + _partyMemberOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_partyMemberOverheadHpBarCheckbox)) { - Text = Strings.Settings.ShowPartyOverheadHpBar + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowPartyOverheadHpBar, }; // Game Settings - Information: players overhead hp bar. - _playerOverheadHpBarCheckbox = new LabeledCheckBox(_informationSettings, "PlayerOverheadHpBarCheckbox") + _playerOverheadHpBarCheckbox = new LabeledCheckBox(parent: _informationSettings, name: nameof(_playerOverheadHpBarCheckbox)) { - Text = Strings.Settings.ShowPlayerOverheadHpBar + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.ShowPlayerOverheadHpBar, }; - // Game Settings - Targeting. - _targetingSettings = new ScrollControl(_gameSettingsContainer, "TargetingSettings"); - _targetingSettings.EnableScroll(false, false); - // Game Settings - Targeting: Sticky Target. - _stickyTarget = new LabeledCheckBox(_targetingSettings, "StickyTargetCheckbox") + _stickyTarget = new LabeledCheckBox(parent: _targetingSettings, name: nameof(_stickyTarget)) { - Text = Strings.Settings.StickyTarget + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.StickyTarget, }; // Game Settings - Targeting: Auto-turn to Target. - _autoTurnToTarget = new LabeledCheckBox(_targetingSettings, "AutoTurnToTargetCheckbox") + _autoTurnToTarget = new LabeledCheckBox(parent: _targetingSettings, name: nameof(_autoTurnToTarget)) { + Dock = Pos.Top, + Font = _defaultFont, Text = Strings.Settings.AutoTurnToTarget, }; // Game Settings - Targeting: Auto-turn to Target. - _autoSoftRetargetOnSelfCast = new LabeledCheckBox(_targetingSettings, "AutoSoftRetargetOnSelfCast") + _autoSoftRetargetOnSelfCast = new LabeledCheckBox(parent: _targetingSettings, name: nameof(_autoSoftRetargetOnSelfCast)) { + Dock = Pos.Top, + Font = _defaultFont, Text = Strings.Settings.AutoSoftRetargetOnSelfCast, TooltipText = Strings.Settings.AutoSoftRetargetOnSelfCastTooltip, TooltipBackgroundName = "tooltip.png", - TooltipFontName = "sourcesansproblack", + TooltipFont = _defaultFont, TooltipTextColor = Color.White, }; - // Game Settings - Typewriter Text - _typewriterCheckbox = new LabeledCheckBox(_interfaceSettings, "TypewriterCheckbox") - { - Text = Strings.Settings.TypewriterText - }; - - #endregion - - #region InitVideoSettings +#endregion Game - // Init VideoSettings Tab. - _videoSettingsTab = new Button(this, "VideoSettingsTab") - { - Text = Strings.Settings.VideoSettingsTab - }; - _videoSettingsTab.Clicked += VideoSettingsTab_Clicked; +#region Video // Video Settings Get Stored in the VideoSettings Scroll Control. - _videoSettingsContainer = new ScrollControl(this, "VideoSettingsContainer"); - _videoSettingsContainer.EnableScroll(false, false); - - // Video Settings - Resolution Background. - var resolutionBackground = new ImagePanel(_videoSettingsContainer, "ResolutionPanel"); - - // Video Settings - Resolution Label. - var resolutionLabel = new Label(resolutionBackground, "ResolutionLabel") + _videoContainer = new ScrollControl(parent: this, name: nameof(_videoContainer)) { - Text = Strings.Settings.Resolution + Dock = Pos.Fill, + DockChildSpacing = new Padding(0, 4, 0, 0), + InnerPanelPadding = new Padding(4), }; // Video Settings - Resolution List. - _resolutionList = new ComboBox(resolutionBackground, "ResolutionCombobox"); - var myModes = Graphics.Renderer?.GetValidVideoModes(); - myModes?.ForEach( - t => - { - var item = _resolutionList.AddItem(t); - item.TextAlign = Pos.Left; - } - ); - - _worldScale = new LabeledHorizontalSlider(_videoSettingsContainer, "WorldScale") + _resolutionList = new LabeledComboBox(parent: _videoContainer, name: nameof(_resolutionList)) { - IsDisabled = !Options.IsLoaded, - Label = Strings.Settings.WorldScale, - SnapToNotches = false, + Dock = Pos.Top, + Font = _defaultFont, + Label = Strings.Settings.Resolution, + TextPadding = new Padding(8, 4, 0, 4), }; - // Video Settings - FPS Background. - var fpsBackground = new ImagePanel(_videoSettingsContainer, "FPSPanel"); - - // Video Settings - FPS Label. - var fpsLabel = new Label(fpsBackground, "FPSLabel") + var availableVideoModes = Graphics.Renderer?.GetValidVideoModes().ToArray() ?? []; + for (var videoModeIndex = 0; videoModeIndex < availableVideoModes.Length; videoModeIndex++) { - Text = Strings.Settings.TargetFps - }; + var availableVideoMode = availableVideoModes[videoModeIndex]; + var resolutionParts = availableVideoMode.Split(','); + var resolutionLabel = Strings.Settings.FormatResolution.ToString(resolutionParts.Cast().ToArray()); + var addedItem = _resolutionList.AddItem(label: resolutionLabel, userData: videoModeIndex); + addedItem.TextAlign = Pos.Left; + } // 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 LabeledComboBox(parent: _videoContainer, name: nameof(_fpsList)) + { + Dock = Pos.Top, + Font = _defaultFont, + Label = Strings.Settings.FPS, + TextPadding = new Padding(8, 4, 0, 4), + }; + _ = _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: _videoContainer, name: nameof(_fullscreenCheckbox)) { - Text = Strings.Settings.Fullscreen + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.Fullscreen, }; // Video Settings - Enable Lighting Checkbox - _lightingEnabledCheckbox = new LabeledCheckBox(_videoSettingsContainer, "EnableLightingCheckbox") + _lightingEnabledCheckbox = new LabeledCheckBox(parent: _videoContainer, name: nameof(_lightingEnabledCheckbox)) { - Text = Strings.Settings.EnableLighting + Dock = Pos.Top, + Font = _defaultFont, + Text = Strings.Settings.EnableLighting, }; - #endregion - - #region InitAudioSettings - - // Init AudioSettingsTab. - _audioSettingsTab = new Button(this, "AudioSettingsTab") + _worldScale = new LabeledSlider(parent: _videoContainer, name: nameof(_worldScale)) { - Text = Strings.Settings.AudioSettingsTab + Dock = Pos.Top, + IsDisabled = !Options.IsLoaded, + Font = _defaultFont, + Label = Strings.Settings.WorldScale, + Orientation = Orientation.LeftToRight, + SnapToNotches = true, + ValueFormatString = Strings.Settings.FormatZoom, }; - _audioSettingsTab.Clicked += AudioSettingsTab_Clicked; - // Audio Settings Get Stored in the AudioSettings Scroll Control. - _audioSettingsContainer = new ScrollControl(this, "AudioSettingsContainer"); - _audioSettingsContainer.EnableScroll(false, false); +#endregion Video + +#region Audio - // Audio Settings - Sound Label - _soundLabel = new Label(_audioSettingsContainer, "SoundLabel") + // Audio Settings Get Stored in the AudioSettings Scroll Control. + _audioContainer = new ScrollControl(parent: this, name: nameof(_audioContainer)) { - Text = Strings.Settings.SoundVolume.ToString(100) + Dock = Pos.Fill, + DockChildSpacing = new Padding(0, 4, 0, 0), + InnerPanelPadding = new Padding(4), }; - // Audio Settings - Sound Slider - _soundSlider = new HorizontalSlider(_audioSettingsContainer, "SoundSlider") - { + // Audio Settings - Music Slider + _musicSlider = new LabeledSlider(parent: _audioContainer, name: nameof(_musicSlider)) + { + Dock = Pos.Top, + Font = _defaultFont, + Orientation = Orientation.LeftToRight, + Height = 35, + DraggerSize = new Point(9, 9), + 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, }; - _soundSlider.ValueChanged += SoundSlider_ValueChanged; - // Audio Settings - Music Label - _musicLabel = new Label(_audioSettingsContainer, "MusicLabel") - { - Text = Strings.Settings.MusicVolume.ToString(100) - }; + _musicSlider.ValueChanged += MusicSliderOnValueChanged; + _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: _audioContainer, name: nameof(_soundEffectsSlider)) + { + Dock = Pos.Top, + Font = _defaultFont, + Orientation = Orientation.LeftToRight, + Height = 35, + DraggerSize = new Point(9, 9), + 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, }; - _musicSlider.ValueChanged += MusicSlider_ValueChanged; - #endregion + _soundEffectsSlider.ValueChanged += SoundEffectsSliderOnValueChanged; + _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); - #region InitKeybindingSettings +#endregion Audio - // Init KeybindingsSettings Tab. - _keybindingSettingsTab = new Button(this, "KeybindingSettingsTab") - { - Text = Strings.Settings.KeyBindingSettingsTab - }; - _keybindingSettingsTab.Clicked += KeybindingSettingsTab_Clicked; +#region Controls // KeybindingSettings Get Stored in the KeybindingSettings Scroll Control - _keybindingSettingsContainer = new ScrollControl(this, "KeybindingSettingsContainer"); - _keybindingSettingsContainer.EnableScroll(false, true); + _controlsContainer = new ScrollControl(parent: this, name: nameof(_controlsContainer)) + { + Dock = Pos.Fill, + InnerPanelPadding = new Padding(4), + }; - // Keybinding Settings - Restore Default Keys Button. - _keybindingRestoreBtn = new Button(this, "KeybindingsRestoreBtn") + _controlsTable = new Table(parent: _controlsContainer, name: nameof(_controlsTable)) { - Text = Strings.Settings.Restore + Dock = Pos.Top, + DockChildSpacing = new Padding(0, 4, 0, 0), + ColumnCount = 3, + Font = _defaultFont, + SizeToContents = true, }; - _keybindingRestoreBtn.Clicked += KeybindingsRestoreBtn_Clicked; // 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; @@ -448,15 +525,154 @@ public SettingsWindow(Base parent, MainMenu? mainMenu, EscapeMenu? escapeMenu) : Input.KeyUp += OnKeyUp; Input.MouseUp += OnKeyUp; - #endregion +#endregion Controls + + _tabs = new TabControl(parent: this, name: nameof(_tabs)) + { + Dock = Pos.Fill, + Font = _defaultFont, + Margin = new Margin(left: 4, top: 4, right: 4, bottom: 0), + }; + + _gameSettingsTab = _tabs.AddPage( + label: Strings.Settings.GameSettingsTab, + page: _gameContainer, + tabName: nameof(_gameSettingsTab) + ); + _videoSettingsTab = _tabs.AddPage( + label: Strings.Settings.VideoSettingsTab, + page: _videoContainer, + tabName: nameof(_videoSettingsTab) + ); + _audioSettingsTab = _tabs.AddPage( + label: Strings.Settings.AudioSettingsTab, + page: _audioContainer, + tabName: nameof(_audioSettingsTab) + ); + _controlsTab = _tabs.AddPage( + label: Strings.Settings.ControlsTab, + page: _controlsContainer, + tabName: nameof(_controlsTab) + ); + _tabs.TabChanged += TabsOnTabChanged; + + (_bottomBar, _restoreDefaultsButton, _applyPendingChangesButton, _cancelPendingChangesButton) = + CreateBottomBar(this); + + LoadJsonUi(stage: UI.Shared, resolution: Graphics.Renderer?.GetResolutionString()); + } + + public override bool IsBlockingInput => _keybindingEditBtn is not null; + + private BottomBarItems CreateBottomBar(Base parent) + { + var bottomBar = new Panel(parent: parent, name: nameof(_bottomBar)) + { + Dock = Pos.Bottom, + MinimumSize = new Point(x: 0, y: 40), + Margin = Margin.Four, + Padding = new Padding(horizontal: 8, vertical: 4), + }; + + // Keybinding Settings - Restore Default Keys Button. + var restoreDefaultKeybindingsButton = new Button(parent: bottomBar, name: nameof(_restoreDefaultsButton)) + { + Alignment = [Alignments.Left, Alignments.CenterV], + AutoSizeToContents = true, + Font = _defaultFont, + 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"); + + // Apply Button. + var applyPendingChangesButton = new Button(parent: bottomBar, name: nameof(_applyPendingChangesButton)) + { + Alignment = [Alignments.Center], + AutoSizeToContents = true, + Font = _defaultFont, + 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. + var cancelPendingChangesButton = new Button(parent: bottomBar, name: nameof(_cancelPendingChangesButton)) + { + Alignment = [Alignments.Right, Alignments.CenterV], + AutoSizeToContents = true, + Font = _defaultFont, + 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"); + + return ( + BottomBar: bottomBar, + RestoreDefaultControlsButton: restoreDefaultKeybindingsButton, + ApplyPendingChangesButton: applyPendingChangesButton, + CancelPendingChangesButton: cancelPendingChangesButton + ); + } + + private void GameContainerOnTabChanged(Base sender, TabChangeEventArgs arguments) + { + // ReSharper disable once InvertIf + if (arguments.ActiveTab == _gameSettingsTabTargeting) + { + _autoTurnToTarget.IsDisabled = !(Options.Instance?.Player?.EnableAutoTurnToTarget ?? false); + _autoSoftRetargetOnSelfCast.IsDisabled = + !(Options.Instance?.Combat?.EnableAutoSelfCastFriendlySpellsWhenTargetingHostile ?? false); + } + } + + private void TabsOnTabChanged(Base @base, TabChangeEventArgs tabChangeEventArgs) + { + if (_controlsTab.IsTabActive) + { + _restoreDefaultsButton.IsVisible = true; + + bool controlsAdded = false; + + _controlsTable.FitContents(); + + var row = _controlBindingButtons.Count; + foreach (var (control, mapping) in (_keybindingEditControls ?? Controls.ActiveControls).Mappings) + { + if (!_controlBindingButtons.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); + } + } - LoadJsonUi(UI.Shared, Graphics.Renderer?.GetResolutionString()); - IsHidden = true; + if (controlsAdded) + { + // Current.SaveUIJson(UI.Shared, Name, GetJsonUI(true), Graphics.Renderer?.GetResolutionString()); + } + } + else + { + _restoreDefaultsButton.IsVisible = false; + } } private bool AddControlKeybindRow(Control control, ref int row, out Button[] keyButtons) { - if (mKeybindingBtns.TryGetValue(control, out var existingButtons)) + if (_controlBindingButtons.TryGetValue(control, out var existingButtons)) { keyButtons = existingButtons; return false; @@ -482,50 +698,51 @@ 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 controlLabel = 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, controlLabel, enableMouseInput: false); + + 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.SetHoverSound("octave-tap-resonant.wav"); + key1.SetMouseDownSound("octave-tap-warm.wav"); + controlRow.SetCellContents(1, key1, enableMouseInput: true); + 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.SetHoverSound("octave-tap-resonant.wav"); + key2.SetMouseDownSound("octave-tap-warm.wav"); + controlRow.SetCellContents(2, key2, enableMouseInput: true); + key2.Clicked += Key_Clicked; keyButtons = [key1, key2]; - mKeybindingBtns.Add(control, keyButtons); + _controlBindingButtons.Add(control, keyButtons); return true; } @@ -569,43 +786,21 @@ 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. - _keybindingRestoreBtn.Hide(); - } - } - - void InterfaceSettings_Clicked(Base sender, ClickedEventArgs arguments) + void InterfaceSettings_Clicked(Base sender, MouseButtonState arguments) { _interfaceSettings.Show(); _informationSettings.Hide(); _targetingSettings.Hide(); } - void InformationSettings_Clicked(Base sender, ClickedEventArgs arguments) + void InformationSettings_Clicked(Base sender, MouseButtonState arguments) { _interfaceSettings.Hide(); _informationSettings.Show(); _targetingSettings.Hide(); } - void TargetingSettings_Clicked(Base sender, ClickedEventArgs arguments) + void TargetingSettings_Clicked(Base sender, MouseButtonState arguments) { _interfaceSettings.Hide(); _informationSettings.Hide(); @@ -615,128 +810,15 @@ void TargetingSettings_Clicked(Base sender, ClickedEventArgs arguments) !(Options.Instance?.Combat?.EnableAutoSelfCastFriendlySpellsWhenTargetingHostile ?? false); } - private void VideoSettingsTab_Clicked(Base sender, ClickedEventArgs arguments) + private void Reset() { - // 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. - _keybindingRestoreBtn.Hide(); - } - } + Title = Strings.Settings.Title; - 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. - _keybindingRestoreBtn.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. - _keybindingRestoreBtn.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() - { - // Settings Window Title. - _settingsHeader.SetText(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. - _settingsApplyBtn.Show(); - _settingsCancelBtn.Show(); - _keybindingRestoreBtn.Hide(); + _gameSettingsTab.Select(); UpdateWorldScaleControls(); } - private readonly HashSet _keysDown = []; - private void OnKeyDown(Keys modifier, Keys key) { if (_keybindingEditBtn != default) @@ -780,7 +862,7 @@ private void OnKeyUp(Keys modifier, Keys key) _keybindingEditControls.UpdateControl(control.Key, bindingIndex, Keys.None, Keys.None); // Update UI. - mKeybindingBtns[control.Key][bindingIndex].Text = Strings.Keys.KeyDictionary[Enum.GetName(typeof(Keys), Keys.None)?.ToLower() ?? string.Empty]; + _controlBindingButtons[control.Key][bindingIndex].Text = Strings.Keys.KeyDictionary[Enum.GetName(typeof(Keys), Keys.None)?.ToLower() ?? string.Empty]; } } } @@ -828,7 +910,7 @@ public void Show(bool returnToMenu = false) _friendOverheadHpBarCheckbox.IsChecked = Globals.Database.FriendOverheadHpBar; _guildMemberOverheadHpBarCheckbox.IsChecked = Globals.Database.GuildMemberOverheadHpBar; _myOverheadHpBarCheckbox.IsChecked = Globals.Database.MyOverheadHpBar; - _mpcOverheadHpBarCheckbox.IsChecked = Globals.Database.NpcOverheadHpBar; + _npcOverheadHpBarCheckbox.IsChecked = Globals.Database.NpcOverheadHpBar; _partyMemberOverheadHpBarCheckbox.IsChecked = Globals.Database.PartyMemberOverheadHpBar; _playerOverheadHpBarCheckbox.IsChecked = Globals.Database.PlayerOverheadHpBar; _stickyTarget.IsChecked = Globals.Database.StickyTarget; @@ -844,22 +926,19 @@ public void Show(bool returnToMenu = false) if (Graphics.Renderer?.GetValidVideoModes().Count > 0) { - string resolutionLabel; if (Graphics.Renderer.HasOverrideResolution) { - resolutionLabel = Strings.Settings.ResolutionCustom; - - _customResolutionMenuItem ??= _resolutionList.AddItem(Strings.Settings.ResolutionCustom); + _customResolutionMenuItem ??= _resolutionList.AddItem( + label: Strings.Settings.ResolutionCustom, + userData: -1 + ); _customResolutionMenuItem.Show(); + _resolutionList.SelectedItem = _customResolutionMenuItem; } else { - var validVideoModes = Graphics.Renderer.GetValidVideoModes(); - var targetResolution = Globals.Database.TargetResolution; - resolutionLabel = targetResolution < 0 || validVideoModes.Count <= targetResolution ? Strings.Settings.ResolutionCustom : validVideoModes[Globals.Database.TargetResolution]; + _resolutionList.SelectByUserData(Globals.Database.TargetResolution); } - - _resolutionList.SelectByText(resolutionLabel); } switch (Globals.Database.TargetFps) @@ -898,9 +977,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); @@ -909,7 +986,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; @@ -943,21 +1020,21 @@ 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(); } - private void Key_Clicked(Base sender, ClickedEventArgs arguments) + private void Key_Clicked(Base sender, MouseButtonState arguments) { EditKeyPressed((Button)sender); } @@ -975,7 +1052,7 @@ private void EditKeyPressed(Button sender) } } - private void KeybindingsRestoreBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void RestoreDefaultKeybindingsButton_Clicked(Base sender, MouseButtonState arguments) { if (_keybindingEditControls is not {} controls) { @@ -993,12 +1070,12 @@ private void KeybindingsRestoreBtn_Clicked(Base sender, ClickedEventArgs argumen for (var bindingIndex = 0; bindingIndex < mapping.Bindings.Count; bindingIndex++) { var binding = mapping.Bindings[bindingIndex]; - mKeybindingBtns[control][bindingIndex].Text = Strings.Keys.FormatKeyName(binding.Modifier, binding.Key); + _controlBindingButtons[control][bindingIndex].Text = Strings.Keys.FormatKeyName(binding.Modifier, binding.Key); } } } - private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void SettingsApplyBtn_Clicked(Base sender, MouseButtonState arguments) { var shouldReset = false; @@ -1018,7 +1095,7 @@ private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) Globals.Database.FriendOverheadHpBar = _friendOverheadHpBarCheckbox.IsChecked; Globals.Database.GuildMemberOverheadHpBar = _guildMemberOverheadHpBarCheckbox.IsChecked; Globals.Database.MyOverheadHpBar = _myOverheadHpBarCheckbox.IsChecked; - Globals.Database.NpcOverheadHpBar = _mpcOverheadHpBarCheckbox.IsChecked; + Globals.Database.NpcOverheadHpBar = _npcOverheadHpBarCheckbox.IsChecked; Globals.Database.PartyMemberOverheadHpBar = _partyMemberOverheadHpBarCheckbox.IsChecked; Globals.Database.PlayerOverheadHpBar = _playerOverheadHpBarCheckbox.IsChecked; Globals.Database.StickyTarget = _stickyTarget.IsChecked; @@ -1029,9 +1106,8 @@ private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) // Video Settings. Globals.Database.WorldZoom = (float)_worldScale.Value; - var resolution = _resolutionList.SelectedItem; - var validVideoModes = Graphics.Renderer?.GetValidVideoModes(); - var targetResolution = validVideoModes?.FindIndex(videoMode => string.Equals(videoMode, resolution.Text)) ?? -1; + var resolutionItem = _resolutionList.SelectedItem; + var targetResolution = resolutionItem?.UserData as int? ?? -1; var newFps = 0; Globals.Database.EnableLighting = _lightingEnabledCheckbox.IsChecked; @@ -1077,7 +1153,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. @@ -1099,7 +1175,7 @@ private void SettingsApplyBtn_Clicked(Base sender, ClickedEventArgs arguments) Hide(); } - private void SettingsCancelBtn_Clicked(Base sender, ClickedEventArgs arguments) + private void CancelPendingChangesButton_Clicked(Base sender, MouseButtonState arguments) { // Update previously saved values in order to discard changes. Globals.Database.MusicVolume = _previousMusicVolume; diff --git a/Intersect.Client.Core/Localization/Strings.cs b/Intersect.Client.Core/Localization/Strings.cs index f8fbebae09..8f9d68e422 100644 --- a/Intersect.Client.Core/Localization/Strings.cs +++ b/Intersect.Client.Core/Localization/Strings.cs @@ -1344,6 +1344,24 @@ public partial struct Internals [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString Padding = @"Padding"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString Dock = @"Dock"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString Alignment = @"Alignment"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString TextAlign = @"Text Align"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString TextPadding = @"Text Padding"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString Font = @"Font"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString AutoSizeToContents = @"Auto Size-to-Conents"; } public partial struct Inventory @@ -1878,7 +1896,7 @@ public partial struct LoginWindow public static LocalizedString Login = @"Login"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public static LocalizedString Password = @"Password:"; + public static LocalizedString Password = @"Password"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString SavePassword = @"Save Password"; @@ -1887,7 +1905,7 @@ public partial struct LoginWindow public static LocalizedString Title = @"Login"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public static LocalizedString Username = @"Username:"; + public static LocalizedString Username = @"Username"; } public partial struct Main @@ -1943,6 +1961,12 @@ public partial struct Settings [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString EnableLighting = @"Enable Light Effects"; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString FormatResolution = @"{00}x{01}"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString FormatZoom = @"{00}x"; + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString Fps120 = @"120"; @@ -1968,13 +1992,25 @@ public partial struct Settings public static LocalizedString InterfaceSettings = @"Interface"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public static LocalizedString KeyBindingSettingsTab = @"Controls"; + public static LocalizedString ControlsTab = @"Controls"; + + [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] + public static LocalizedString Language = @"Language"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString MusicVolume = @"Music Volume: {00}%"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public static LocalizedString Resolution = @"Resolution:"; + 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"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString ResolutionCustom = @"Custom Resolution"; @@ -2037,7 +2073,7 @@ public partial struct Settings public static LocalizedString StickyTarget = @"Sticky Target"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] - public static LocalizedString TargetFps = @"Target FPS:"; + public static LocalizedString FPS = @"FPS"; [JsonProperty(NullValueHandling = NullValueHandling.Ignore)] public static LocalizedString TargetingSettings = @"Targeting"; diff --git a/Intersect.Client.Core/MonoGame/Input/MonoInput.cs b/Intersect.Client.Core/MonoGame/Input/MonoInput.cs index cd20a816f7..11cf603a44 100644 --- a/Intersect.Client.Core/MonoGame/Input/MonoInput.cs +++ b/Intersect.Client.Core/MonoGame/Input/MonoInput.cs @@ -149,7 +149,7 @@ private void InputHandlerOnFocusChanged(Base? control, FocusSource focusSource) Mouse.SetPosition((int)center.X, (int)center.Y); var mouseState = Mouse.GetState(); Interface.Interface.GwenInput.ProcessMessage( - new GwenInputMessage(IntersectInput.InputEvent.MouseMove, new Pointf(mouseState.X, mouseState.Y), (int)MouseButtons.None, Keys.Alt) + new GwenInputMessage(IntersectInput.InputEvent.MouseMove, new Pointf(mouseState.X, mouseState.Y), MouseButton.None, Keys.Alt) ); } @@ -157,25 +157,25 @@ private void Window_TextInput(object sender, TextInputEventArgs e) { Interface.Interface.GwenInput.ProcessMessage( new GwenInputMessage( - IntersectInput.InputEvent.TextEntered, GetMousePosition(), (int) MouseButtons.None, Keys.Alt, false, + IntersectInput.InputEvent.TextEntered, GetMousePosition(), MouseButton.None, Keys.Alt, false, false, false, e.Character.ToString() ) ); } - public override bool MouseButtonDown(MouseButtons mb) + public override bool MouseButtonDown(MouseButton mb) { switch (mb) { - case MouseButtons.Left: + case MouseButton.Left: return mLastMouseState.LeftButton == ButtonState.Pressed; - case MouseButtons.Right: + case MouseButton.Right: return mLastMouseState.RightButton == ButtonState.Pressed; - case MouseButtons.Middle: + case MouseButton.Middle: return mLastMouseState.MiddleButton == ButtonState.Pressed; - case MouseButtons.X1: + case MouseButton.X1: return mLastMouseState.XButton1 == ButtonState.Pressed; - case MouseButtons.X2: + case MouseButton.X2: return mLastMouseState.XButton2 == ButtonState.Pressed; default: throw new ArgumentOutOfRangeException(nameof(mb), mb, null); @@ -190,7 +190,7 @@ public override Pointf GetMousePosition() return new Pointf(mMouseX, mMouseY); } - private void CheckMouseButton(Keys modifier, ButtonState bs, MouseButtons mb) + private void CheckMouseButton(Keys modifier, ButtonState bs, MouseButton mb) { if (Globals.GameState == GameStates.Intro) { @@ -200,7 +200,7 @@ private void CheckMouseButton(Keys modifier, ButtonState bs, MouseButtons mb) if (bs == ButtonState.Pressed && !MouseButtonDown(mb)) { Interface.Interface.GwenInput.ProcessMessage( - new GwenInputMessage(IntersectInput.InputEvent.MouseDown, GetMousePosition(), (int) mb, Keys.Alt) + new GwenInputMessage(IntersectInput.InputEvent.MouseDown, GetMousePosition(), mb, Keys.Alt) ); Core.Input.OnMouseDown(modifier, mb); @@ -208,7 +208,7 @@ private void CheckMouseButton(Keys modifier, ButtonState bs, MouseButtons mb) else if (bs == ButtonState.Released && MouseButtonDown(mb)) { Interface.Interface.GwenInput.ProcessMessage( - new GwenInputMessage(IntersectInput.InputEvent.MouseUp, GetMousePosition(), (int) mb, Keys.Alt) + new GwenInputMessage(IntersectInput.InputEvent.MouseUp, GetMousePosition(), mb, Keys.Alt) ); Core.Input.OnMouseUp(modifier, mb); @@ -224,7 +224,7 @@ private void CheckMouseScrollWheel(int scrlVValue, int scrlHValue) p = new Pointf(scrlHValue - mMouseHScroll, scrlVValue - mMouseVScroll); Interface.Interface.GwenInput.ProcessMessage( - new GwenInputMessage(IntersectInput.InputEvent.MouseScroll, p, (int)MouseButtons.Middle, Keys.Alt) + new GwenInputMessage(IntersectInput.InputEvent.MouseScroll, p, MouseButton.Middle, Keys.Alt) ); mMouseVScroll = scrlVValue; @@ -275,7 +275,7 @@ public override void Update(TimeSpan elapsed) mMouseY = (int)(mouseState.Y * ((MonoRenderer)Core.Graphics.Renderer).GetMouseOffset().Y); Interface.Interface.GwenInput.ProcessMessage( new GwenInputMessage( - IntersectInput.InputEvent.MouseMove, GetMousePosition(), (int)MouseButtons.None, Keys.Alt + IntersectInput.InputEvent.MouseMove, GetMousePosition(), MouseButton.None, Keys.Alt ) ); } @@ -348,11 +348,11 @@ public override void Update(TimeSpan elapsed) var modifier = GetPressedModifier(keyboardState); //Check for state changes in the left mouse button - CheckMouseButton(modifier, mouseState.LeftButton, MouseButtons.Left); - CheckMouseButton(modifier, mouseState.RightButton, MouseButtons.Right); - CheckMouseButton(modifier, mouseState.MiddleButton, MouseButtons.Middle); - CheckMouseButton(modifier, mouseState.XButton1, MouseButtons.X1); - CheckMouseButton(modifier, mouseState.XButton2, MouseButtons.X2); + CheckMouseButton(modifier, mouseState.LeftButton, MouseButton.Left); + CheckMouseButton(modifier, mouseState.RightButton, MouseButton.Right); + CheckMouseButton(modifier, mouseState.MiddleButton, MouseButton.Middle); + CheckMouseButton(modifier, mouseState.XButton1, MouseButton.X1); + CheckMouseButton(modifier, mouseState.XButton2, MouseButton.X2); CheckMouseScrollWheel(mouseState.ScrollWheelValue, mouseState.HorizontalScrollWheelValue); @@ -363,7 +363,7 @@ public override void Update(TimeSpan elapsed) ApplicationContext.Context.Value?.Logger.LogTrace("{0} -> {1}", key.Key, key.Value); Interface.Interface.GwenInput.ProcessMessage( new GwenInputMessage( - IntersectInput.InputEvent.KeyDown, GetMousePosition(), (int) MouseButtons.None, key.Key + IntersectInput.InputEvent.KeyDown, GetMousePosition(), MouseButton.None, key.Key ) ); @@ -373,7 +373,7 @@ public override void Update(TimeSpan elapsed) { Interface.Interface.GwenInput.ProcessMessage( new GwenInputMessage( - IntersectInput.InputEvent.KeyUp, GetMousePosition(), (int) MouseButtons.None, key.Key + IntersectInput.InputEvent.KeyUp, GetMousePosition(), MouseButton.None, key.Key ) ); @@ -395,7 +395,7 @@ public override void Update(TimeSpan elapsed) { Interface.Interface.GwenInput.ProcessMessage( new GwenInputMessage( - IntersectInput.InputEvent.KeyUp, GetMousePosition(), (int) MouseButtons.None, key.Key + IntersectInput.InputEvent.KeyUp, GetMousePosition(), MouseButton.None, key.Key ) ); @@ -403,11 +403,11 @@ public override void Update(TimeSpan elapsed) } } - CheckMouseButton(modifier, ButtonState.Released, MouseButtons.Left); - CheckMouseButton(modifier, ButtonState.Released, MouseButtons.Right); - CheckMouseButton(modifier, ButtonState.Released, MouseButtons.Middle); - CheckMouseButton(modifier, ButtonState.Released, MouseButtons.X1); - CheckMouseButton(modifier, ButtonState.Released, MouseButtons.X2); + CheckMouseButton(modifier, ButtonState.Released, MouseButton.Left); + CheckMouseButton(modifier, ButtonState.Released, MouseButton.Right); + CheckMouseButton(modifier, ButtonState.Released, MouseButton.Middle); + CheckMouseButton(modifier, ButtonState.Released, MouseButton.X1); + CheckMouseButton(modifier, ButtonState.Released, MouseButton.X2); mLastKeyboardState = new KeyboardState(); mLastMouseState = new MouseState(); } diff --git a/Intersect.Client.Framework/File Management/GameContentManager.cs b/Intersect.Client.Framework/File Management/GameContentManager.cs index 34444a2f4b..7927a132a2 100644 --- a/Intersect.Client.Framework/File Management/GameContentManager.cs +++ b/Intersect.Client.Framework/File Management/GameContentManager.cs @@ -25,7 +25,11 @@ public enum UI Debug, } - public static GameContentManager Current { get; private set; } + public static GameContentManager Current + { + get => _current ?? throw new InvalidOperationException("Content manager not initialized"); + private set => _current = value; + } protected readonly Dictionary mAnimationDict = []; @@ -62,6 +66,7 @@ public enum UI protected readonly Dictionary mTexturePackDict = []; protected readonly Dictionary mTilesetDict = []; + private static GameContentManager? _current; public Dictionary> Content => Textures; @@ -320,7 +325,7 @@ public bool TryGetShader(string shaderName, [NotNullWhen(true)] out GameShader? return mShaderDict?.GetValueOrDefault(name.ToLower()); } - public virtual GameFont GetFont(string? name, int size) + public virtual GameFont? GetFont(string? name, int size) { if (name == null) { @@ -332,7 +337,7 @@ public virtual GameFont GetFont(string? name, int size) .FirstOrDefault(t => t.GetSize() == size); } - public virtual GameAudioSource GetMusic(string name) + public virtual GameAudioSource? GetMusic(string name) { if (string.IsNullOrEmpty(name)) { @@ -347,7 +352,7 @@ public virtual GameAudioSource GetMusic(string name) return mMusicDict.TryGetValue(name.ToLower(), out var music) ? music as GameAudioSource : default; } - public virtual GameAudioSource GetSound(string name) + public virtual GameAudioSource? GetSound(string name) { if (string.IsNullOrEmpty(name)) { diff --git a/Intersect.Client.Framework/GenericClasses/KeysExtensions.cs b/Intersect.Client.Framework/GenericClasses/KeysExtensions.cs index 8a3d53b805..87dac21147 100644 --- a/Intersect.Client.Framework/GenericClasses/KeysExtensions.cs +++ b/Intersect.Client.Framework/GenericClasses/KeysExtensions.cs @@ -4,29 +4,29 @@ namespace Intersect.Client.Framework.GenericClasses; public static class KeysExtensions { - public static bool TryGetMouseButton(this Keys key, out MouseButtons mouseButton) + public static bool TryGetMouseButton(this Keys key, out MouseButton mouseButton) { // ReSharper disable once SwitchStatementHandlesSomeKnownEnumValuesWithDefault switch (key) { case Keys.LButton: - mouseButton = MouseButtons.Left; + mouseButton = MouseButton.Left; return true; case Keys.RButton: - mouseButton = MouseButtons.Right; + mouseButton = MouseButton.Right; return true; case Keys.MButton: - mouseButton = MouseButtons.Middle; + mouseButton = MouseButton.Middle; return true; case Keys.XButton1: - mouseButton = MouseButtons.X1; + mouseButton = MouseButton.X1; return true; case Keys.XButton2: - mouseButton = MouseButtons.X2; + mouseButton = MouseButton.X2; return true; default: diff --git a/Intersect.Client.Framework/GenericClasses/Rectangle.cs b/Intersect.Client.Framework/GenericClasses/Rectangle.cs index c640074e01..0f5dc1e0e3 100644 --- a/Intersect.Client.Framework/GenericClasses/Rectangle.cs +++ b/Intersect.Client.Framework/GenericClasses/Rectangle.cs @@ -37,6 +37,14 @@ public Rectangle(Point position, Point size) Size = size; } + public Rectangle(Rectangle other) + { + X = other.X; + Y = other.Y; + Width = other.Width; + Height = other.Height; + } + public static Rectangle Intersect(Rectangle a, Rectangle b) { // MS.NET returns a non-empty rectangle if the two rectangles diff --git a/Intersect.Client.Framework/Gwen/Control/Base.cs b/Intersect.Client.Framework/Gwen/Control/Base.cs index 2cc34b6d5e..a164fe973f 100644 --- a/Intersect.Client.Framework/Gwen/Control/Base.cs +++ b/Intersect.Client.Framework/Gwen/Control/Base.cs @@ -11,18 +11,21 @@ using Newtonsoft.Json; using Newtonsoft.Json.Linq; using Intersect.Client.Framework.Gwen.Renderer; +using Intersect.Client.Framework.Input; using Intersect.Core; +using Intersect.Framework; using Intersect.Framework.Reflection; +using Intersect.Framework.Serialization; using Microsoft.Extensions.Logging; namespace Intersect.Client.Framework.Gwen.Control; - /// /// Base control class. /// public partial class Base : IDisposable { + private const string PropertyNameInnerPanel = "InnerPanel"; private bool _inheritParentEnablementProperties; @@ -81,7 +84,8 @@ internal bool InheritParentEnablementProperties private bool mDisposed; - private Pos mDock; + private Pos _dock; + private Padding _dockChildSpacing; private Package mDragAndDrop_package; @@ -105,9 +109,9 @@ internal bool InheritParentEnablementProperties private Margin mMargin; - private Point mMaximumSize = default; + private Point _maximumSize = default; - private Point mMinimumSize = default; + private Point _minimumSize = default; private bool mMouseInputEnabled; @@ -137,7 +141,7 @@ internal bool InheritParentEnablementProperties public virtual string? TooltipFontName { - get => mToolTipFont?.Name; + get => _tooltipFont?.Name; set { if (value == TooltipFontName) @@ -145,13 +149,13 @@ public virtual string? TooltipFontName return; } - ToolTipFont = GameContentManager.Current.GetFont(value, ToolTipFont?.Size ?? 10); + TooltipFont = GameContentManager.Current.GetFont(value, TooltipFont?.Size ?? 10); } } public virtual int TooltipFontSize { - get => mToolTipFont?.Size ?? 10; + get => _tooltipFont?.Size ?? 10; set { if (value == TooltipFontSize) @@ -159,7 +163,7 @@ public virtual int TooltipFontSize return; } - ToolTipFont = GameContentManager.Current.GetFont(ToolTipFont?.Name, value); + TooltipFont = GameContentManager.Current.GetFont(TooltipFont?.Name, value); } } @@ -210,9 +214,9 @@ public virtual Color? TooltipTextColor } } - private GameFont mToolTipFont; + private GameFont? _tooltipFont; - private string mToolTipFontInfo; + private string? _tooltipFontInfo; private object mUserData; @@ -259,13 +263,13 @@ public Base(Base? parent = default, string? name = default) /// /// Font. /// - public GameFont ToolTipFont + public GameFont? TooltipFont { - get => mToolTipFont; + get => _tooltipFont; set { - mToolTipFont = value; - mToolTipFontInfo = $"{value?.GetName()},{value?.GetSize()}"; + _tooltipFont = value; + _tooltipFontInfo = value == null ? null : $"{value.GetName()},{value.GetSize()}"; } } @@ -274,8 +278,7 @@ public GameFont ToolTipFont /// /// Returns true if any on click events are set. /// - internal bool ClickEventAssigned => - Clicked != null || RightClicked != null || DoubleClicked != null || DoubleRightClicked != null; + internal bool ClickEventAssigned => Clicked != null || DoubleClicked != null; /// /// Logical list of children. If InnerPanel is not null, returns InnerPanel's children. @@ -338,26 +341,41 @@ public Alignments[] Alignment } } + public virtual bool IsBlockingInput => true; + /// /// Dock position. /// public Pos Dock { - get => mDock; + get => _dock; set { - if (mDock == value) + if (_dock == value) { return; } - mDock = value; + _dock = value; Invalidate(); InvalidateParent(); } } + public Padding DockChildSpacing + { + get => _dockChildSpacing; + set + { + _dockChildSpacing = value; + if (_innerPanel is { } innerPanel) + { + innerPanel.DockChildSpacing = value; + } + } + } + protected bool HasSkin => mSkin != null || (mParent?.HasSkin ?? false); /// @@ -566,27 +584,10 @@ public object UserData /// /// Indicates whether the control is disabled. /// - public virtual bool IsDisabled + public bool IsDisabled { get => (_inheritParentEnablementProperties && Parent != default) ? Parent.IsDisabled : _disabled; - set - { - if (value == _disabled) - { - return; - } - - if (_inheritParentEnablementProperties) - { - _disabled = Parent?.IsDisabled ?? value; - } - else - { - _disabled = value; - } - - Invalidate(); - } + set => SetAndDoIfChanged(ref _disabled, value, Invalidate); } /// @@ -629,9 +630,11 @@ protected virtual void OnVisibilityChanged(object? sender, VisibilityChangedEven } - public virtual bool IsHiddenByTree => mHidden || (Parent?.IsHidden ?? false); + protected virtual Point InnerPanelSizeFrom(Point size) => size; - public virtual bool IsDisabledByTree => _disabled || (Parent?.IsDisabled ?? false); + public virtual bool IsHiddenByTree => mHidden || (Parent?.IsHiddenByTree ?? false); + + public virtual bool IsDisabledByTree => _disabled || (Parent?.IsDisabledByTree ?? false); /// /// Determines whether the control's position should be restricted to parent's bounds. @@ -731,6 +734,20 @@ public string? Name /// public Rectangle Bounds => mBounds; + public Rectangle OuterBounds + { + get + { + var margin = mMargin; + Rectangle outerBounds = new(mBounds); + outerBounds.X -= margin.Left; + outerBounds.Y -= margin.Top; + outerBounds.Width += margin.Left + margin.Right; + outerBounds.Height += margin.Top + margin.Bottom; + return outerBounds; + } + } + public bool ClipContents { get; set; } = true; /// @@ -748,8 +765,15 @@ public string? Name /// public Point MinimumSize { - get => mMinimumSize; - set => mMinimumSize = value; + get => _minimumSize; + set + { + _minimumSize = value; + if (_innerPanel != null) + { + _innerPanel.MinimumSize = InnerPanelSizeFrom(value); + } + } } /// @@ -757,13 +781,13 @@ public Point MinimumSize /// public Point MaximumSize { - get => mMaximumSize; + get => _maximumSize; set { - mMaximumSize = value; + _maximumSize = value; if (_innerPanel != null) { - _innerPanel.MaximumSize = value; + _innerPanel.MaximumSize = InnerPanelSizeFrom(value); } } } @@ -826,6 +850,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; @@ -965,12 +993,32 @@ public virtual string GetJsonUI(bool isRoot = false) return JsonConvert.SerializeObject(GetJson(isRoot), Formatting.Indented); } - public virtual JObject GetJson(bool isRoot = default) + public virtual JObject? GetJson(bool isRoot = false, bool onlySerializeIfNotEmpty = false) { - var alignments = new List(); - foreach (var alignment in mAlignments) + JObject children = new(); + + // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator + foreach (var component in mChildren) + { + if (component == _innerPanel) + { + continue; + } + + if (component == Tooltip) + { + continue; + } + + if (!string.IsNullOrEmpty(component.Name) && children[component.Name] == null) + { + children.Add(component.Name, component.GetJson()); + } + } + + if (onlySerializeIfNotEmpty && !children.HasValues) { - alignments.Add(alignment.ToString()); + return null; } isRoot |= Parent == default; @@ -980,42 +1028,35 @@ public virtual JObject GetJson(bool isRoot = default) : mBounds; var serializedProperties = new JObject( - new JProperty("Bounds", Rectangle.ToString(boundsToWrite)), - new JProperty("Padding", Padding.ToString(mPadding)), + new JProperty(nameof(Bounds), Rectangle.ToString(boundsToWrite)), + new JProperty(nameof(Dock), Dock.ToString()), + new JProperty(nameof(Padding), Padding.ToString(mPadding)), new JProperty("AlignmentEdgeDistances", Padding.ToString(mAlignmentDistance)), new JProperty("AlignmentTransform", mAlignmentTransform.ToString()), - new JProperty("Margin", mMargin.ToString()), - new JProperty("RenderColor", Color.ToString(mColor)), - new JProperty("Alignments", string.Join(",", alignments.ToArray())), + new JProperty(nameof(Margin), mMargin.ToString()), + new JProperty(nameof(RenderColor), Color.ToString(mColor)), + new JProperty(nameof(Alignments), string.Join(",", mAlignments.ToArray())), new JProperty("DrawBackground", mDrawBackground), - new JProperty("MinimumSize", mMinimumSize.ToString()), - new JProperty("MaximumSize", mMaximumSize.ToString()), + new JProperty(nameof(MinimumSize), _minimumSize.ToString()), + new JProperty(nameof(MaximumSize), _maximumSize.ToString()), new JProperty("Disabled", _disabled), new JProperty("Hidden", mHidden), - new JProperty("RestrictToParent", mRestrictToParent), - new JProperty("MouseInputEnabled", mMouseInputEnabled), + new JProperty(nameof(RestrictToParent), mRestrictToParent), + new JProperty(nameof(MouseInputEnabled), mMouseInputEnabled), new JProperty("HideToolTip", mHideToolTip), new JProperty("ToolTipBackground", _tooltipBackgroundName), - new JProperty("ToolTipFont", mToolTipFontInfo), + new JProperty("ToolTipFont", _tooltipFontInfo), new JProperty( nameof(TooltipTextColor), _tooltipTextColor == null ? JValue.CreateNull() : Color.ToString(_tooltipTextColor) ) ); - JObject children = new(); - - // ReSharper disable once ForeachCanBePartlyConvertedToQueryUsingAnotherGetEnumerator - foreach (var component in mChildren) + if (_innerPanel is { } innerPanel) { - if (component == Tooltip) + if (innerPanel.GetJson(onlySerializeIfNotEmpty: true) is {} serializedInnerPanel) { - continue; - } - - if (!string.IsNullOrEmpty(component.Name) && children[component.Name] == null) - { - children.Add(component.Name, component.GetJson()); + serializedProperties.Add(PropertyNameInnerPanel, serializedInnerPanel); } } @@ -1029,11 +1070,14 @@ public virtual JObject GetJson(bool isRoot = default) public virtual JObject FixJson(JObject json) { - var children = json.Property("Children"); - if (children != null) + if (json.Remove(PropertyNameInnerPanel, out var tokenInnerPanel)) + { + json.Add(PropertyNameInnerPanel, tokenInnerPanel); + } + + if (json.Remove(nameof(Children), out var tokenChildren)) { - json.Remove("Children"); - json.Add(children); + json.Add(nameof(Children), tokenChildren); } return json; @@ -1085,12 +1129,17 @@ public void LoadJsonUi(GameContentManager.UI stage, string? resolution, bool sav }); } - public virtual void LoadJson(JToken obj, bool isRoot = default) + public virtual void LoadJson(JToken token, bool isRoot = default) { - if (obj["Alignments"] != null) + if (token is not JObject obj) + { + return; + } + + if (obj[nameof(Alignments)] != null) { RemoveAlignments(); - var alignments = ((string) obj["Alignments"]).Split(','); + var alignments = ((string) obj[nameof(Alignments)]).Split(','); foreach (var alignment in alignments) { switch (alignment.ToLower()) @@ -1133,9 +1182,9 @@ public virtual void LoadJson(JToken obj, bool isRoot = default) } } - if (obj["Bounds"] != null) + if (obj[nameof(Bounds)] != null) { - mBoundsOnDisk = Rectangle.FromString((string)obj["Bounds"]); + mBoundsOnDisk = Rectangle.FromString((string)obj[nameof(Bounds)]); isRoot = isRoot || Parent == default; if (isRoot) { @@ -1147,9 +1196,14 @@ public virtual void LoadJson(JToken obj, bool isRoot = default) } } - if (obj["Padding"] != null) + if (obj.TryGet(nameof(Dock), out var dock)) + { + Dock = dock; + } + + if (obj[nameof(Padding)] != null) { - Padding = Padding.FromString((string) obj["Padding"]); + Padding = Padding.FromString((string) obj[nameof(Padding)]); } if (obj["AlignmentEdgeDistances"] != null) @@ -1162,14 +1216,14 @@ public virtual void LoadJson(JToken obj, bool isRoot = default) mAlignmentTransform = Point.FromString((string) obj["AlignmentTransform"]); } - if (obj["Margin"] != null) + if (obj[nameof(Margin)] != null) { - Margin = Margin.FromString((string) obj["Margin"]); + Margin = Margin.FromString((string) obj[nameof(Margin)]); } - if (obj["RenderColor"] != null) + if (obj[nameof(RenderColor)] != null) { - RenderColor = Color.FromString((string) obj["RenderColor"]); + RenderColor = Color.FromString((string) obj[nameof(RenderColor)]); } if (obj["DrawBackground"] != null) @@ -1177,14 +1231,14 @@ public virtual void LoadJson(JToken obj, bool isRoot = default) ShouldDrawBackground = (bool) obj["DrawBackground"]; } - if (obj["MinimumSize"] != null) + if (obj[nameof(MinimumSize)] != null) { - MinimumSize = Point.FromString((string) obj["MinimumSize"]); + MinimumSize = Point.FromString((string) obj[nameof(MinimumSize)]); } - if (obj["MaximumSize"] != null) + if (obj[nameof(MaximumSize)] != null) { - MaximumSize = Point.FromString((string) obj["MaximumSize"]); + MaximumSize = Point.FromString((string) obj[nameof(MaximumSize)]); } if (obj["Disabled"] != null) @@ -1197,14 +1251,14 @@ public virtual void LoadJson(JToken obj, bool isRoot = default) IsHidden = (bool) obj["Hidden"]; } - if (obj["RestrictToParent"] != null) + if (obj[nameof(RestrictToParent)] != null) { - RestrictToParent = (bool) obj["RestrictToParent"]; + RestrictToParent = (bool) obj[nameof(RestrictToParent)]; } - if (obj["MouseInputEnabled"] != null) + if (obj[nameof(MouseInputEnabled)] != null) { - MouseInputEnabled = (bool) obj["MouseInputEnabled"]; + MouseInputEnabled = (bool) obj[nameof(MouseInputEnabled)]; } if (obj["HideToolTip"] != null && (bool) obj["HideToolTip"]) @@ -1226,11 +1280,22 @@ public virtual void LoadJson(JToken obj, bool isRoot = default) _tooltipBackground = texture; } - if (obj["ToolTipFont"] != null && obj["ToolTipFont"].Type != JTokenType.Null) + if (obj.TryGetValue(nameof(TooltipFont), out var tokenTooltipFont) && tokenTooltipFont is JValue { Type: JTokenType.String } valueTooltipFont) { - var fontArr = ((string) obj["ToolTipFont"]).Split(','); - mToolTipFontInfo = (string) obj["ToolTipFont"]; - mToolTipFont = GameContentManager.Current.GetFont(fontArr[0], int.Parse(fontArr[1])); + if (valueTooltipFont.Value() is { } fontInfo) + { + var fontParts = fontInfo.Split(',', StringSplitOptions.RemoveEmptyEntries | StringSplitOptions.TrimEntries); + var fontName = fontParts.FirstOrDefault(); + if (!int.TryParse(fontParts.Skip(1).FirstOrDefault(), out var fontSize)) + { + fontSize = 10; + } + TooltipFont = GameContentManager.Current?.GetFont(fontName, fontSize); + } + else + { + TooltipFont = null; + } } if (obj[nameof(TooltipTextColor)] is JValue { Type: JTokenType.String } tooltipTextColorValue) @@ -1244,28 +1309,37 @@ public virtual void LoadJson(JToken obj, bool isRoot = default) UpdateToolTipProperties(); - if (HasNamedChildren()) + if (_innerPanel is { } innerPanel && obj.TryGetValue(PropertyNameInnerPanel, out var tokenInnerPanel)) { - if (obj["Children"] != null) + innerPanel.LoadJson(tokenInnerPanel); + } + + if (obj[nameof(Children)] != null) + { + var children = obj[nameof(Children)]; + foreach (JProperty tkn in children) { - var children = obj["Children"]; - foreach (JProperty tkn in children) + var name = tkn.Name; + foreach (var ctrl in mChildren) { - var name = tkn.Name; - foreach (var ctrl in mChildren) + if (ctrl.Name == name) { - if (ctrl.Name == name) - { - ctrl.LoadJson(tkn.First); - } + ctrl.LoadJson(tkn.First); } } } } + + Invalidate(alsoInvalidateParent: true); } public virtual void ProcessAlignments() { + if (this is Label { Text: "Debug" }) + { + this.ToString(); + } + foreach (var alignment in mAlignments) { switch (alignment) @@ -1320,37 +1394,31 @@ private bool HasNamedChildren() /// /// Invoked when mouse pointer enters the control. /// - public event GwenEventHandler HoverEnter; + public event GwenEventHandler? HoverEnter; /// /// Invoked when mouse pointer leaves the control. /// - public event GwenEventHandler HoverLeave; + public event GwenEventHandler? HoverLeave; /// /// Invoked when control's bounds have been changed. /// public event GwenEventHandler? BoundsChanged; - /// - /// Invoked when the control has been left-clicked. - /// - public virtual event GwenEventHandler Clicked; + public virtual event GwenEventHandler? MouseDown; - /// - /// Invoked when the control has been double-left-clicked. - /// - public virtual event GwenEventHandler DoubleClicked; + public virtual event GwenEventHandler? MouseUp; /// - /// Invoked when the control has been right-clicked. + /// Invoked when the control has been left-clicked. /// - public virtual event GwenEventHandler RightClicked; + public virtual event GwenEventHandler? Clicked; /// - /// Invoked when the control has been double-right-clicked. + /// Invoked when the control has been double-left-clicked. /// - public virtual event GwenEventHandler DoubleRightClicked; + public virtual event GwenEventHandler? DoubleClicked; #if DIAGNOSTIC ~Base() @@ -1396,6 +1464,7 @@ public override string ToString() /// /// Enables the control. /// + // TODO: Remove public void Enable() { IsDisabled = false; @@ -1404,6 +1473,7 @@ public void Enable() /// /// Disables the control. /// + // TODO: Remove public virtual void Disable() { IsDisabled = true; @@ -1472,14 +1542,12 @@ public virtual void SetToolTipText(string? text) return; } - labelTooltip = new Label(this, name: "Tooltip") + labelTooltip = new Label(this, name: nameof(Tooltip)) { AutoSizeToContents = true, - Font = mToolTipFont ?? GameContentManager.Current?.GetFont("sourcesansproblack", 10), + Font = _tooltipFont ?? GameContentManager.Current?.GetFont("sourcesansproblack", 10), MaximumSize = new Point(300, 0), - Padding = new Padding( - 5, - 3, + TextPadding = new Padding( 5, 3 ), @@ -1500,9 +1568,9 @@ protected virtual void UpdateToolTipProperties() if (Tooltip != null && Tooltip is Label tooltip) { tooltip.TextColorOverride = _tooltipTextColor ?? Skin.Colors.TooltipText; - if (mToolTipFont != null) + if (_tooltipFont != null) { - tooltip.Font = mToolTipFont; + tooltip.Font = _tooltipFont; } tooltip.ToolTipBackground = _tooltipBackground; @@ -1545,8 +1613,24 @@ protected virtual void InvalidateChildren(bool recursive = false) /// public virtual void Invalidate() { - mNeedsLayout = true; - mCacheTextureDirty = true; + if (!mNeedsLayout) + { + mNeedsLayout = true; + } + + if (!mCacheTextureDirty) + { + mCacheTextureDirty = true; + } + } + + public void Invalidate(bool alsoInvalidateParent) + { + Invalidate(); + if (alsoInvalidateParent) + { + InvalidateParent(); + } } public virtual void MoveBefore(Base other) @@ -1920,47 +2004,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); } /// @@ -2003,6 +2099,8 @@ public virtual void SetPosition(Point point) => SetBounds( /// True if bounds changed. public virtual bool SetSize(int width, int height) => SetBounds(X, Y, width, height); + public bool SetSize(Point point) => SetSize(point.X, point.Y); + /// /// Sets the control bounds. /// @@ -2041,6 +2139,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,19 +2149,34 @@ 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 (oldBounds.Size != mBounds.Size) + if (newBounds.Width > 100000 || newBounds.Height > 100000) + { + ApplicationContext.CurrentContext.Logger.LogWarning( + "Extremely large component resize '{ComponentName}' {OldBounds} => {NewBounds}", + CanonicalName, + oldBounds.Size, + newBounds.Size + ); + } + + mBounds = newBounds; + + if (oldBounds.Size != newBounds.Size) { ProcessAlignments(); } - OnBoundsChanged(oldBounds); + OnBoundsChanged(oldBounds, newBounds); BoundsChanged?.Invoke(this, EventArgs.Empty); @@ -2117,11 +2233,12 @@ public virtual void Position(Pos pos, int xPadding = 0, int yPadding = 0) /// Handler invoked when control's bounds change. /// /// Old bounds. - protected virtual void OnBoundsChanged(Rectangle oldBounds) + /// + protected virtual void OnBoundsChanged(Rectangle oldBounds, Rectangle newBounds) { //Anything that needs to update on size changes //Iterate my children and tell them I've changed - Parent?.OnChildBoundsChanged(oldBounds, this); + Parent?.OnChildBoundsChanged(this, oldBounds, newBounds); if (mBounds.Width != oldBounds.Width || mBounds.Height != oldBounds.Height) { @@ -2146,7 +2263,7 @@ protected virtual void OnScaleChanged() /// /// Handler invoked when control children's bounds change. /// - protected virtual void OnChildBoundsChanged(Rectangle oldChildBounds, Base child) + protected virtual void OnChildBoundsChanged(Base child, Rectangle oldChildBounds, Rectangle newChildBounds) { } @@ -2211,14 +2328,12 @@ protected virtual void DoCacheRender(Skin.Base skin, Base master) // Render the control Render(skin); - // Render the children (Reverse). - for (int i = 0; i < mChildren.Count; i++) + var childrenToRender = mChildren.ToArray(); + childrenToRender = OrderChildrenForRendering(childrenToRender.Where(child => child.IsVisible)); + + foreach (var child in childrenToRender) { - var child = mChildren[i]; - if (!child.IsHidden) - { - child.DoCacheRender(skin, master); - } + child.DoCacheRender(skin, master); } // Finish the cache texture if necessary @@ -2344,11 +2459,6 @@ protected virtual void RenderRecursive(Skin.Base skin, Rectangle clipRect) foreach (var child in childrenToRender) { - if (!child.IsVisible) - { - continue; - } - child.DoRender(skin); } @@ -2419,6 +2529,11 @@ protected virtual bool OnMouseWheeled(int delta) /// internal bool InputMouseWheeled(int delta) { + if (IsDisabledByTree) + { + return false; + } + return OnMouseWheeled(delta); } @@ -2441,6 +2556,11 @@ protected virtual bool OnMouseHWheeled(int delta) /// internal bool InputMouseHWheeled(int delta) { + if (IsDisabledByTree) + { + return false; + } + return OnMouseHWheeled(delta); } @@ -2460,95 +2580,140 @@ protected virtual void OnMouseMoved(int x, int y, int dx, int dy) /// internal void InputMouseMoved(int x, int y, int dx, int dy) { + if (IsDisabledByTree) + { + return; + } + OnMouseMoved(x, y, dx, dy); } - /// - /// Handler invoked on mouse click (left) event. - /// - /// X coordinate. - /// Y coordinate. - /// If set to true mouse button is down. - protected virtual void OnMouseClickedLeft(int x, int y, bool down, bool automated = false) + internal void InputNonUserMouseClicked(MouseButton mouseButton, Point mousePosition, bool isPressed) { - if (down && Clicked != null) + if (IsDisabledByTree) { - Clicked(this, new ClickedEventArgs(x, y, down)); + return; } + + OnMouseDoubleClicked(mouseButton, mousePosition, userAction: false); + Clicked?.Invoke(this, new MouseButtonState(mouseButton, mousePosition, isPressed: isPressed)); } - /// - /// Invokes left mouse click event (used by input system). - /// - internal void InputMouseClickedLeft(int x, int y, bool down, bool automated = false) + internal void InputMouseDoubleClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - OnMouseClickedLeft(x, y, down, automated); + if (IsDisabledByTree) + { + return; + } + + OnMouseDoubleClicked(mouseButton, mousePosition, userAction); + DoubleClicked?.Invoke(this, new MouseButtonState(mouseButton, mousePosition, true)); } - /// - /// Handler invoked on mouse click (right) event. - /// - /// X coordinate. - /// Y coordinate. - /// If set to true mouse button is down. - protected virtual void OnMouseClickedRight(int x, int y, bool down) + protected void PlaySound(string? name) { - if (down && RightClicked != null) + if (name == null || this.IsDisabled) + { + return; + } + + if (Canvas is not { } canvas) + { + return; + } + + name = GameContentManager.RemoveExtension(name).ToLower(); + if (GameContentManager.Current.GetSound(name) is not { } sound) + { + return; + } + + if (sound.CreateInstance() is not { } soundInstance) { - RightClicked(this, new ClickedEventArgs(x, y, down)); + return; } + + canvas.PlayAndAddSound(soundInstance); } - /// - /// Invokes right mouse click event (used by input system). - /// - internal void InputMouseClickedRight(int x, int y, bool down) + public bool IsActive { - OnMouseClickedRight(x, y, down); + get => _mouseButtonPressed.GetValueOrDefault(MouseButton.Left, false); + set => _mouseButtonPressed[MouseButton.Left] = value; } - /// - /// Handler invoked on mouse double click (left) event. - /// - /// X coordinate. - /// Y coordinate. - protected virtual void OnMouseDoubleClickedLeft(int x, int y) - { - // [omeg] should this be called? - // [halfofastaple] Maybe. Technically, a double click is still technically a single click. However, this shouldn't be called here, and - // Should be called by the event handler. - OnMouseClickedLeft(x, y, true); + public bool IsMouseButtonActive(MouseButton mouseButton) => + _mouseButtonPressed.GetValueOrDefault(mouseButton, false); - DoubleClicked?.Invoke(this, new ClickedEventArgs(x, y, true)); + private readonly Dictionary _mouseButtonPressed = []; + + public IReadOnlyDictionary MouseButtonPressed => _mouseButtonPressed; + + protected virtual void OnMouseClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) + { } - /// - /// Invokes left double mouse click event (used by input system). - /// - internal void InputMouseDoubleClickedLeft(int x, int y) + protected virtual void OnMouseDoubleClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - OnMouseDoubleClickedLeft(x, y); } - /// - /// Handler invoked on mouse double click (right) event. - /// - /// X coordinate. - /// Y coordinate. - protected virtual void OnMouseDoubleClickedRight(int x, int y) + protected virtual void OnMouseDown(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - // [halfofastaple] See: OnMouseDoubleClicked for discussion on triggering single clicks in a double click event - OnMouseClickedRight(x, y, true); + } - DoubleRightClicked?.Invoke(this, new ClickedEventArgs(x, y, true)); + protected virtual void OnMouseUp(MouseButton mouseButton, Point mousePosition, bool userAction = true) + { } - /// - /// Invokes right double mouse click event (used by input system). - /// - internal void InputMouseDoubleClickedRight(int x, int y) + public bool KeepFocusOnMouseExit { get; set; } + + internal void InputMouseButtonState(MouseButton mouseButton, Point mousePosition, bool pressed, bool userAction = true) { - OnMouseDoubleClickedRight(x, y); + var emitsEvents = !IsDisabledByTree; + var mouseButtonStateArgs = new MouseButtonState(mouseButton, mousePosition.X, mousePosition.Y, pressed); + var wasActive = IsMouseButtonActive(mouseButton); + if (pressed) + { + _mouseButtonPressed[mouseButton] = true; + InputHandler.MouseFocus = this; + + if (!wasActive) + { + OnMouseDown(mouseButton, mousePosition, userAction); + if (emitsEvents) + { + MouseDown?.Invoke(this, mouseButtonStateArgs); + } + } + } + else + { + if (IsHovered && wasActive) + { + OnMouseClicked(mouseButton, mousePosition, userAction); + if (emitsEvents) + { + Clicked?.Invoke(this, mouseButtonStateArgs); + } + } + + _mouseButtonPressed[mouseButton] = false; + InputHandler.MouseFocus = null; + + if (wasActive) + { + OnMouseUp(mouseButton, mousePosition, userAction); + if (emitsEvents) + { + MouseUp?.Invoke(this, mouseButtonStateArgs); + } + } + } + + if (wasActive != pressed) + { + Redraw(); + } } /// @@ -2556,78 +2721,67 @@ internal void InputMouseDoubleClickedRight(int x, int y) /// protected virtual void OnMouseEntered() { - if (HoverEnter != null) - { - HoverEnter.Invoke(this, EventArgs.Empty); - } + HoverEnter?.Invoke(this, EventArgs.Empty); + + Redraw(); + } + /// + /// Invokes mouse enter event (used by input system). + /// + internal void InputMouseEntered() + { if (Tooltip != null) { ToolTip.Enable(this); } - else if (Parent != null && Parent.Tooltip != null) + else if (Parent is { Tooltip: not null }) { ToolTip.Enable(Parent); } - Redraw(); - } - - protected void PlaySound(string filename) - { - if (filename == null || this.IsDisabled) + if (IsDisabledByTree) { return; } - filename = GameContentManager.RemoveExtension(filename).ToLower(); - var sound = GameContentManager.Current.GetSound(filename); - if (sound != null) - { - var soundInstance = sound.CreateInstance(); - if (soundInstance != null) - { - Canvas.PlayAndAddSound(soundInstance); - } - } + OnMouseEntered(); } /// - /// Invokes mouse enter event (used by input system). + /// Handler invoked on mouse cursor leaving control's bounds. /// - internal void InputMouseEntered() + protected virtual void OnMouseLeft() { - OnMouseEntered(); + IsActive = false; + + HoverLeave?.Invoke(this, EventArgs.Empty); + + Redraw(); } /// - /// Handler invoked on mouse cursor leaving control's bounds. + /// Invokes mouse leave event (used by input system). /// - protected virtual void OnMouseLeft() + internal void InputMouseLeft() { - if (HoverLeave != null) - { - HoverLeave.Invoke(this, EventArgs.Empty); - } - if (Tooltip != null) { ToolTip.Disable(this); } - else if (Parent != null && Parent.Tooltip != null) + else if (Parent is { Tooltip: not null }) { ToolTip.Disable(Parent); } - Redraw(); - } + if (IsDisabledByTree) + { + return; + } - /// - /// Invokes mouse leave event (used by input system). - /// - internal void InputMouseLeft() - { OnMouseLeft(); + + IsActive = false; } /// @@ -2692,33 +2846,48 @@ protected virtual void OnChildTouched(Base control) /// /// Gets a child by its coordinates. /// - /// Child X. - /// Child Y. + /// The local X coordinate to check. + /// The local Y coordinate to check. + /// /// Control or null if not found. - public virtual Base GetControlAt(int x, int y) + public Base? GetComponentAt(int x, int y, ComponentStateFilters filters = default) { - // Return null if control is hidden or coordinates are outside the control's bounds. - if (IsHidden || x < 0 || y < 0 || x >= Width || y >= Height) + // If it's out of our bounds, return null + if (x < 0 || Width <= x || y < 0 || Height <= y) { return null; } + // If we and/or an ancestor are hidden, return null if we aren't explicitly allowing hidden components + if (IsHidden) + { + if (!filters.HasFlag(ComponentStateFilters.IncludeHidden)) + { + return null; + } + } + // Check children in reverse order (last added first). - for (int i = mChildren.Count - 1; i >= 0; i--) + for (int childIndex = mChildren.Count - 1; childIndex >= 0; childIndex--) { - var child = mChildren[i]; - var found = child.GetControlAt(x - child.X, y - child.Y); - if (found != null) + var child = mChildren[childIndex]; + if (child.GetComponentAt(x - child.X, y - child.Y, filters) is { } descendant) { - return found; + return descendant; } } - // Return control if it is mouse input enabled, otherwise return null. - return MouseInputEnabled ? this : null; + // By default, we only return components that are mouse-input enabled, but if the filters include + // those components explicitly, we can return them too. This is particularly useful for debugging. + if (MouseInputEnabled || filters.HasFlag(ComponentStateFilters.IncludeMouseInputDisabled)) + { + return this; + } + + return null; } - public virtual Base GetControlAt(Point point) => GetControlAt(point.X, point.Y); + public Base? GetComponentAt(Point point, ComponentStateFilters filters = default) => GetComponentAt(point.X, point.Y, filters); /// /// Lays out the control's interior according to alignment, padding, dock etc. @@ -2757,7 +2926,8 @@ protected virtual void RecurseLayout(Skin.Base skin) return; } - foreach (var child in mChildren) + var children = mChildren.ToArray(); + foreach (var child in children) { child.Prelayout(skin); } @@ -2774,15 +2944,17 @@ 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) + var dockChildSpacing = DockChildSpacing; + + foreach (var child in children) { if (child.ShouldSkipLayout) { @@ -2803,70 +2975,104 @@ protected virtual void RecurseLayout(Skin.Base skin) var childOuterWidth = childMarginH + child.Width; var childOuterHeight = childMarginV + child.Height; - if (childDock.HasFlag(Pos.Top)) + var availableWidth = remainingBounds.Width - childMarginH; + var availableHeight = remainingBounds.Height - childMarginV; + + 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, - ownBounds.Width - childMarginH, - child.Height + remainingBounds.X + childMargin.Left, + y, + child.Width, + height ); - ownBounds.Y += childOuterHeight; - ownBounds.Height -= childOuterHeight; + var boundsDeltaX = childOuterWidth + dockChildSpacing.Left; + remainingBounds.X += boundsDeltaX; + remainingBounds.Width -= boundsDeltaX; } - if (childDock.HasFlag(Pos.Left)) + if (childDock.HasFlag(Pos.Right)) { + var height = childFitsContents + ? child.Height + : availableHeight; + + var offsetFromRight = child.Width + childMargin.Right + dockChildSpacing.Right; child.SetBounds( - ownBounds.X + childMargin.Left, - ownBounds.Y + childMargin.Top, + remainingBounds.X + remainingBounds.Width - offsetFromRight, + remainingBounds.Y + childMargin.Top, child.Width, - ownBounds.Height - childMarginV + height ); - ownBounds.X += childOuterWidth; - ownBounds.Width -= childOuterWidth; + remainingBounds.Width -= childOuterWidth + dockChildSpacing.Right; } - if (childDock.HasFlag(Pos.Right)) + if (childDock.HasFlag(Pos.Top) && !childDock.HasFlag(Pos.Left) && !childDock.HasFlag(Pos.Right)) { + var width = childFitsContents + ? child.Width + : availableWidth; + child.SetBounds( - ownBounds.X + ownBounds.Width - child.Width - childMargin.Right, - ownBounds.Y + childMargin.Top, - child.Width, - ownBounds.Height - childMarginV + remainingBounds.X + childMargin.Left, + remainingBounds.Y + childMargin.Top, + width, + child.Height ); - ownBounds.Width -= childOuterWidth; + var boundsDeltaY = childOuterHeight + dockChildSpacing.Top; + remainingBounds.Y += boundsDeltaY; + remainingBounds.Height -= boundsDeltaY; } - if (childDock.HasFlag(Pos.Bottom)) + if (childDock.HasFlag(Pos.Bottom) && !childDock.HasFlag(Pos.Left) && !childDock.HasFlag(Pos.Right)) { - if (child.Name?.StartsWith("BottomBar") ?? false) - { - child.Margin.ToString(); - } + var width = childFitsContents + ? child.Width + : availableWidth; + var offsetFromBottom = child.Height + childMargin.Bottom + dockChildSpacing.Bottom; child.SetBounds( - ownBounds.Left + childMargin.Left, - ownBounds.Bottom - (child.Height + childMargin.Bottom), - ownBounds.Width - childMarginH, + remainingBounds.Left + childMargin.Left, + remainingBounds.Bottom - offsetFromBottom, + width, child.Height ); - ownBounds.Height -= childOuterHeight; + remainingBounds.Height -= childOuterHeight + dockChildSpacing.Bottom; } child.RecurseLayout(skin); } - mInnerBounds = ownBounds; + mInnerBounds = remainingBounds; // // Fill uses the left over space, so do that now. // - foreach (var child in mChildren) + foreach (var child in children) { if (child.ShouldSkipLayout) { @@ -2885,30 +3091,32 @@ 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); + var offsetFromRight = child.Width + childMargin.Right + dockChildSpacing.Right; + newPosition.X = remainingBounds.Right - offsetFromRight; } if (Pos.Bottom == (dock & (Pos.Bottom | Pos.Top))) { - newPosition.Y = ownBounds.Bottom - (childMargin.Bottom + child.Height); + var offsetFromBottom = child.Height + childMargin.Bottom + dockChildSpacing.Bottom; + newPosition.Y = remainingBounds.Bottom - offsetFromBottom; } 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 +3124,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 +3191,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); } /// @@ -3126,8 +3357,9 @@ public virtual bool DragAndDrop_CanAcceptPackage(Package p) public virtual bool SizeToChildren(bool width = true, bool height = true) { var size = GetChildrenSize(); - size.X += Padding.Right; - size.Y += Padding.Bottom; + var padding = Padding; + size.X += padding.Right + padding.Left; + size.Y += padding.Bottom + padding.Top; if (!SetSize(width ? size.X : Width, height ? size.Y : Height)) { @@ -3149,20 +3381,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.OuterBounds; + 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); } - return size; + 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 contentSize; } /// @@ -3190,9 +3444,14 @@ internal virtual bool HandleAccelerator(string accelerator) /// /// Accelerator text. /// Handler. - public void AddAccelerator(string accelerator, GwenEventHandler handler) + public void AddAccelerator(string? accelerator, GwenEventHandler handler) { - accelerator = accelerator.Trim().ToUpperInvariant(); + accelerator = accelerator?.Trim().ToUpperInvariant(); + if (string.IsNullOrWhiteSpace(accelerator)) + { + return; + } + mAccelerators[accelerator] = handler; } @@ -3236,11 +3495,7 @@ public virtual void UpdateColors() /// /// Invalidates control's parent. /// - public void InvalidateParent() - { - mParent?.Invalidate(); - mParent?.InvalidateParent(); - } + public void InvalidateParent() => mParent?.Invalidate(alsoInvalidateParent: true); /// /// Handler for keyboard events. @@ -3337,6 +3592,11 @@ protected virtual bool OnKeyPressed(Key key, bool down = true) /// internal bool InputKeyPressed(Key key, bool down = true) { + if (IsDisabledByTree) + { + return false; + } + return OnKeyPressed(key, down); } @@ -3347,6 +3607,11 @@ internal bool InputKeyPressed(Key key, bool down = true) /// True if handled. protected virtual bool OnKeyReleaseed(Key key) { + if (IsDisabledByTree) + { + return false; + } + return OnKeyPressed(key, false); } @@ -3578,11 +3843,22 @@ internal void InputCopy(Base from) internal void InputPaste(Base from) { + if (IsDisabledByTree) + { + return; + } + OnPaste(from, EventArgs.Empty); } internal void InputCut(Base from) { + if (IsDisabledByTree) + { + OnCopy(from, EventArgs.Empty); + return; + } + OnCut(from, EventArgs.Empty); } @@ -3659,6 +3935,11 @@ protected virtual bool OnChar(Char chr) internal bool InputChar(Char chr) { + if (IsDisabledByTree) + { + return false; + } + return OnChar(chr); } diff --git a/Intersect.Client.Framework/Gwen/Control/Button.cs b/Intersect.Client.Framework/Gwen/Control/Button.cs index fda504d6b1..f12f37f5e4 100644 --- a/Intersect.Client.Framework/Gwen/Control/Button.cs +++ b/Intersect.Client.Framework/Gwen/Control/Button.cs @@ -2,72 +2,29 @@ using Intersect.Client.Framework.File_Management; using Intersect.Client.Framework.Graphics; using Intersect.Client.Framework.Gwen.Input; +using Intersect.Client.Framework.Input; +using Intersect.Core; +using Intersect.Framework.Reflection; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace Intersect.Client.Framework.Gwen.Control; -public static class ButtonControlStateExtensions -{ - public static Label.ControlState ToLabelControlState(this Button.ControlState controlState) - { - return controlState switch - { - Button.ControlState.Normal => Label.ControlState.Normal, - Button.ControlState.Hovered => Label.ControlState.Hovered, - Button.ControlState.Clicked => Label.ControlState.Clicked, - Button.ControlState.Disabled => Label.ControlState.Disabled, - _ => throw new ArgumentOutOfRangeException(nameof(controlState), controlState, null), - }; - } -} - /// /// Button control. /// public partial class Button : Label { - - public enum ControlState - { - - Normal = 0, - - Hovered, - - Clicked, - - Disabled, - - } - private bool mCenterImage; - private GameTexture mClickedImage; - - private string mClickedImageFilename; - - protected string mClickSound; - - private bool mDepressed; - - private GameTexture mDisabledImage; - - private string mDisabledImageFilename; - - private GameTexture mHoverImage; - - private string mHoverImageFilename; - //Sound Effects - protected string mHoverSound; - - protected string mMouseDownSound; - - protected string mMouseUpSound; + private string? mClickSound; + private string? mHoverSound; + private string? mMouseDownSound; + private string? mMouseUpSound; - private GameTexture mNormalImage; - - private string mNormalImageFilename; + private readonly Dictionary _stateTextures = []; + private readonly Dictionary _stateTextureNames = []; private bool mToggle; @@ -77,34 +34,18 @@ public enum ControlState /// Control constructor. /// /// Parent control. + /// + /// public Button(Base parent, string? name = default, bool disableText = false) : base(parent, name, disableText) { AutoSizeToContents = false; - SetSize(100, 20); + Size = new Point(100, 20); MouseInputEnabled = true; TextAlign = Pos.Center; TextPadding = new Padding(3, 3, 3, 3); Name = name; } - /// - /// Indicates whether the button is depressed. - /// - public bool IsDepressed - { - get => mDepressed; - set - { - if (mDepressed == value) - { - return; - } - - mDepressed = value; - Redraw(); - } - } - /// /// Indicates whether the button is toggleable. /// @@ -158,52 +99,48 @@ public bool ToggleState } } - /// - /// Invoked when the button is pressed. - /// - public event GwenEventHandler Pressed; - - /// - /// Invoked when the button is released. - /// - public event GwenEventHandler Released; - /// /// Invoked when the button's toggle state has changed. /// - public event GwenEventHandler Toggled; + public event GwenEventHandler? Toggled; /// /// Invoked when the button's toggle state has changed to On. /// - public event GwenEventHandler ToggledOn; + public event GwenEventHandler? ToggledOn; /// /// Invoked when the button's toggle state has changed to Off. /// - public event GwenEventHandler ToggledOff; + public event GwenEventHandler? ToggledOff; - public override JObject GetJson(bool isRoot = default) + public override JObject? GetJson(bool isRoot = false, bool onlySerializeIfNotEmpty = false) { - var obj = base.GetJson(isRoot); - if (this.GetType() != typeof(CheckBox)) + var serializedProperties = base.GetJson(isRoot, onlySerializeIfNotEmpty); + if (serializedProperties is null) + { + return null; + } + + if (this is not Checkbox) { - obj.Add("NormalImage", GetImageFilename(ControlState.Normal)); - obj.Add("HoveredImage", GetImageFilename(ControlState.Hovered)); - obj.Add("ClickedImage", GetImageFilename(ControlState.Clicked)); - obj.Add("DisabledImage", GetImageFilename(ControlState.Disabled)); + serializedProperties.Add("NormalImage", GetStateTextureName(ComponentState.Normal)); + serializedProperties.Add("HoveredImage", GetStateTextureName(ComponentState.Hovered)); + serializedProperties.Add("ClickedImage", GetStateTextureName(ComponentState.Active)); + serializedProperties.Add("DisabledImage", GetStateTextureName(ComponentState.Disabled)); } - obj.Add("CenterImage", mCenterImage); - if (this.GetType() != typeof(ComboBox)) + if (this is not ComboBox) { - obj.Add("HoverSound", mHoverSound); - obj.Add("MouseUpSound", mMouseUpSound); - obj.Add("MouseDownSound", mMouseDownSound); - obj.Add("ClickSound", mClickSound); + serializedProperties.Add("HoverSound", mHoverSound); + serializedProperties.Add("MouseUpSound", mMouseUpSound); + serializedProperties.Add("MouseDownSound", mMouseDownSound); + serializedProperties.Add("ClickSound", mClickSound); } - return base.FixJson(obj); + serializedProperties.Add("CenterImage", mCenterImage); + + return base.FixJson(serializedProperties); } public override void LoadJson(JToken obj, bool isRoot = default) @@ -211,37 +148,37 @@ public override void LoadJson(JToken obj, bool isRoot = default) base.LoadJson(obj); if (obj["NormalImage"] != null) { - SetImage( - GameContentManager.Current.GetTexture( - TextureType.Gui, (string)obj["NormalImage"] - ), (string)obj["NormalImage"], ControlState.Normal + SetStateTexture( + GameContentManager.Current.GetTexture(TextureType.Gui, (string)obj["NormalImage"]), + (string)obj["NormalImage"], + ComponentState.Normal ); } if (obj["HoveredImage"] != null) { - SetImage( - GameContentManager.Current.GetTexture( - TextureType.Gui, (string)obj["HoveredImage"] - ), (string)obj["HoveredImage"], ControlState.Hovered + SetStateTexture( + GameContentManager.Current.GetTexture(TextureType.Gui, (string)obj["HoveredImage"]), + (string)obj["HoveredImage"], + ComponentState.Hovered ); } if (obj["ClickedImage"] != null) { - SetImage( - GameContentManager.Current.GetTexture( - TextureType.Gui, (string)obj["ClickedImage"] - ), (string)obj["ClickedImage"], ControlState.Clicked + SetStateTexture( + GameContentManager.Current.GetTexture(TextureType.Gui, (string)obj["ClickedImage"]), + (string)obj["ClickedImage"], + ComponentState.Active ); } if (obj["DisabledImage"] != null) { - SetImage( - GameContentManager.Current.GetTexture( - TextureType.Gui, (string)obj["DisabledImage"] - ), (string)obj["DisabledImage"], ControlState.Disabled + SetStateTexture( + GameContentManager.Current.GetTexture(TextureType.Gui, (string)obj["DisabledImage"]), + (string)obj["DisabledImage"], + ComponentState.Disabled ); } @@ -250,7 +187,7 @@ public override void LoadJson(JToken obj, bool isRoot = default) mCenterImage = (bool)obj["CenterImage"]; } - if (this.GetType() != typeof(ComboBox) && this.GetType() != typeof(CheckBox)) + if (this.GetType() != typeof(ComboBox) && this.GetType() != typeof(Checkbox)) { if (obj["HoverSound"] != null) { @@ -303,10 +240,7 @@ public virtual void Toggle() /// /// "Clicks" the button. /// - public virtual void Press(Base control = null) - { - OnClicked(0, 0); - } + public virtual void Press(Base? control = null) => OnMouseClicked(default, default); /// /// Renders the control using specified skin. @@ -318,7 +252,7 @@ protected override void Render(Skin.Base skin) if (ShouldDrawBackground) { - var drawDepressed = IsDepressed && IsHovered; + var drawDepressed = IsActive && IsHovered; if (IsToggle) { drawDepressed = drawDepressed || ToggleState; @@ -330,55 +264,21 @@ protected override void Render(Skin.Base skin) } } - /// - /// Handler invoked on mouse click (left) event. - /// - /// X coordinate. - /// Y coordinate. - /// If set to true mouse button is down. - protected override void OnMouseClickedLeft(int x, int y, bool down, bool automated = false) + protected override void OnMouseDown(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - //base.OnMouseClickedLeft(x, y, down); - if (down) - { - IsDepressed = true; - InputHandler.MouseFocus = this; - - //Play Mouse Down Sound - base.PlaySound(mMouseDownSound); - if (Pressed != null) - { - Pressed.Invoke(this, EventArgs.Empty); - } - } - else - { - if (IsHovered && mDepressed) - { - //Play Clicked Sound - base.PlaySound(mClickSound); - OnClicked(x, y); - } - - //Play Mouse Up Sound - base.PlaySound(mMouseUpSound); - IsDepressed = false; - InputHandler.MouseFocus = null; - if (Released != null) - { - Released.Invoke(this, EventArgs.Empty); - } - } + base.OnMouseDown(mouseButton, mousePosition, userAction); + base.PlaySound(mMouseDownSound); + } - Redraw(); + protected override void OnMouseUp(MouseButton mouseButton, Point mousePosition, bool userAction = true) + { + base.OnMouseUp(mouseButton, mousePosition, userAction); + base.PlaySound(mMouseUpSound); } - /// - /// Internal OnPressed implementation. - /// - protected virtual void OnClicked(int x, int y) + protected override void OnMouseClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - if (IsDisabled) + if (IsDisabledByTree) { return; } @@ -388,7 +288,8 @@ protected virtual void OnClicked(int x, int y) Toggle(); } - base.OnMouseClickedLeft(x, y, true); + base.OnMouseClicked(mouseButton, mousePosition, userAction); + base.PlaySound(mClickSound); } /// @@ -410,10 +311,7 @@ protected override bool OnKeySpace(bool down) /// /// Default accelerator handler. /// - protected override void OnAccelerator() - { - OnClicked(0, 0); - } + protected override void OnAccelerator() => OnMouseClicked(default, default); /// /// Lays out the control's interior according to alignment, padding, dock etc. @@ -429,136 +327,100 @@ protected override void Layout(Skin.Base skin) /// public override void UpdateColors() { - var textColor = GetTextColor(Label.ControlState.Normal); - if (IsDisabled && GetTextColor(Label.ControlState.Disabled) != null) + var textColor = GetTextColor(ComponentState.Normal) ?? Skin.Colors.Button.Normal; + if (IsDisabledByTree) { - textColor = GetTextColor(Label.ControlState.Disabled); + textColor = GetTextColor(ComponentState.Disabled) ?? Skin.Colors.Button.Disabled; } - else if (IsHovered && GetTextColor(Label.ControlState.Hovered) != null) + else if (IsActive) { - textColor = GetTextColor(Label.ControlState.Hovered); + textColor = GetTextColor(ComponentState.Active) ?? Skin.Colors.Button.Active; } - - if (textColor != null) + else if (IsHovered) { - TextColor = textColor; - - return; - } - - if (IsDisabled) - { - TextColor = Skin.Colors.Button.Disabled; - - return; + textColor = GetTextColor(ComponentState.Hovered) ?? Skin.Colors.Button.Hover; } - if (IsDepressed || ToggleState) + // ApplicationContext.CurrentContext.Logger.LogInformation( + // "'{ComponentName}' IsDisabled={IsDisabled} IsActive={IsActive} IsHovered={IsHovered} TextColor={TextColor}", + // CanonicalName, + // IsDisabled, + // IsActive, + // IsHovered, + // textColor + // ); + + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (textColor == null) { - TextColor = Skin.Colors.Button.Down; + ApplicationContext.CurrentContext.Logger.LogError( + "Text color for the current control state of {ComponentType} '{ComponentName}' is somehow null IsDisabled={IsDisabled} IsActive={IsActive} IsHovered={IsHovered}", + GetType().GetName(qualified: true), + CanonicalName, + IsDisabled, + IsActive, + IsHovered + ); - return; + textColor = new Color(r: 255, g: 0, b: 255); } - if (IsHovered) + if ((textColor.ToArgb() & 0xffffff) == 0) { - TextColor = Skin.Colors.Button.Hover; - - return; + textColor.ToString(); } - TextColor = Skin.Colors.Button.Normal; + TextColor = textColor; } - /// - /// Handler invoked on mouse double click (left) event. - /// - /// X coordinate. - /// Y coordinate. - protected override void OnMouseDoubleClickedLeft(int x, int y) + protected override void OnMouseDoubleClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - base.OnMouseDoubleClickedLeft(x, y); - OnMouseClickedLeft(x, y, true); + base.OnMouseDoubleClicked(mouseButton, mousePosition, userAction); + OnMouseClicked(mouseButton, mousePosition, userAction); } - public void SetImage(string textureName, ControlState controlState) + public void SetStateTexture(string textureName, ComponentState componentState) { var texture = GameContentManager.Current.GetTexture(TextureType.Gui, textureName); - SetImage(texture, textureName, controlState); + SetStateTexture(texture, textureName, componentState); } /// /// Sets the button's image. /// - /// Texture name. Null to remove. - public void SetImage(GameTexture texture, string fileName, ControlState state) + /// + /// + /// + public void SetStateTexture(GameTexture? texture, string? name, ComponentState state) { - if (texture == null && !string.IsNullOrWhiteSpace(fileName)) + if (texture == null && !string.IsNullOrWhiteSpace(name)) { - texture = GameContentManager.Current?.GetTexture(TextureType.Gui, fileName); + texture = GameContentManager.Current.GetTexture(TextureType.Gui, name); } - switch (state) + if (texture == null) { - case ControlState.Normal: - mNormalImageFilename = fileName; - mNormalImage = texture; - - break; - case ControlState.Hovered: - mHoverImageFilename = fileName; - mHoverImage = texture; - - break; - case ControlState.Clicked: - mClickedImageFilename = fileName; - mClickedImage = texture; - - break; - case ControlState.Disabled: - mDisabledImageFilename = fileName; - mDisabledImage = texture; - - break; - default: - throw new ArgumentOutOfRangeException(nameof(state), state, null); + _ = _stateTextures.Remove(state); } - } - - public GameTexture? GetImage(ControlState state) - { - switch (state) + else { - case ControlState.Normal: - return mNormalImage; - case ControlState.Hovered: - return mHoverImage; - case ControlState.Clicked: - return mClickedImage; - case ControlState.Disabled: - return mDisabledImage; - default: - return null; + _stateTextures[state] = texture; } - } - public string GetImageFilename(ControlState state) - { - switch (state) + if (name == null) + { + _ = _stateTextureNames.Remove(state); + } + else { - case ControlState.Normal: - return mNormalImageFilename; - case ControlState.Hovered: - return mHoverImageFilename; - case ControlState.Clicked: - return mClickedImageFilename; - case ControlState.Disabled: - return mDisabledImageFilename; - default: - return null; + _stateTextureNames[state] = name; } } + public GameTexture? GetStateTexture(ComponentState state) => _stateTextures.GetValueOrDefault(state); + + public string? GetStateTextureName(ComponentState state) => _stateTextureNames.GetValueOrDefault(state); + protected override void OnMouseEntered() { base.OnMouseEntered(); diff --git a/Intersect.Client.Framework/Gwen/Control/Canvas.cs b/Intersect.Client.Framework/Gwen/Control/Canvas.cs index 5b852eddce..517419cb51 100644 --- a/Intersect.Client.Framework/Gwen/Control/Canvas.cs +++ b/Intersect.Client.Framework/Gwen/Control/Canvas.cs @@ -3,6 +3,7 @@ using Intersect.Client.Framework.Gwen.DragDrop; using Intersect.Client.Framework.Gwen.Input; using Intersect.Client.Framework.Audio; +using Intersect.Client.Framework.Input; namespace Intersect.Client.Framework.Gwen.Control; @@ -172,9 +173,10 @@ protected override void Render(Skin.Base skin) /// Handler invoked when control's bounds change. /// /// Old bounds. - protected override void OnBoundsChanged(Rectangle oldBounds) + /// + protected override void OnBoundsChanged(Rectangle oldBounds, Rectangle newBounds) { - base.OnBoundsChanged(oldBounds); + base.OnBoundsChanged(oldBounds, newBounds); InvalidateChildren(true); } @@ -314,15 +316,8 @@ public bool Input_MouseMoved(int x, int y, int dx, int dy) /// Handles mouse button events. Called from Input subsystems. /// /// True if handled. - public bool Input_MouseButton(int button, bool down) - { - if (IsHidden) - { - return false; - } - - return InputHandler.OnMouseClicked(this, button, down); - } + public bool Input_MouseButton(MouseButton button, bool down) => + !IsHidden && InputHandler.OnMouseButtonStateChanged(this, button, down); /// /// Handles mouse button events. Called from Input subsystems. diff --git a/Intersect.Client.Framework/Gwen/Control/CheckBox.cs b/Intersect.Client.Framework/Gwen/Control/Checkbox.cs similarity index 84% rename from Intersect.Client.Framework/Gwen/Control/CheckBox.cs rename to Intersect.Client.Framework/Gwen/Control/Checkbox.cs index 91b3751222..65d27221eb 100644 --- a/Intersect.Client.Framework/Gwen/Control/CheckBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/Checkbox.cs @@ -1,5 +1,6 @@ using Intersect.Client.Framework.File_Management; using Intersect.Client.Framework.Graphics; +using Intersect.Client.Framework.Input; using Newtonsoft.Json.Linq; namespace Intersect.Client.Framework.Gwen.Control; @@ -8,7 +9,7 @@ namespace Intersect.Client.Framework.Gwen.Control; /// /// CheckBox control. /// -public partial class CheckBox : Button +public partial class Checkbox : Button { public enum ControlState @@ -48,12 +49,12 @@ public enum ControlState private string mUncheckedSound; /// - /// Initializes a new instance of the class. + /// Initializes a new instance of the class. /// /// Parent control. /// /// - public CheckBox(Base parent, string? name = default, bool disableText = true) : base(parent, name, disableText) + public Checkbox(Base parent, string? name = default, bool disableText = true) : base(parent, name, disableText) { MinimumSize = new Point(22, 22); Size = new Point(22, 22); @@ -83,17 +84,22 @@ public bool IsChecked /// protected virtual bool AllowUncheck => true; - public override JObject GetJson(bool isRoot = default) + public override JObject? GetJson(bool isRoot = false, bool onlySerializeIfNotEmpty = false) { - var obj = base.GetJson(isRoot); - obj.Add("NormalImage", GetImageFilename(ControlState.Normal)); - obj.Add("CheckedImage", GetImageFilename(ControlState.CheckedNormal)); - obj.Add("DisabledImage", GetImageFilename(ControlState.Disabled)); - obj.Add("CheckedDisabledImage", GetImageFilename(ControlState.CheckedDisabled)); - obj.Add("CheckedSound", mCheckSound); - obj.Add("UncheckedSound", mUncheckedSound); - - return base.FixJson(obj); + var serializedProperties = base.GetJson(isRoot, onlySerializeIfNotEmpty); + if (serializedProperties is null) + { + return null; + } + + serializedProperties.Add("NormalImage", GetImageFilename(ControlState.Normal)); + serializedProperties.Add("CheckedImage", GetImageFilename(ControlState.CheckedNormal)); + serializedProperties.Add("DisabledImage", GetImageFilename(ControlState.Disabled)); + serializedProperties.Add("CheckedDisabledImage", GetImageFilename(ControlState.CheckedDisabled)); + serializedProperties.Add("CheckedSound", mCheckSound); + serializedProperties.Add("UncheckedSound", mUncheckedSound); + + return base.FixJson(serializedProperties); } public override void LoadJson(JToken obj, bool isRoot = default) @@ -211,7 +217,7 @@ protected virtual void OnCheckChanged() protected override void Render(Skin.Base skin) { //base.Render(skin); - skin.DrawCheckBox(this, mChecked, IsHovered, IsDepressed); + skin.DrawCheckBox(this, mChecked, IsHovered, IsActive); } public void SetCheckSize(int w, int h) @@ -221,9 +227,9 @@ public void SetCheckSize(int w, int h) /// /// Internal OnPressed implementation. /// - protected override void OnClicked(int x, int y) + protected override void OnMouseClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - if (IsDisabled) + if (IsDisabledByTree) { return; } @@ -233,7 +239,7 @@ protected override void OnClicked(int x, int y) return; } - base.OnClicked(x, y); + base.OnMouseClicked(mouseButton, mousePosition, userAction); } public void SetImage(GameTexture texture, string fileName, ControlState state) diff --git a/Intersect.Client.Framework/Gwen/Control/CollapsibleList.cs b/Intersect.Client.Framework/Gwen/Control/CollapsibleList.cs index 272f99ad48..9b6821c3ed 100644 --- a/Intersect.Client.Framework/Gwen/Control/CollapsibleList.cs +++ b/Intersect.Client.Framework/Gwen/Control/CollapsibleList.cs @@ -16,8 +16,6 @@ public partial class CollapsibleList : ScrollControl public CollapsibleList(Base parent) : base(parent) { MouseInputEnabled = true; - EnableScroll(false, true); - AutoHideBars = true; } /// diff --git a/Intersect.Client.Framework/Gwen/Control/ColorLerpBox.cs b/Intersect.Client.Framework/Gwen/Control/ColorLerpBox.cs index 39d281021e..285483a416 100644 --- a/Intersect.Client.Framework/Gwen/Control/ColorLerpBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/ColorLerpBox.cs @@ -1,5 +1,6 @@ using Intersect.Client.Framework.GenericClasses; using Intersect.Client.Framework.Gwen.Input; +using Intersect.Client.Framework.Input; namespace Intersect.Client.Framework.Gwen.Control; @@ -124,26 +125,34 @@ protected override void OnMouseMoved(int x, int y, int dx, int dy) } } - /// - /// Handler invoked on mouse click (left) event. - /// - /// X coordinate. - /// Y coordinate. - /// If set to true mouse button is down. - protected override void OnMouseClickedLeft(int x, int y, bool down, bool automated = false) + protected override void OnMouseDown(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - base.OnMouseClickedLeft(x, y, down); - mDepressed = down; - if (down) + base.OnMouseDown(mouseButton, mousePosition, userAction); + + if (mouseButton != MouseButton.Left) { - InputHandler.MouseFocus = this; + return; } - else + + mDepressed = true; + InputHandler.MouseFocus = this; + + OnMouseMoved(mousePosition.X, mousePosition.Y, 0, 0); + } + + protected override void OnMouseUp(MouseButton mouseButton, Point mousePosition, bool userAction = true) + { + base.OnMouseUp(mouseButton, mousePosition, userAction); + + if (mouseButton != MouseButton.Left) { - InputHandler.MouseFocus = null; + return; } - OnMouseMoved(x, y, 0, 0); + mDepressed = false; + InputHandler.MouseFocus = null; + + OnMouseMoved(mousePosition.X, mousePosition.Y, 0, 0); } /// diff --git a/Intersect.Client.Framework/Gwen/Control/ColorPicker.cs b/Intersect.Client.Framework/Gwen/Control/ColorPicker.cs index 0a8c1e8430..2fa039535c 100644 --- a/Intersect.Client.Framework/Gwen/Control/ColorPicker.cs +++ b/Intersect.Client.Framework/Gwen/Control/ColorPicker.cs @@ -118,7 +118,10 @@ private void CreateColorControl(string name, int y) numeric.SelectAllOnFocus = true; numeric.TextChanged += NumericTyped; - var slider = new HorizontalSlider(colorGroup); + var slider = new Slider(colorGroup) + { + Orientation = Orientation.LeftToRight, + }; slider.SetPosition(colorSize + 5, 10); slider.SetRange(0, 255); slider.SetSize(80, colorSize); @@ -198,16 +201,22 @@ private void CreateControls() //UpdateControls(); } - private void UpdateColorControls(string name, Color col, int sliderVal) + private void UpdateColorControls(string name, Color color, int value) { - var disp = FindChildByName(name, true) as ColorDisplay; - disp.Color = col; + if (FindChildByName(name, true) is ColorDisplay colorDisplay) + { + colorDisplay.Color = color; + } - var slider = FindChildByName(name + "Slider", true) as HorizontalSlider; - slider.Value = sliderVal; + if (FindChildByName(name + "Slider", true) is Slider slider) + { + slider.Value = value; + } - var box = FindChildByName(name + "Box", true) as TextBoxNumeric; - box.Value = sliderVal; + if (FindChildByName(name + "Box", true) is TextBoxNumeric numberBox) + { + numberBox.Value = value; + } } private void UpdateControls() @@ -236,10 +245,10 @@ private void SlidersMoved(Base control, EventArgs args) HorizontalSlider* alphaSlider = gwen_cast( FindChildByName( "AlphaSlider", true ) ); */ - var slider = control as HorizontalSlider; - if (slider != null) + if (control is Slider slider) { - SetColorByName(GetColorFromName(slider.Name), (int) slider.Value); + var color = GetColorFromName(slider.Name); + SetColorByName(color, (int) slider.Value); } UpdateControls(); @@ -292,8 +301,13 @@ private int GetColorByName(string colorName) return 0; } - private static string GetColorFromName(string name) + private static string GetColorFromName(string? name) { + if (string.IsNullOrWhiteSpace(name)) + { + return string.Empty; + } + if (name.Contains("Red")) { return "Red"; @@ -314,7 +328,7 @@ private static string GetColorFromName(string name) return "Alpha"; } - return String.Empty; + return string.Empty; } private void SetColorByName(string colorName, int colorValue) diff --git a/Intersect.Client.Framework/Gwen/Control/ColorSlider.cs b/Intersect.Client.Framework/Gwen/Control/ColorSlider.cs index 9be0daf3e4..d7a4499c42 100644 --- a/Intersect.Client.Framework/Gwen/Control/ColorSlider.cs +++ b/Intersect.Client.Framework/Gwen/Control/ColorSlider.cs @@ -1,6 +1,7 @@ using Intersect.Client.Framework.GenericClasses; using Intersect.Client.Framework.Graphics; using Intersect.Client.Framework.Gwen.Input; +using Intersect.Client.Framework.Input; namespace Intersect.Client.Framework.Gwen.Control; @@ -75,26 +76,34 @@ protected override void Render(Skin.Base skin) base.Render(skin); } - /// - /// Handler invoked on mouse click (left) event. - /// - /// X coordinate. - /// Y coordinate. - /// If set to true mouse button is down. - protected override void OnMouseClickedLeft(int x, int y, bool down, bool automated = false) + protected override void OnMouseDown(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - base.OnMouseClickedLeft(x, y, down); - mDepressed = down; - if (down) + base.OnMouseDown(mouseButton, mousePosition, userAction); + + if (mouseButton != MouseButton.Left) { - InputHandler.MouseFocus = this; + return; } - else + + mDepressed = true; + InputHandler.MouseFocus = this; + + OnMouseMoved(mousePosition.X, mousePosition.Y, 0, 0); + } + + protected override void OnMouseUp(MouseButton mouseButton, Point mousePosition, bool userAction = true) + { + base.OnMouseUp(mouseButton, mousePosition, userAction); + + if (mouseButton != MouseButton.Left) { - InputHandler.MouseFocus = null; + return; } - OnMouseMoved(x, y, 0, 0); + mDepressed = false; + InputHandler.MouseFocus = null; + + OnMouseMoved(mousePosition.X, mousePosition.Y, 0, 0); } /// diff --git a/Intersect.Client.Framework/Gwen/Control/ComboBox.cs b/Intersect.Client.Framework/Gwen/Control/ComboBox.cs index 0299300dbc..0b61772ff8 100644 --- a/Intersect.Client.Framework/Gwen/Control/ComboBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/ComboBox.cs @@ -1,18 +1,24 @@ 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.Client.Framework.Input; +using Intersect.Core; +using Intersect.Framework.Reflection; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace Intersect.Client.Framework.Gwen.Control; - /// /// ComboBox control. /// public partial class ComboBox : Button { + private readonly Base _arrowIcon; + private readonly Menu _menu; - private readonly Base mButton; + private Point _itemMaximumSize; private string mCloseMenuSound; @@ -20,14 +26,12 @@ public partial class ComboBox : Button private string mHoverMenuSound; - private Menu mMenu; - private bool mMenuAbove; //Sound Effects private string mOpenMenuSound; - private MenuItem mSelectedItem; + private MenuItem? mSelectedItem; private string mSelectItemSound; @@ -36,23 +40,26 @@ public partial class ComboBox : Button /// /// Parent control. /// - public ComboBox(Base parent, string name = default) : base(parent, name) + public ComboBox(Base parent, string? name = default) : base(parent, name) { - SetSize(100, 20); - mMenu = new Menu(this) + Size = new Point(100, 20); + _menu = new Menu(this, name: nameof(_menu)) { IsHidden = true, IconMarginDisabled = true, - IsTabable = false + IsTabable = false, + MaximumSize = new Point(100, 200), }; - var arrow = new DownArrow(this); - mButton = arrow; + _arrowIcon = new DownArrow(this, name: nameof(_arrowIcon)) + { + Margin = new Margin(4, 0, 0, 0), + }; TextAlign = Pos.Left | Pos.CenterV; Text = string.Empty; - Margin = new Margin(3, 0, 0, 0); + AutoSizeToContents = true; IsTabable = true; KeyboardInputEnabled = true; } @@ -60,24 +67,45 @@ public ComboBox(Base parent, string name = default) : base(parent, name) /// /// Indicates whether the combo menu is open. /// - public bool IsOpen => mMenu != null && !mMenu.IsHidden; + public bool IsOpen => _menu.IsVisible; /// /// Selected item. /// /// Not just String property, because items also have internal names. - public MenuItem SelectedItem + public MenuItem? SelectedItem { get => mSelectedItem; set { - if (value == null || value.Parent != mMenu) + if (value == mSelectedItem) { return; } - mSelectedItem = value; - OnItemSelected(mSelectedItem, new ItemSelectedEventArgs(value, true, mSelectedItem.UserData)); + if (value == null) + { + mSelectedItem = null; + // TODO: Event? + return; + } + + if (value.Parent == _menu) + { + mSelectedItem = value; + OnItemSelected(mSelectedItem, new ItemSelectedEventArgs(value, true, mSelectedItem.UserData)); + return; + } + + ApplicationContext.CurrentContext.Logger.LogWarning( + "Tried to set selected item of {ComponentTypeName} '{ComponentName}' to '{SelectionName}' ({SelectionType})", + GetType().GetName(qualified: true), + CanonicalName, + value.Name, + value.GetType().GetName(qualified: true) + ); + + mSelectedItem = null; } } @@ -86,7 +114,7 @@ public MenuItem SelectedItem /// /// Invoked when the selected item has changed. /// - public event GwenEventHandler ItemSelected; + public event GwenEventHandler? ItemSelected; public void SetMenuAbove() { @@ -106,12 +134,16 @@ public void SetMenuBelow() } } - public override JObject GetJson(bool isRoot = default) + public override JObject? GetJson(bool isRoot = false, bool onlySerializeIfNotEmpty = false) { - var obj = base.GetJson(isRoot); + var obj = base.GetJson(isRoot, onlySerializeIfNotEmpty); + if (obj is null) + { + return null; + } + obj.Add("MenuAbove", mMenuAbove); - obj.Add("Menu", mMenu.GetJson()); - obj.Add("DropDownButton", mButton.GetJson()); + obj.Add("DropDownButton", _arrowIcon.GetJson()); obj.Add("OpenMenuSound", mOpenMenuSound); obj.Add("CloseMenuSound", mCloseMenuSound); obj.Add("HoverMenuSound", mHoverMenuSound); @@ -131,12 +163,12 @@ public override void LoadJson(JToken obj, bool isRoot = default) if (obj["Menu"] != null) { - mMenu.LoadJson(obj["Menu"]); + _menu.LoadJson(obj["Menu"]); } if (obj["DropDownButton"] != null) { - mButton.LoadJson(obj["DropDownButton"]); + _arrowIcon.LoadJson(obj["DropDownButton"]); } if (obj["OpenMenuSound"] != null) @@ -185,18 +217,20 @@ public override void LoadJson(JToken obj, bool isRoot = default) /// Item label (displayed). /// Item name. /// Newly created control. - public virtual MenuItem AddItem(string label, string name = "", object userData = null) + public virtual MenuItem AddItem(string label, string? name = default, object? userData = default) { - var item = mMenu.AddItem(label, null, "", "", this.Font); + var item = _menu.AddItem(label, null, "", "", Font); item.Name = name; item.Selected += OnItemSelected; item.UserData = userData; - item.SetTextColor(GetTextColor(Label.ControlState.Normal), Label.ControlState.Normal); - item.SetTextColor(GetTextColor(Label.ControlState.Hovered), Label.ControlState.Hovered); - item.SetTextColor(GetTextColor(Label.ControlState.Clicked), Label.ControlState.Clicked); - item.SetTextColor(GetTextColor(Label.ControlState.Disabled), Label.ControlState.Disabled); + item.SetTextColor(GetTextColor(ComponentState.Normal), ComponentState.Normal); + item.SetTextColor(GetTextColor(ComponentState.Hovered), ComponentState.Hovered); + item.SetTextColor(GetTextColor(ComponentState.Active), ComponentState.Active); + item.SetTextColor(GetTextColor(ComponentState.Disabled), ComponentState.Disabled); item.SetHoverSound(mHoverItemSound); + UpdateItemMaximumSize(label); + if (mSelectedItem == null) { OnItemSelected(item, new ItemSelectedEventArgs(null, true, selectedUserData: null)); @@ -205,43 +239,60 @@ public virtual MenuItem AddItem(string label, string name = "", object userData return item; } + private void UpdateItemMaximumSize(string item) + { + var itemSize = Skin.Renderer.MeasureText(Font, item); + _itemMaximumSize.X = Math.Max(_itemMaximumSize.X, itemSize.X); + _itemMaximumSize.Y = Math.Max(_itemMaximumSize.Y, itemSize.Y); + } + + protected override void OnFontChanged(Base sender, GameFont? oldFont, GameFont? newFont) + { + _itemMaximumSize = default; + foreach (var item in _menu.MenuItems) + { + UpdateItemMaximumSize(item.Text ?? string.Empty); + } + } + /// /// Renders the control using specified skin. /// /// Skin to use. protected override void Render(Skin.Base skin) { - skin.DrawComboBox(this, IsDepressed, IsOpen); + skin.DrawComboBox(this, IsActive, IsOpen); } public override void Disable() { base.Disable(); - GetCanvas().CloseMenus(); + Canvas?.CloseMenus(); } /// /// Internal Pressed implementation. /// - protected override void OnClicked(int x, int y) + protected override void OnMouseClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) { + var canvas = Canvas; + if (IsOpen) { - GetCanvas().CloseMenus(); + canvas?.CloseMenus(); return; } - var wasMenuHidden = mMenu.IsHidden; - - GetCanvas().CloseMenus(); + var wasMenuHidden = _menu.IsHidden; + canvas?.CloseMenus(); if (wasMenuHidden) { Open(); } - base.OnClicked(x, y); + base.OnMouseClicked(mouseButton, mousePosition, userAction); } /// @@ -249,10 +300,8 @@ protected override void OnClicked(int x, int y) /// public virtual void DeleteAll() { - if (mMenu != null) - { - mMenu.DeleteAll(); - } + _itemMaximumSize = default; + _menu.DeleteAll(); } /// @@ -272,12 +321,9 @@ protected virtual void OnItemSelected(Base control, ItemSelectedEventArgs args) mSelectedItem = item; Text = mSelectedItem.Text; - mMenu.IsHidden = true; + _menu.IsHidden = true; - if (ItemSelected != null) - { - ItemSelected.Invoke(this, args); - } + ItemSelected?.Invoke(this, args); if (!args.Automated) { @@ -295,7 +341,8 @@ protected virtual void OnItemSelected(Base control, ItemSelectedEventArgs args) /// Skin to use. protected override void Layout(Skin.Base skin) { - mButton.Position(Pos.Right | Pos.CenterV, 4, 0); + _arrowIcon.Position(Pos.Right | Pos.CenterV, 4, 0); + base.Layout(skin); } @@ -304,9 +351,9 @@ protected override void Layout(Skin.Base skin) /// protected override void OnLostKeyboardFocus() { - if (GetTextColor(Label.ControlState.Normal) != null) + if (GetTextColor(ComponentState.Normal) != null) { - TextColor = GetTextColor(Label.ControlState.Normal); + TextColor = GetTextColor(ComponentState.Normal); return; } @@ -320,14 +367,14 @@ protected override void OnLostKeyboardFocus() protected override void OnKeyboardFocus() { //Until we add the blue highlighting again - if (GetTextColor(Label.ControlState.Normal) != null) + if (GetTextColor(ComponentState.Normal) != null) { - TextColor = GetTextColor(Label.ControlState.Normal); + TextColor = GetTextColor(ComponentState.Normal); return; } - TextColor = Color.Black; + TextColor = Color.White; } /// @@ -335,32 +382,31 @@ protected override void OnKeyboardFocus() /// public virtual void Open() { - if (!IsDisabled) + if (IsDisabledByTree) { - if (null == mMenu) - { - return; - } + return; + } - mMenu.Parent = GetCanvas(); - mMenu.IsHidden = false; - mMenu.BringToFront(); + _menu.Parent = GetCanvas(); + _menu.IsHidden = false; + _menu.BringToFront(); - var p = LocalPosToCanvas(Point.Empty); - if (mMenuAbove) - { - mMenu.RestrictToParent = false; - mMenu.Height = mMenu.Children.Sum(child => child != null ? child.Height : 0); - mMenu.SetBounds(new Rectangle(p.X, p.Y - mMenu.Height, Width, mMenu.Height)); - mMenu.RestrictToParent = true; - } - else - { - mMenu.SetBounds(new Rectangle(p.X, p.Y + Height, Width, mMenu.Height)); - } - - base.PlaySound(mOpenMenuSound); + var p = LocalPosToCanvas(Point.Empty); + var height = _menu.Children.OfType().Sum(child => child.Height); + var width = Width; + _menu.MaximumSize = _menu.MaximumSize with { X = width }; + if (mMenuAbove) + { + _menu.RestrictToParent = false; + _menu.SetBounds(new Rectangle(p.X, p.Y - _menu.Height, width, height)); + _menu.RestrictToParent = true; + } + else + { + _menu.SetBounds(new Rectangle(p.X, p.Y + Height, width, height)); } + + base.PlaySound(mOpenMenuSound); } /// @@ -368,12 +414,7 @@ public virtual void Open() /// public virtual void Close() { - if (mMenu == null) - { - return; - } - - mMenu.Hide(); + _menu.Hide(); base.PlaySound(mCloseMenuSound); } @@ -392,13 +433,13 @@ protected override bool OnKeyDown(bool down) return true; } - var it = mMenu.Children.FindIndex(x => x == mSelectedItem); - if (it + 1 >= mMenu.Children.Count) + var it = _menu.Children.FindIndex(x => x == mSelectedItem); + if (it + 1 >= _menu.Children.Count) { return true; } - var selectedItem = mMenu.Children[it + 1]; + var selectedItem = _menu.Children[it + 1]; OnItemSelected(this, new ItemSelectedEventArgs(selectedItem, selectedUserData: selectedItem.UserData)); return true; @@ -418,13 +459,13 @@ protected override bool OnKeyUp(bool down) return true; } - var it = mMenu.Children.FindLastIndex(x => x == mSelectedItem); + var it = _menu.Children.FindLastIndex(x => x == mSelectedItem); if (it - 1 < 0) { return true; } - var selectedItem = mMenu.Children[it - 1]; + var selectedItem = _menu.Children[it - 1]; OnItemSelected(this, new ItemSelectedEventArgs(selectedItem, selectedUserData: selectedItem.UserData)); return true; @@ -443,17 +484,19 @@ protected override void RenderFocus(Skin.Base skin) /// If a menu item can not be found that matches input, nothing happens. /// /// The label to look for, this is what is shown to the user. - public void SelectByText(string text) + public bool SelectByText(string text) { - foreach (MenuItem item in mMenu.Children) + foreach (MenuItem item in _menu.Children) { if (item.Text == text) { SelectedItem = item; - return; + return true; } } + + return false; } /// @@ -461,17 +504,19 @@ public void SelectByText(string text) /// If a menu item can not be found that matches input, nothing happens. /// /// The internal name to look for. To select by what is displayed to the user, use "SelectByText". - public void SelectByName(string name) + public bool SelectByName(string name) { - foreach (MenuItem item in mMenu.Children) + foreach (MenuItem item in _menu.Children) { if (item.Name == name) { SelectedItem = item; - return; + return true; } } + + return false; } /// @@ -484,7 +529,7 @@ public void SelectByName(string name) /// public virtual bool SelectByUserData(object? userdata) { - foreach (MenuItem item in mMenu.Children) + foreach (MenuItem item in _menu.Children) { if (userdata == null) { @@ -508,28 +553,32 @@ public virtual bool SelectByUserData(object? userdata) public void SetMenuBackgroundColor(Color clr) { - mMenu.RenderColor = clr; + _menu.RenderColor = clr; } public void SetMenuMaxSize(int w, int h) { - mMenu.MaximumSize = new Point(w, h); + _menu.MaximumSize = new Point(w, h); + } + + protected override Point GetContentSize() => _itemMaximumSize == default ? base.GetContentSize() : _itemMaximumSize + new Point(4, 0); + + protected override Padding GetContentPadding() + { + var padding = base.GetContentPadding(); + padding.Right += _arrowIcon.OuterWidth; + return padding; } - public override void SetTextColor(Color clr, Label.ControlState state) + public override void SetTextColor(Color clr, ComponentState state) { base.SetTextColor(clr, state); - foreach (MenuItem itm in mMenu.Children) + foreach (MenuItem itm in _menu.Children) { itm.SetTextColor(clr, state); } } - public void SetMenu(Menu menu) - { - mMenu = menu; - } - protected override void OnMouseEntered() { base.OnMouseEntered(); diff --git a/Intersect.Client.Framework/Gwen/Control/ComponentState.cs b/Intersect.Client.Framework/Gwen/Control/ComponentState.cs new file mode 100644 index 0000000000..d5c93cb8c7 --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/ComponentState.cs @@ -0,0 +1,9 @@ +namespace Intersect.Client.Framework.Gwen.Control; + +public enum ComponentState +{ + Normal = 0, + Hovered, + Active, + Disabled, +} \ No newline at end of file diff --git a/Intersect.Client.Framework/Gwen/Control/ComponentStateFilters.cs b/Intersect.Client.Framework/Gwen/Control/ComponentStateFilters.cs new file mode 100644 index 0000000000..808437cbe8 --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/ComponentStateFilters.cs @@ -0,0 +1,10 @@ +namespace Intersect.Client.Framework.Gwen.Control; + +[Flags] +public enum ComponentStateFilters +{ + None, + + IncludeHidden, + IncludeMouseInputDisabled, +} \ No newline at end of file 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/ClickedEventArgs.cs b/Intersect.Client.Framework/Gwen/Control/EventArguments/ClickedEventArgs.cs deleted file mode 100644 index a4e155fdfb..0000000000 --- a/Intersect.Client.Framework/Gwen/Control/EventArguments/ClickedEventArgs.cs +++ /dev/null @@ -1,20 +0,0 @@ -namespace Intersect.Client.Framework.Gwen.Control.EventArguments; - - -public partial class ClickedEventArgs : EventArgs -{ - - internal ClickedEventArgs(int x, int y, bool down) - { - this.X = x; - this.Y = y; - this.MouseDown = down; - } - - public int X { get; private set; } - - public int Y { get; private set; } - - public bool MouseDown { get; private set; } - -} diff --git a/Intersect.Client.Framework/Gwen/Control/EventArguments/MouseButtonState.cs b/Intersect.Client.Framework/Gwen/Control/EventArguments/MouseButtonState.cs new file mode 100644 index 0000000000..f0fe0f129c --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/EventArguments/MouseButtonState.cs @@ -0,0 +1,30 @@ +using Intersect.Client.Framework.Input; + +namespace Intersect.Client.Framework.Gwen.Control.EventArguments; + +public sealed class MouseButtonState : EventArgs +{ + internal MouseButtonState(MouseButton mouseButton, int x, int y, bool isPressed) + { + MouseButton = mouseButton; + X = x; + Y = y; + IsPressed = isPressed; + } + + internal MouseButtonState(MouseButton mouseButton, Point mousePosition, bool isPressed) + { + MouseButton = mouseButton; + X = mousePosition.X; + Y = mousePosition.Y; + IsPressed = isPressed; + } + + public MouseButton MouseButton { get; } + + public int X { get; private set; } + + public int Y { get; private set; } + + public bool IsPressed { get; private set; } +} \ No newline at end of file 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..cd83b17662 --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/EventArguments/ValueChangedEventArgs.cs @@ -0,0 +1,10 @@ +namespace Intersect.Client.Framework.Gwen.Control.EventArguments; + +public class ValueChangedEventArgs : EventArgs +{ + public ValueChangedEventArgs() { } + + public required TValue Value { get; init; } + + public TValue OldValue { get; init; } +} \ No newline at end of file diff --git a/Intersect.Client.Framework/Gwen/Control/GroupBox.cs b/Intersect.Client.Framework/Gwen/Control/GroupBox.cs index 70c0b45638..26cd23626f 100644 --- a/Intersect.Client.Framework/Gwen/Control/GroupBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/GroupBox.cs @@ -16,7 +16,7 @@ public GroupBox(Base parent) : base(parent) { AutoSizeToContents = false; - // Set to true, because it's likely that our + // Set to true, because it's likely that our // children will want mouse input, and they // can't get it without us.. MouseInputEnabled = true; @@ -58,20 +58,22 @@ protected override void Render(Skin.Base skin) /// /// Sizes to contents. /// - public override void SizeToContents() - { - // we inherit from Label and shouldn't use its method. - DoSizeToContents(); - } + public override bool SizeToContents() => DoSizeToContents(); - protected virtual void DoSizeToContents() + protected virtual bool DoSizeToContents() { - _innerPanel.SizeToChildren(); - SizeToChildren(); - if (Width < TextWidth + TextPadding.Right + TextPadding.Left) + var sizeChanged = _innerPanel?.SizeToChildren() ?? false; + sizeChanged &= SizeToChildren(); + + var textWidth = TextWidth + TextPadding.Right + TextPadding.Left; + + // ReSharper disable once InvertIf + if (Width < textWidth) { - Width = TextWidth + TextPadding.Right + TextPadding.Left; + Width = textWidth; + sizeChanged = true; } - } + return sizeChanged; + } } diff --git a/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs b/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs index 01af80321f..3e67af7f2e 100644 --- a/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs +++ b/Intersect.Client.Framework/Gwen/Control/HorizontalScrollBar.cs @@ -1,4 +1,5 @@ using Intersect.Client.Framework.Gwen.Input; +using Intersect.Client.Framework.Input; namespace Intersect.Client.Framework.Gwen.Control; @@ -52,17 +53,7 @@ public override int BarSize public override float NudgeAmount { - get - { - if (mDepressed) - { - return mViewableContentSize / mContentSize; - } - else - { - return base.NudgeAmount; - } - } + get => mDepressed ? mViewableContentSize / mContentSize : base.NudgeAmount; set => base.NudgeAmount = value; } @@ -81,7 +72,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); @@ -94,7 +85,7 @@ protected override void Layout(Skin.Base skin) mBar.IsHidden = Width - ButtonSize * 2 <= barWidth; //Based on our last scroll amount, produce a position for the bar - if (!mBar.IsHeld) + if (!mBar.IsActive) { SetScrollAmount(ScrollAmount, true); } @@ -128,35 +119,40 @@ public override void ScrollToRight() SetScrollAmount(1, true); } - /// - /// Handler invoked on mouse click (left) event. - /// - /// X coordinate. - /// Y coordinate. - /// If set to true mouse button is down. - protected override void OnMouseClickedLeft(int x, int y, bool down, bool automated = false) + protected override void OnMouseDown(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - base.OnMouseClickedLeft(x, y, down); - if (down) + base.OnMouseDown(mouseButton, mousePosition, userAction); + + if (mouseButton != MouseButton.Left) { - mDepressed = true; - InputHandler.MouseFocus = this; + return; } - else + + mDepressed = true; + InputHandler.MouseFocus = this; + } + + protected override void OnMouseUp(MouseButton mouseButton, Point mousePosition, bool userAction = true) + { + base.OnMouseUp(mouseButton, mousePosition, userAction); + + if (mouseButton != MouseButton.Left) + { + return; + } + + var localCoordinates = CanvasPosToLocal(mousePosition); + if (localCoordinates.X < mBar.X) + { + NudgeLeft(this, EventArgs.Empty); + } + else if (localCoordinates.X > mBar.X + mBar.Width) { - var clickPos = CanvasPosToLocal(new Point(x, y)); - if (clickPos.X < mBar.X) - { - NudgeLeft(this, EventArgs.Empty); - } - else if (clickPos.X > mBar.X + mBar.Width) - { - NudgeRight(this, EventArgs.Empty); - } - - mDepressed = false; - InputHandler.MouseFocus = null; + NudgeRight(this, EventArgs.Empty); } + + mDepressed = false; + InputHandler.MouseFocus = null; } protected override float CalculateScrolledAmount() @@ -196,7 +192,7 @@ public override bool SetScrollAmount(float value, bool forceUpdate = false) /// Event source. protected override void OnBarMoved(Base control, EventArgs args) { - if (mBar.IsHeld) + if (mBar.IsActive) { SetScrollAmount(CalculateScrolledAmount(), false); base.OnBarMoved(control, args); diff --git a/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs b/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs deleted file mode 100644 index d863183923..0000000000 --- a/Intersect.Client.Framework/Gwen/Control/HorizontalSlider.cs +++ /dev/null @@ -1,62 +0,0 @@ -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) - { - mSliderBar.IsHorizontal = true; - } - - protected override float CalculateValue() - { - return (float) mSliderBar.X / (Width - mSliderBar.Width); - } - - protected override void UpdateBarFromValue() - { - mSliderBar.MoveTo((int) ((Width - mSliderBar.Width) * mValue), mSliderBar.Y); - } - - /// - /// Handler invoked on mouse click (left) event. - /// - /// X coordinate. - /// Y coordinate. - /// If set to true mouse button is down. - 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); - } - - /// - /// Lays out the control's interior according to alignment, padding, dock etc. - /// - /// Skin to use. - protected override void Layout(Skin.Base skin) - { - //m_SliderBar.SetSize(15, Height); - UpdateBarFromValue(); - } - - /// - /// Renders the control using specified skin. - /// - /// Skin to use. - protected override void Render(Skin.Base skin) - { - skin.DrawSlider(this, true, Notches, mSnapToNotches ? mNotchCount : 0, mSliderBar.Width); - } - -} diff --git a/Intersect.Client.Framework/Gwen/Control/ILabel.cs b/Intersect.Client.Framework/Gwen/Control/ILabel.cs index 061132ba1a..760c270d17 100644 --- a/Intersect.Client.Framework/Gwen/Control/ILabel.cs +++ b/Intersect.Client.Framework/Gwen/Control/ILabel.cs @@ -2,23 +2,27 @@ namespace Intersect.Client.Framework.Gwen.Control; -public interface ILabel : IColorableText, IAutoSizeToContents +public interface ILabel : IAutoSizeToContents, IColorableText { Pos TextAlign { get; set; } GameFont? Font { get; set; } - string FontName { get; set; } + string? FontName { get; set; } int FontSize { get; set; } - Color TextColor { get; set; } + Color? TextColor { get; set; } - Color TextColorOverride { get; set; } + Color? TextColorOverride { get; set; } Padding TextPadding { get; set; } - void SizeToContents(); + /// + /// Resizes the component to fit the contents. + /// + /// If the size was changed. + bool SizeToContents(); void UpdateColors(); } diff --git a/Intersect.Client.Framework/Gwen/Control/ISmartAutoSizeToContents.cs b/Intersect.Client.Framework/Gwen/Control/ISmartAutoSizeToContents.cs new file mode 100644 index 0000000000..f2552be1d1 --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/ISmartAutoSizeToContents.cs @@ -0,0 +1,22 @@ +namespace Intersect.Client.Framework.Gwen.Control; + +public interface ISmartAutoSizeToContents : IAutoSizeToContents +{ + bool IAutoSizeToContents.AutoSizeToContents + { + get => AutoSizeToContentWidth || AutoSizeToContentHeight; + set + { + AutoSizeToContentWidth = value; + AutoSizeToContentHeight = value; + } + } + + bool AutoSizeToContentWidth { get; set; } + + bool AutoSizeToContentHeight { get; set; } + + bool AutoSizeToContentWidthOnChildResize { get; set; } + + bool AutoSizeToContentHeightOnChildResize { get; set; } +} \ No newline at end of file diff --git a/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs b/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs index 6f18c1d71e..de23670bb9 100644 --- a/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs +++ b/Intersect.Client.Framework/Gwen/Control/ImagePanel.cs @@ -4,6 +4,7 @@ using Intersect.Client.Framework.Graphics; using Intersect.Client.Framework.Gwen.ControlInternal; using Intersect.Client.Framework.Gwen.Skin.Texturing; +using Intersect.Client.Framework.Input; using Newtonsoft.Json.Linq; namespace Intersect.Client.Framework.Gwen.Control; @@ -44,10 +45,6 @@ public ImagePanel(Base parent, string? name = default) : base(parent, name: name _uv = [0, 0, 1, 1]; MouseInputEnabled = true; - - Clicked += ImagePanel_Clicked; - RightClicked += ImagePanel_RightClicked; - HoverEnter += ImagePanel_HoverEnter; } public Margin? TextureNinePatchMargin @@ -109,19 +106,23 @@ public string? TextureFilename } } - private void ImagePanel_HoverEnter(Base sender, EventArgs arguments) + protected override void OnMouseEntered() { + base.OnMouseEntered(); PlaySound(mHoverSound); } - private void ImagePanel_RightClicked(Base sender, EventArguments.ClickedEventArgs arguments) + protected override void OnMouseClicked(MouseButton mouseButton, Point mousePosition, bool userAction = true) { - PlaySound(mRightMouseClickSound); - } - - private void ImagePanel_Clicked(Base sender, EventArguments.ClickedEventArgs arguments) - { - PlaySound(mLeftMouseClickSound); + base.OnMouseClicked(mouseButton, mousePosition, userAction); + PlaySound( + mouseButton switch + { + MouseButton.Left => mLeftMouseClickSound, + MouseButton.Right => mRightMouseClickSound, + _ => null, + } + ); } /// @@ -132,17 +133,21 @@ public override void Dispose() base.Dispose(); } - public override JObject GetJson(bool isRoot = default) + public override JObject? GetJson(bool isRoot = false, bool onlySerializeIfNotEmpty = false) { - var obj = base.GetJson(isRoot); + var serializedProperties = base.GetJson(isRoot, onlySerializeIfNotEmpty); + if (serializedProperties is null) + { + return null; + } - obj.Add(nameof(Texture), TextureFilename); - obj.Add(nameof(TextureNinePatchMargin), TextureNinePatchMargin?.ToString()); - obj.Add("HoverSound", mHoverSound); - obj.Add("LeftMouseClickSound", mLeftMouseClickSound); - obj.Add("RightMouseClickSound", mRightMouseClickSound); + serializedProperties.Add(nameof(Texture), TextureFilename); + serializedProperties.Add(nameof(TextureNinePatchMargin), TextureNinePatchMargin?.ToString()); + serializedProperties.Add("HoverSound", mHoverSound); + serializedProperties.Add("LeftMouseClickSound", mLeftMouseClickSound); + serializedProperties.Add("RightMouseClickSound", mRightMouseClickSound); - return base.FixJson(obj); + return base.FixJson(serializedProperties); } public override void LoadJson(JToken token, bool isRoot = default) @@ -377,7 +382,7 @@ protected override bool OnKeySpace(bool down) { if (down) { - base.OnMouseClickedLeft(0, 0, true); + base.OnMouseDown(MouseButton.Left, default); } return true; diff --git a/Intersect.Client.Framework/Gwen/Control/Label.cs b/Intersect.Client.Framework/Gwen/Control/Label.cs index 39330c303c..7b3ae2eac4 100644 --- a/Intersect.Client.Framework/Gwen/Control/Label.cs +++ b/Intersect.Client.Framework/Gwen/Control/Label.cs @@ -1,9 +1,12 @@ using System.Runtime.CompilerServices; using Intersect.Client.Framework.Content; using Intersect.Client.Framework.File_Management; +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.Core; +using Microsoft.Extensions.Logging; using Newtonsoft.Json.Linq; namespace Intersect.Client.Framework.Gwen.Control; @@ -14,27 +17,13 @@ namespace Intersect.Client.Framework.Gwen.Control; /// public partial class Label : Base, ILabel { - - public enum ControlState - { - - Normal = 0, - - Hovered, - - Clicked, - - Disabled, - - } - protected readonly Text _textElement; - private string fontInfo; + private string? _fontInfo; - private Pos mAlign; + private Pos _textAlign; - private bool mAutoSizeToContents; + private bool _autoSizeToContents; private string? mBackgroundTemplateFilename; @@ -48,7 +37,12 @@ public enum ControlState protected Color? mNormalTextColor; - private Padding mTextPadding; + private Padding _textPadding; + + private string? _displayedText; + private string? _text; + private string? _formatString; + private Range _replacementRange; /// /// Initializes a new instance of the class. @@ -67,9 +61,115 @@ public Label(Base parent, string? name = default, bool disableText = false) : ba SetSize(100, 10); TextAlign = Pos.Left | Pos.Top; - mAutoSizeToContents = true; + _autoSizeToContents = true; + } + + public string? FormatString + { + get => _formatString; + set + { + if (string.Equals(value, _formatString, StringComparison.Ordinal)) + { + return; + } + + _replacementRange = GetRangeForFormatArgument(value, 0); + _formatString = value; + } } + private static Range GetRangeForFormatArgument(string? format, int argumentIndex) + { + const char charStartArgument = '{'; + const char charEndArgument = '}'; + + if (argumentIndex < 0) + { + throw new ArgumentOutOfRangeException(nameof(argumentIndex), argumentIndex, "Must be non-negative"); + } + + if (string.IsNullOrEmpty(format)) + { + return default; + } + + var minimumDigits = argumentIndex < 10 ? 1 : (int)Math.Floor(Math.Log10(argumentIndex)); + if (format.Length < 2 + minimumDigits) + { + return default; + } + + char searchChar = charStartArgument; + int? formatSplit = null; + Range argumentIndexCharacterRange = default; + for (var characterIndex = 0; characterIndex < format.Length; ++characterIndex) + { + var currentChar = format[characterIndex]; + switch (currentChar) + { + case charStartArgument: + searchChar = charEndArgument; + formatSplit = null; + argumentIndexCharacterRange = new Range(characterIndex + 1, characterIndex + 1); + continue; + + case charEndArgument: + { + if (searchChar == charStartArgument) + { + continue; + } + + searchChar = charStartArgument; + formatSplit = null; + argumentIndexCharacterRange = new Range( + argumentIndexCharacterRange.Start, + (formatSplit ?? characterIndex) + ); + + if (IsArgument(format, argumentIndexCharacterRange, argumentIndex)) + { + return new Range( + argumentIndexCharacterRange.Start.Value - 1, + ^(format.Length - (argumentIndexCharacterRange.End.Value + 1)) + ); + } + + argumentIndexCharacterRange = default; + + continue; + } + + case ':': + formatSplit = characterIndex; + + Range offsetRange = new(argumentIndexCharacterRange.Start, characterIndex); + if (IsArgument(format, offsetRange, argumentIndex)) + { + continue; + } + + searchChar = charStartArgument; + formatSplit = null; + continue; + + default: + if (formatSplit == null && !char.IsAsciiDigit(currentChar)) + { + searchChar = charStartArgument; + } + + break; + } + } + + return argumentIndexCharacterRange; + } + + private static bool IsArgument(string format, Range range, int argumentIndex) => + int.TryParse(format[range], out var index) && index == argumentIndex; + public WrappingBehavior WrappingBehavior { get => _wrappingBehavior; @@ -99,13 +199,19 @@ public GameTexture? ToolTipBackground } } + public bool IsTextDisabled + { + get => _textElement.IsHidden; + set => _textElement.IsHidden = value; + } + /// /// Text alignment. /// public Pos TextAlign { - get => mAlign; - set => SetAndDoIfChanged(ref mAlign, value, Invalidate); + get => _textAlign; + set => SetAndDoIfChanged(ref _textAlign, value, Invalidate); } public override void Invalidate() @@ -113,8 +219,6 @@ public override void Invalidate() base.Invalidate(); } - private string? _text; - /// /// Text. /// @@ -123,16 +227,7 @@ public virtual string? Text [MethodImpl(MethodImplOptions.AggressiveInlining)] get => _text; [MethodImpl(MethodImplOptions.AggressiveInlining)] - set - { - if (string.Equals(value, _text, StringComparison.Ordinal)) - { - return; - } - - _text = value; - _textElement.DisplayedText = _text; - } + set => SetText(value, doEvents: true); } /// @@ -148,18 +243,40 @@ public virtual GameFont? Font return; } - _textElement.Font = value; - fontInfo = $"{value?.GetName()},{value?.GetSize()}"; + var oldValue = _textElement.Font; + if (value is not null) + { + _textElement.Font = value; + _fontInfo = $"{value.GetName()},{value.GetSize()}"; + } + else + { + _fontInfo = null; + } + + OnFontChanged(this, oldValue, value); - if (mAutoSizeToContents) + if (_autoSizeToContents) { SizeToContents(); } Invalidate(); + + FontChanged?.Invoke(this, new ValueChangedEventArgs + { + OldValue = oldValue, + Value = value, + }); } } + public event GwenEventHandler>? FontChanged; + + protected virtual void OnFontChanged(Base sender, GameFont? oldFont, GameFont? newFont) + { + } + /// /// Font Name /// @@ -235,7 +352,7 @@ public string? TextOverride /// /// Text length (in characters). /// - public int TextLength => _textElement.Length; + public int TextLength => _text?.Length ?? 0; public int TextRight => _textElement.Right; @@ -244,10 +361,10 @@ public string? TextOverride /// public bool AutoSizeToContents { - get => mAutoSizeToContents; + get => _autoSizeToContents; set { - mAutoSizeToContents = value; + _autoSizeToContents = value; Invalidate(); InvalidateParent(); } @@ -258,34 +375,39 @@ public bool AutoSizeToContents /// public Padding TextPadding { - get => mTextPadding; + get => _textPadding; set { - mTextPadding = value; + _textPadding = value; Invalidate(); InvalidateParent(); } } - public override JObject GetJson(bool isRoot = default) + public override JObject? GetJson(bool isRoot = false, bool onlySerializeIfNotEmpty = false) { - var obj = base.GetJson(isRoot); + var serializedProperties = base.GetJson(isRoot, onlySerializeIfNotEmpty); + if (serializedProperties is null) + { + return null; + } + if (typeof(Label) == GetType()) { - obj.Add("BackgroundTemplate", mBackgroundTemplateFilename); + serializedProperties.Add("BackgroundTemplate", mBackgroundTemplateFilename); } - obj.Add("TextColor", Color.ToString(TextColor)); - obj.Add("HoveredTextColor", Color.ToString(mHoverTextColor)); - obj.Add("ClickedTextColor", Color.ToString(mClickedTextColor)); - obj.Add("DisabledTextColor", Color.ToString(mDisabledTextColor)); - obj.Add("TextAlign", mAlign.ToString()); - obj.Add("TextPadding", Padding.ToString(mTextPadding)); - obj.Add("AutoSizeToContents", mAutoSizeToContents); - obj.Add("Font", fontInfo); - obj.Add("TextScale", _textElement.GetScale()); + serializedProperties.Add(nameof(TextColor), mNormalTextColor?.ToString()); + serializedProperties.Add("HoveredTextColor", mHoverTextColor?.ToString()); + serializedProperties.Add("ClickedTextColor", mClickedTextColor?.ToString()); + serializedProperties.Add("DisabledTextColor", mDisabledTextColor?.ToString()); + serializedProperties.Add(nameof(TextAlign), TextAlign.ToString()); + serializedProperties.Add(nameof(TextPadding), _textPadding.ToString()); + serializedProperties.Add(nameof(AutoSizeToContents), _autoSizeToContents); + serializedProperties.Add(nameof(Font), _fontInfo); + serializedProperties.Add("TextScale", _textElement.GetScale()); - return base.FixJson(obj); + return base.FixJson(serializedProperties); } public override void LoadJson(JToken obj, bool isRoot = default) @@ -316,10 +438,14 @@ public override void LoadJson(JToken obj, bool isRoot = default) mClickedTextColor = Color.FromString((string)obj["ClickedTextColor"]); } - mDisabledTextColor = Color.FromString((string)obj["DisabledTextColor"], new Color(255, 90, 90, 90)); + if (obj["DisabledTextColor"] != null) + { + mDisabledTextColor = Color.FromString((string)obj["DisabledTextColor"]); + } + if (obj["TextAlign"] != null) { - mAlign = (Pos)Enum.Parse(typeof(Pos), (string)obj["TextAlign"]); + TextAlign = (Pos)Enum.Parse(typeof(Pos), (string)obj["TextAlign"]); } if (obj["TextPadding"] != null) @@ -329,7 +455,7 @@ public override void LoadJson(JToken obj, bool isRoot = default) if (obj["AutoSizeToContents"] != null) { - mAutoSizeToContents = (bool)obj["AutoSizeToContents"]; + _autoSizeToContents = (bool)obj["AutoSizeToContents"]; } var tokenFont = obj["Font"]; @@ -344,7 +470,7 @@ public override void LoadJson(JToken obj, bool isRoot = default) ); if (fontArr.Length > 1) { - fontInfo = stringFont; + _fontInfo = stringFont; try { Font = GameContentManager.Current.GetFont(fontArr[0], int.Parse(fontArr[1])); @@ -399,25 +525,25 @@ public void SetBackgroundTemplate(GameTexture texture, string fileName) public virtual void MakeColorNormal() { - TextColor = Skin.Colors.Label.Default; + TextColor = Skin.Colors.Label.Normal; } public virtual void MakeColorBright() { - TextColor = Skin.Colors.Label.Bright; + TextColor = Skin.Colors.Label.Hover; } public virtual void MakeColorDark() { - TextColor = Skin.Colors.Label.Dark; + TextColor = Skin.Colors.Label.Disabled; } public virtual void MakeColorHighlight() { - TextColor = Skin.Colors.Label.Highlight; + TextColor = Skin.Colors.Label.Active; } - public override event GwenEventHandler Clicked + public override event GwenEventHandler Clicked { add { @@ -431,7 +557,7 @@ public override event GwenEventHandler Clicked } } - public override event GwenEventHandler DoubleClicked + public override event GwenEventHandler DoubleClicked { add { @@ -445,47 +571,40 @@ public override event GwenEventHandler DoubleClicked } } - public override event GwenEventHandler RightClicked - { - add - { - base.RightClicked += value; - MouseInputEnabled = ClickEventAssigned; - } - remove - { - base.RightClicked -= value; - MouseInputEnabled = ClickEventAssigned; - } - } - - public override event GwenEventHandler DoubleRightClicked - { - add - { - base.DoubleRightClicked += value; - MouseInputEnabled = ClickEventAssigned; - } - remove - { - base.DoubleRightClicked -= value; - MouseInputEnabled = ClickEventAssigned; - } - } - /// /// Returns index of the character closest to specified point (in canvas coordinates). /// /// /// - /// /// - protected virtual Point GetClosestCharacter(int x, int y) + protected virtual Point GetClosestCharacter(int x, int y) => GetClosestCharacter(new Point(x, y)); + + protected Point GetClosestCharacter(Point point) { - var coordinates = new Point(x, y); - coordinates = _textElement.CanvasPosToLocal(coordinates); - var closestCharacterIndex = _textElement.GetClosestCharacter(coordinates); - return new Point(closestCharacterIndex, 0); + point = _textElement.CanvasPosToLocal(point); + var closestCharacterIndex = _textElement.GetClosestCharacter(point); + Point closestCharacterPoint = new(closestCharacterIndex, 0); + + var replacementRange = _replacementRange; + if (_textOverride != null || _displayedText is not { } displayedText || replacementRange.Equals(default)) + { + return closestCharacterPoint; + } + + var startIndex = replacementRange.Start.Value; + var endIndex = replacementRange.End.Value; + if (replacementRange.End.IsFromEnd) + { + endIndex = displayedText.Length - endIndex; + } + + closestCharacterPoint.X = Math.Clamp( + closestCharacterPoint.X, + startIndex, + endIndex + ); + + return closestCharacterPoint; } /// @@ -513,7 +632,7 @@ protected override void Layout(Skin.Base skin) { base.Layout(skin); - if (mAutoSizeToContents) + if (_autoSizeToContents) { SizeToContents(); } @@ -523,41 +642,33 @@ protected override void Layout(Skin.Base skin) protected void AlignTextElement(Text textElement) { - var align = mAlign; + var align = TextAlign; + var textOuterWidth = textElement.OuterWidth; + var textOuterHeight = textElement.OuterHeight; + var textPadding = TextPadding; - var x = mTextPadding.Left + Padding.Left; - var y = mTextPadding.Top + Padding.Top; + var availableWidth = Width - (textPadding.Left + textPadding.Right); + var availableHeight = Height - (textPadding.Top + textPadding.Bottom); - if (0 != (align & Pos.Right)) + var x = textPadding.Left; + var y = textPadding.Top; + + if (align.HasFlag(Pos.CenterH)) { - x = Width - textElement.Width - mTextPadding.Right - Padding.Right; + x = textPadding.Left + (int)((availableWidth - textOuterWidth) / 2f); } - - if (0 != (align & Pos.CenterH)) + else if (align.HasFlag(Pos.Right)) { - x = (int)(mTextPadding.Left + - Padding.Left + - (Width - - textElement.Width - - mTextPadding.Left - - Padding.Left - - mTextPadding.Right - - Padding.Right) * - 0.5f); + x = availableWidth - textOuterWidth; } - if (0 != (align & Pos.CenterV)) + if (align.HasFlag(Pos.CenterV)) { - y = (int)(mTextPadding.Top + - Padding.Top + - (Height - textElement.Height) * 0.5f - - mTextPadding.Bottom - - Padding.Bottom); + y = textPadding.Top + (int)((availableHeight - textOuterHeight) / 2f); } - - if (0 != (align & Pos.Bottom)) + else if (align.HasFlag(Pos.Bottom)) { - y = Height - textElement.Height - mTextPadding.Bottom - Padding.Bottom; + y = availableHeight - textOuterHeight; } textElement.SetPosition(x, y); @@ -568,7 +679,7 @@ protected void AlignTextElement(Text textElement) /// /// Text to set. /// Determines whether to invoke "text changed" event. - public virtual void SetText(string text, bool doEvents = true) + public virtual void SetText(string? text, bool doEvents = true) { if (string.Equals(_text, text, StringComparison.Ordinal)) { @@ -576,26 +687,36 @@ public virtual void SetText(string text, bool doEvents = true) } _text = text; - _textElement.DisplayedText = text; + _displayedText = string.IsNullOrWhiteSpace(_formatString) ? _text : string.Format(_formatString, _text); + _textElement.DisplayedText = _displayedText; - if (mAutoSizeToContents) + var sizeChanged = _autoSizeToContents && SizeToContents(); + if (sizeChanged) { - SizeToContents(); + Invalidate(); + InvalidateParent(); } - Invalidate(); - InvalidateParent(); - if (doEvents) { OnTextChanged(); } } + protected override void OnChildBoundsChanged(Base child, Rectangle oldChildBounds, Rectangle newChildBounds) + { + base.OnChildBoundsChanged(child, oldChildBounds, newChildBounds); + + if (_autoSizeToContents && oldChildBounds.Size != newChildBounds.Size) + { + Invalidate(); + } + } + public virtual void SetTextScale(float scale) { _textElement.SetScale(scale); - if (mAutoSizeToContents) + if (_autoSizeToContents) { SizeToContents(); } @@ -604,23 +725,50 @@ public virtual void SetTextScale(float scale) InvalidateParent(); } - public virtual void SizeToContents() - { - _textElement.SetPosition(mTextPadding.Left + Padding.Left, mTextPadding.Top + Padding.Top); + protected virtual Point GetContentSize() => _textElement.Size; - var newWidth = _textElement.Width + Padding.Left + Padding.Right + mTextPadding.Left + mTextPadding.Right; - newWidth = Math.Max(newWidth, MinimumSize.X); + protected virtual Padding GetContentPadding() => Padding + _textPadding; - var newHeight = _textElement.Height + Padding.Top + Padding.Bottom + mTextPadding.Top + mTextPadding.Bottom; - newHeight = Math.Max(newHeight, MinimumSize.Y); + public virtual bool SizeToContents() + { + var newSize = MeasureShrinkToContents(); - if (!SetSize(newWidth, newHeight)) + var oldSize = Size; + if (!SetSize(newSize)) { - return; + return false; } ProcessAlignments(); InvalidateParent(); + + SizedToContents?.Invoke( + this, + new ValueChangedEventArgs + { + Value = newSize, OldValue = oldSize, + } + ); + + return true; + } + + public GwenEventHandler>? SizedToContents; + + public virtual Point MeasureShrinkToContents() + { + var contentPadding = GetContentPadding(); + _textElement.SetPosition(contentPadding.Left, contentPadding.Top); + + var contentSize = GetContentSize(); + + var newWidth = contentSize.X + contentPadding.Left + contentPadding.Right; + newWidth = Math.Max(newWidth, MinimumSize.X); + + var newHeight = contentSize.Y + contentPadding.Top + contentPadding.Bottom; + newHeight = Math.Max(newHeight, MinimumSize.Y); + + return new Point(newWidth, newHeight); } /// @@ -649,57 +797,54 @@ protected override void Render(Skin.Base skin) /// public override void UpdateColors() { - var textColor = GetTextColor(ControlState.Normal); - if (IsDisabled && GetTextColor(ControlState.Disabled) != null) - { - textColor = GetTextColor(ControlState.Disabled); - } - else if (IsHovered && GetTextColor(ControlState.Hovered) != null) + var textColor = GetTextColor(ComponentState.Normal) ?? Skin.Colors.Label.Normal; + if (IsDisabledByTree) { - textColor = GetTextColor(ControlState.Hovered); + textColor = GetTextColor(ComponentState.Disabled) ?? Skin.Colors.Label.Disabled; } - - if (textColor != null) + else if (IsActive) { - TextColor = textColor; - - return; + textColor = GetTextColor(ComponentState.Active) ?? Skin.Colors.Label.Active; } - - if (IsDisabled) + else if (IsHovered) { - TextColor = Skin.Colors.Button.Disabled; - - return; + textColor = GetTextColor(ComponentState.Hovered) ?? Skin.Colors.Label.Hover; } - if (IsHovered && ClickEventAssigned) + // ReSharper disable once ConditionIsAlwaysTrueOrFalseAccordingToNullableAPIContract + if (textColor == null) { - TextColor = Skin.Colors.Button.Hover; + ApplicationContext.CurrentContext.Logger.LogError( + "Text color for the current control state of '{ComponentName}' is somehow null IsDisabled={IsDisabled} IsActive={IsActive} IsHovered={IsHovered}", + CanonicalName, + IsDisabled, + IsActive, + IsHovered + ); - return; + textColor = new Color(r: 255, g: 0, b: 255); } - TextColor = Skin.Colors.Button.Normal; + TextColor = textColor; } - public virtual void SetTextColor(Color clr, ControlState state) + public virtual void SetTextColor(Color clr, ComponentState state) { switch (state) { - case ControlState.Normal: + case ComponentState.Normal: mNormalTextColor = clr; break; - case ControlState.Hovered: + case ComponentState.Hovered: mHoverTextColor = clr; break; - case ControlState.Clicked: + case ComponentState.Active: mClickedTextColor = clr; break; - case ControlState.Disabled: + case ComponentState.Disabled: mDisabledTextColor = clr; break; @@ -710,21 +855,20 @@ public virtual void SetTextColor(Color clr, ControlState state) UpdateColors(); } - public virtual Color? GetTextColor(ControlState state) + public virtual Color? GetTextColor(ComponentState state) { switch (state) { - case ControlState.Normal: + case ComponentState.Normal: return mNormalTextColor; - case ControlState.Hovered: + case ComponentState.Hovered: return mHoverTextColor; - case ControlState.Clicked: + case ComponentState.Active: return mClickedTextColor; - case ControlState.Disabled: + case ComponentState.Disabled: return mDisabledTextColor; default: return null; } } - -} +} \ No newline at end of file diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs b/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs index be3c988964..b5b79a2dc2 100644 --- a/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs +++ b/Intersect.Client.Framework/Gwen/Control/LabeledCheckBox.cs @@ -10,8 +10,7 @@ namespace Intersect.Client.Framework.Gwen.Control; /// public partial class LabeledCheckBox : Base { - - private readonly CheckBox mCheckBox; + private readonly Checkbox _checkbox; private readonly Label _label; @@ -19,29 +18,35 @@ public partial class LabeledCheckBox : Base /// Initializes a new instance of the class. /// /// Parent control. - public LabeledCheckBox(Base parent, string name = "") : base(parent, name) + /// + public LabeledCheckBox(Base parent, string? name = default) : base(parent: parent, name: name) { - _ = SetSize(208, 26); + Size = new Point(208, 26); - mCheckBox = new CheckBox(this) + _checkbox = new Checkbox(this, name: nameof(_checkbox)) { InheritParentEnablementProperties = true, Dock = Pos.Left, Margin = new Margin(0, 2, 2, 2), IsTabable = false, }; - mCheckBox.CheckChanged += OnCheckChanged; + _checkbox.CheckChanged += OnCheckChanged; - _label = new Label(this) + _label = new Label(this, name: nameof(_label)) { - AutoSizeToContents = false, - Dock = Pos.Fill, + Alignment = [Alignments.CenterV], + AutoSizeToContents = true, + Dock = Pos.Fill | Pos.CenterV, InheritParentEnablementProperties = true, IsTabable = false, - Padding = new Padding(2, 0, 0, 0), TextAlign = Pos.CenterV | Pos.Left, + TextPadding = new Padding(2/*, 0, 0, 0*/), + }; + _label.Clicked += delegate(Base control, MouseButtonState _) + { + _label.ProcessAlignments(); + _checkbox.Press(control); }; - _label.Clicked += delegate (Base control, ClickedEventArgs args) { mCheckBox.Press(control); }; IsTabable = false; } @@ -51,8 +56,8 @@ public LabeledCheckBox(Base parent, string name = "") : base(parent, name) /// public bool IsChecked { - get => mCheckBox.IsChecked; - set => mCheckBox.IsChecked = value; + get => _checkbox.IsChecked; + set => _checkbox.IsChecked = value; } public GameFont? Font @@ -61,7 +66,7 @@ public GameFont? Font set => _label.Font = value; } - public string FontName + public string? FontName { get => _label.FontName; set => _label.FontName = value; @@ -109,7 +114,7 @@ public override int TooltipFontSize set => _label.TooltipFontSize = value; } - public override Color TooltipTextColor + public override Color? TooltipTextColor { get => _label.TooltipTextColor; set => _label.TooltipTextColor = value; @@ -118,7 +123,7 @@ public override Color TooltipTextColor /// /// Label text. /// - public string Text + public string? Text { get => _label.Text; set => _label.Text = value; @@ -127,87 +132,53 @@ public string Text /// /// Invoked when the control has been checked. /// - public event GwenEventHandler Checked; + public event GwenEventHandler? Checked; /// /// Invoked when the control has been unchecked. /// - public event GwenEventHandler UnChecked; + public event GwenEventHandler? Unchecked; /// /// Invoked when the control's check has been changed. /// public event GwenEventHandler CheckChanged; - public override JObject GetJson(bool isRoot = default) - { - var obj = base.GetJson(isRoot); - obj.Add("Label", _label.GetJson()); - obj.Add("Checkbox", mCheckBox.GetJson()); - - return base.FixJson(obj); - } - - public override void LoadJson(JToken obj, bool isRoot = default) - { - base.LoadJson(obj); - if (obj["Label"] != null) - { - _label.Dock = Pos.None; - _label.LoadJson(obj["Label"]); - } - - if (obj["Checkbox"] != null) - { - mCheckBox.Dock = Pos.None; - mCheckBox.LoadJson(obj["Checkbox"]); - } - } - /// /// Handler for CheckChanged event. /// protected virtual void OnCheckChanged(Base control, EventArgs args) { - if (mCheckBox.IsChecked) + if (_checkbox.IsChecked) { - if (Checked != null) - { - Checked.Invoke(this, EventArgs.Empty); - } + Checked?.Invoke(this, EventArgs.Empty); } else { - if (UnChecked != null) - { - UnChecked.Invoke(this, EventArgs.Empty); - } + Unchecked?.Invoke(this, EventArgs.Empty); } - if (CheckChanged != null) - { - CheckChanged.Invoke(this, EventArgs.Empty); - } + CheckChanged?.Invoke(this, EventArgs.Empty); } public void SetCheckSize(int w, int h) { - mCheckBox.SetSize(w, h); + _checkbox.SetSize(w, h); } - public void SetImage(GameTexture texture, string fileName, CheckBox.ControlState state) + public void SetImage(GameTexture texture, string fileName, Checkbox.ControlState state) { - mCheckBox.SetImage(texture, fileName, state); + _checkbox.SetImage(texture, fileName, state); } - public void SetTextColor(Color clr, Label.ControlState state) + public void SetTextColor(Color clr, ComponentState state) { _label.SetTextColor(clr, state); } public void SetLabelDistance(int dist) { - mCheckBox.Margin = new Margin(0, 2, dist, 2); + _checkbox.Margin = new Margin(0, 2, dist, 2); } public void SetFont(GameFont font) @@ -227,7 +198,7 @@ protected override bool OnKeySpace(bool down) base.OnKeySpace(down); if (!down) { - mCheckBox.IsChecked = !mCheckBox.IsChecked; + _checkbox.IsChecked = !_checkbox.IsChecked; } return true; diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledComboBox.cs b/Intersect.Client.Framework/Gwen/Control/LabeledComboBox.cs new file mode 100644 index 0000000000..5e962d7e81 --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/LabeledComboBox.cs @@ -0,0 +1,115 @@ +using Intersect.Client.Framework.GenericClasses; +using Intersect.Client.Framework.Graphics; +using Intersect.Client.Framework.Gwen.Control.EventArguments; + +namespace Intersect.Client.Framework.Gwen.Control; + +public partial class LabeledComboBox : Base, IAutoSizeToContents +{ + private readonly ComboBox _comboBox; + private readonly Label _label; + private bool _autoSizeToContents; + + public event GwenEventHandler? ItemSelected; + + public LabeledComboBox(Base parent, string? name = default) : base(parent: parent, name: name) + { + _autoSizeToContents = true; + + _label = new Label(this, name: nameof(_label)) + { + Alignment = [Alignments.CenterV], + Dock = Pos.Left, + }; + + _comboBox = new ComboBox(this, name: nameof(_comboBox)) + { + Alignment = [Alignments.CenterV], + Dock = Pos.Left, + Margin = new Margin(4, 0, 0, 0), + TextPadding = Padding.Two, + }; + + _comboBox.ItemSelected += (_, args) => ItemSelected?.Invoke(this, args); + } + + public GameFont? Font + { + get => _label.Font; + set + { + _label.Font = value; + _comboBox.Font = value; + } + } + + public GameFont? LabelFont + { + get => _label.Font; + set => _label.Font = value; + } + + public GameFont? ItemFont + { + get => _comboBox.Font; + set => _comboBox.Font = value; + } + + public string? Label + { + get => _label.Text; + set => _label.Text = value; + } + + public MenuItem? SelectedItem + { + get => _comboBox.SelectedItem; + set => _comboBox.SelectedItem = value; + } + + public MenuItem AddItem(string label, string? name = default, object? userData = default) + { + return _comboBox.AddItem(label, name, userData); + } + + public bool SelectByName(string name) => _comboBox.SelectByName(name); + + public bool SelectByText(string text) => _comboBox.SelectByText(text); + + public bool SelectByUserData(object? userData) => _comboBox.SelectByUserData(userData); + + public bool AutoSizeToContents + { + get => _autoSizeToContents; + set => _autoSizeToContents = value; + } + + public Padding LabelTextPadding + { + get => _label.TextPadding; + set => _label.TextPadding = value; + } + + public Padding TextPadding + { + get => _comboBox.TextPadding; + set => _comboBox.TextPadding = value; + } + + protected override void Layout(Skin.Base skin) + { + if (_autoSizeToContents) + { + SizeToChildren(); + } + + base.Layout(skin); + } + + protected override void OnChildBoundsChanged(Base child, Rectangle oldChildBounds, Rectangle newChildBounds) + { + base.OnChildBoundsChanged(child, oldChildBounds, newChildBounds); + + Invalidate(); + } +} \ No newline at end of file diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs b/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs deleted file mode 100644 index 9aeeb2d693..0000000000 --- a/Intersect.Client.Framework/Gwen/Control/LabeledHorizontalSlider.cs +++ /dev/null @@ -1,171 +0,0 @@ -using Intersect.Client.Framework.GenericClasses; -using Newtonsoft.Json.Linq; - -namespace Intersect.Client.Framework.Gwen.Control; - - -/// -/// Base slider. -/// -public partial class LabeledHorizontalSlider : Base -{ - private readonly Label _label; - private readonly HorizontalSlider _slider; - private readonly ImagePanel _sliderBackground; - private readonly TextBoxNumeric _sliderValue; - private double _scale = 1.0; - - /// - /// Initializes a new instance of the class. - /// - /// Parent control. - public LabeledHorizontalSlider(Base parent, string name = "") : base(parent, name) - { - SetBounds(new Rectangle(0, 0, 32, 128)); - - _label = new Label(this, "SliderLabel"); - - _sliderBackground = new ImagePanel(this, "SliderBackground"); - _slider = new HorizontalSlider(_sliderBackground, "Slider"); - _sliderValue = new TextBoxNumeric(_sliderBackground, "SliderValue"); - - _slider.ValueChanged += (sender, arguments) => - { - if (sender == _sliderValue) - { - return; - } - - _sliderValue.Value = _slider.Value * _scale; - ValueChanged?.Invoke(sender, arguments); - }; - - _sliderValue.TextChanged += (sender, arguments) => - { - if (sender == _slider) - { - return; - } - - _slider.Value = _sliderValue.Value / _scale; - ValueChanged?.Invoke(sender, arguments); - }; - - KeyboardInputEnabled = true; - IsTabable = true; - } - - public string Label - { - get => _label.Text; - set => _label.Text = 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; - } - - /// - /// Minimum value. - /// - public double Min - { - get => _slider.Min; - set => _slider.Min = value; - } - - /// - /// Maximum value. - /// - public double Max - { - get => _slider.Max; - set => _slider.Max = value; - } - - 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; - _sliderBackground.IsDisabled = value; - } - } - - /// - /// Invoked when the value has been changed. - /// - public event GwenEventHandler ValueChanged; - - public void SetRange(double min, double max) - { - _slider.SetRange(min, max); - } - - public override JObject GetJson(bool isRoot = default) - { - var obj = base.GetJson(isRoot); - - return base.FixJson(obj); - } - - public override void LoadJson(JToken obj, bool isRoot = default) - { - base.LoadJson(obj); - } - - public override void SetToolTipText(string? text) - { - _label.SetToolTipText(text); - _slider.SetToolTipText(text); - _sliderValue.SetToolTipText(text); - _sliderBackground.SetToolTipText(text); - } -} diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledRadioButton.cs b/Intersect.Client.Framework/Gwen/Control/LabeledRadioButton.cs index 9d513cc193..0ac2fceb3e 100644 --- a/Intersect.Client.Framework/Gwen/Control/LabeledRadioButton.cs +++ b/Intersect.Client.Framework/Gwen/Control/LabeledRadioButton.cs @@ -39,7 +39,7 @@ public LabeledRadioButton(Base parent) : base(parent) IsTabable = false, KeyboardInputEnabled = false, }; - mLabel.Clicked += delegate (Base control, ClickedEventArgs args) { mRadioButton.Press(control); }; + mLabel.Clicked += delegate (Base control, MouseButtonState args) { mRadioButton.Press(control); }; } /// diff --git a/Intersect.Client.Framework/Gwen/Control/LabeledSlider.cs b/Intersect.Client.Framework/Gwen/Control/LabeledSlider.cs new file mode 100644 index 0000000000..7283ff8f00 --- /dev/null +++ b/Intersect.Client.Framework/Gwen/Control/LabeledSlider.cs @@ -0,0 +1,344 @@ +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; + +namespace Intersect.Client.Framework.Gwen.Control; + +public partial class LabeledSlider : Base, IAutoSizeToContents +{ + private readonly Label _label; + private readonly Slider _slider; + private readonly TextBoxNumeric _sliderValue; + private double _scale = 1.0; + private int _rounding = -1; + private bool _autoSizeToContents; + private bool _recomputeValueMinimumSize = true; + + /// + /// 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, + }; + + _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; + var clampedValue = Math.Clamp(newValue, Min, Max); + if (!clampedValue.Equals(newValue)) + { + _sliderValue.Value = clampedValue; + } + _slider.Value = clampedValue; + ValueChanged?.Invoke( + sender, + new ValueChangedEventArgs + { + Value = newValue, + } + ); + }; + + _autoSizeToContents = true; + 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; + _sliderValue.Minimum = value; + } + } + + /// + /// Maximum value. + /// + public double Max + { + get => _slider.Max; + set + { + _slider.Max = value; + _sliderValue.Maximum = value; + _sliderValue.MinimumSize = ComputeMinimumSizeForSliderValue(value); + } + } + + private Point ComputeMinimumSizeForSliderValue(double? value = null) + { + var valueString = (value ?? Max).ToString(CultureInfo.CurrentUICulture); + var valueFormatString = ValueFormatString; + if (!string.IsNullOrWhiteSpace(valueFormatString)) + { + valueString = string.Format(valueFormatString, valueString); + } + + return Skin.Renderer.MeasureText(_sliderValue.Font, valueString) + + _sliderValue.Padding + + _sliderValue.TextPadding; + } + + 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 Point LabelMinimumSize + { + get => _label.MinimumSize; + set => _label.MinimumSize = value; + } + + public void SetDraggerImage(GameTexture? texture, ComponentState state) + { + _slider.SetDraggerImage(texture, state); + } + + public GameTexture? GetDraggerImage(ComponentState state) + { + return _slider.GetDraggerImage(state); + } + + public void SetSound(string? sound, Dragger.ControlSoundState state) + { + _slider.SetSound(sound, state); + } + + public string? ValueFormatString + { + get => _sliderValue.FormatString; + set => _sliderValue.FormatString = value; + } + + /// + /// Invoked when the value has been changed. + /// + public event GwenEventHandler>? ValueChanged; + + protected override void Layout(Skin.Base skin) + { + if (_recomputeValueMinimumSize) + { + _sliderValue.MinimumSize = ComputeMinimumSizeForSliderValue(Max); + } + + if (_autoSizeToContents) + { + SizeToChildren(); + } + + var orientation = _slider.Orientation; + switch (orientation) + { + case Orientation.LeftToRight: + _label.Dock = Pos.Left; + _label.Alignment = [Alignments.CenterV]; + _slider.Dock = Pos.Left; + _slider.Alignment = [Alignments.CenterV]; + _slider.Margin = new Margin(4, 0, 0, 0); + _sliderValue.Dock = Pos.Left; + _sliderValue.Alignment = [Alignments.CenterV]; + _sliderValue.Margin = new Margin(4, 0, 0, 0); + break; + case Orientation.RightToLeft: + _label.Dock = Pos.Right | Pos.CenterV; + _slider.Dock = Pos.Right | Pos.CenterV; + _slider.Margin = new Margin(0, 0, 4, 0); + _sliderValue.Dock = Pos.Right; + _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 void SetToolTipText(string? text) + { + _label.SetToolTipText(text); + _slider.SetToolTipText(text); + _sliderValue.SetToolTipText(text); + } + + protected override void OnBoundsChanged(Rectangle oldBounds, Rectangle newBounds) + { + base.OnBoundsChanged(oldBounds, newBounds); + } + + public void SetRange(double min, double max) => (Min, Max) = (min, max); + + public bool AutoSizeToContents + { + get => _autoSizeToContents; + set => SetAndDoIfChanged(ref _autoSizeToContents, value, Invalidate); + } +} \ 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..d61259b06c 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; @@ -11,64 +12,109 @@ namespace Intersect.Client.Framework.Gwen.Control.Layout; /// /// Base class for multi-column tables. /// -public partial class Table : Base, IColorableText +public partial class Table : Base, ISmartAutoSizeToContents, 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; + private bool _autoSizeToContentWidth; + private bool _autoSizeToContentHeight; + private bool _autoSizeToContentWidthOnChildResize; + private bool _autoSizeToContentHeightOnChildResize; /// /// 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 Point CellSpacing + { + get => _cellSpacing; + set + { + if (value == _cellSpacing) + { + return; + } + + _cellSpacing = value; + var rows = Children.OfType().Skip(1).ToArray(); + foreach (var row in rows) + { + row.Margin = new Margin(0, _cellSpacing.Y, 0, 0); + row.CellSpacing = value with + { + Y = 0, + }; + } + } } - public ITableDataProvider DataProvider + public ITableDataProvider? DataProvider { - get => mDataProvider; - set => SetAndDoIfChanged(ref mDataProvider, value, (oldValue, newValue) => + get => _dataProvider; + set => SetAndDoIfChanged(ref _dataProvider, value, (oldValue, newValue) => { if (oldValue != default) { @@ -128,16 +174,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) { @@ -150,44 +208,34 @@ protected virtual void OnDataChanged(object sender, TableDataChangedEventArgs ar row.SetCellText(args.Column, args.NewValue?.ToString()); } - public override JObject GetJson(bool isRoot = default) + public override JObject? GetJson(bool isRoot = false, bool onlySerializeIfNotEmpty = false) { - 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(nameof(Font), Font?.ToString()); - obj.Add(nameof(TextColor), TextColor.ToString()); - obj.Add(nameof(TextColorOverride), TextColorOverride.ToString()); + var serializedProperties = base.GetJson(isRoot, onlySerializeIfNotEmpty); + if (serializedProperties is null) + { + return null; + } - return base.FixJson(obj); + serializedProperties.Add(nameof(SizeToContents), _sizeToContents); + serializedProperties.Add(nameof(DefaultRowHeight), _defaultRowHeight); + serializedProperties.Add(nameof(Font), Font?.ToString()); + serializedProperties.Add(nameof(TextColor), TextColor?.ToString()); + serializedProperties.Add(nameof(TextColorOverride), TextColorOverride?.ToString()); + + return base.FixJson(serializedProperties); } 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 +267,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 +293,51 @@ 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). + /// Adds a new empty row. /// - /// Column index. - /// Column width. - public int GetColumnWidth(int column) - { - return mColumnWidths[column]; - } + /// Newly created row. + public TableRow AddRow() => AddRow(ColumnCount); /// /// Adds a new empty row. /// /// Newly created row. - public TableRow AddRow() => AddRow(ColumnCount); + public TableRow AddNamedRow(string? name) => AddRow(ColumnCount, name); + + 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); + } - public TableRow AddRow(int columnCount) + return row; + } + + public TableRow AddRow(int columnCount, string? name = null) { - var row = new TableRow(this) + var row = new TableRow(this, name: name) { + 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,30 +358,31 @@ 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); } /// /// Adds a new row with specified text in first column. /// /// Text to add. + /// /// New row. - public TableRow AddRow(string text) + public TableRow AddRow(string text, string? name = null) { - var row = AddRow(); + var row = AddNamedRow(name: name); row.SetCellText(0, text); return row; } - public TableRow AddRow(string text, int columnCount) + public TableRow AddRow(string text, int columnCount, string? name = null) { - var row = AddRow(columnCount); + var row = AddRow(columnCount, name: name); row.SetCellText(0, text); return row; @@ -373,23 +437,35 @@ protected override void Layout(Skin.Base skin) { base.Layout(skin); - if (mSizeToContents) + if (_sizeToContents) { DoSizeToContents(); - mSizeToContents = false; + _sizeToContents = false; } else { - ComputeColumnWidths(); + var autoSizeToContentWidth = _autoSizeToContentWidth; + var autoSizeToContentHeight = _autoSizeToContentHeight; + if (autoSizeToContentWidth || autoSizeToContentHeight) + { + DoSizeToContents(autoSizeToContentWidth, autoSizeToContentHeight); + } + else + { + ComputeColumnWidths(); + } } - var even = false; - foreach (TableRow row in Children) + // ReSharper disable once InvertIf + if (ClientConfiguration.Instance.EnableZebraStripedRows) { - row.EvenRow = even; - even = ClientConfiguration.Instance.EnableZebraStripedRows && !even; - - row.SetColumnWidths(mColumnWidths); + var even = false; + var rows = Children.OfType().ToArray(); + foreach (TableRow row in rows) + { + row.EvenRow = even; + even = !even; + } } } @@ -398,57 +474,164 @@ 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 = null) { - mMaxWidth = maxWidth; - mSizeToContents = true; + MaximumSize = MaximumSize with { X = maxWidth ?? MaximumSize.X }; + _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; + } + + requestedWidths[columnIndex] += extraSpacePerColumn; + availableWidth -= extraSpacePerColumn; } + } - height += row.Height; + 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; } - // sum all column widths - width += mColumnWidths.Take(ColumnCount).Sum(); + actualHeight += Math.Max(0, rows.Length - 1) * CellSpacing.Y; - return (width, height); + return new Point(actualWidth, actualHeight); } - public void DoSizeToContents() + public void DoSizeToContents(bool width = false, bool height = true) { - var (width, height) = ComputeColumnWidths(); + var rows = Children.OfType().ToArray(); + if (_fitRowHeightToContents) + { + foreach (var row in rows) + { + row.SizeToChildren(width: width, height: height); + } + } - SetSize(width, height); + var size = ComputeColumnWidths(rows); + + SetSize(size.X, size.Y); InvalidateParent(); } @@ -469,4 +652,52 @@ public void Invalidate(bool invalidateChildren, bool invalidateRecursive = true) } } + protected override void OnBoundsChanged(Rectangle oldBounds, Rectangle newBounds) + { + if (Bounds.Height == 800) + { + Bounds.ToString(); + } + + base.OnBoundsChanged(oldBounds, newBounds); + } + + protected override void OnChildBoundsChanged(Base child, Rectangle oldChildBounds, Rectangle newChildBounds) + { + base.OnChildBoundsChanged(child, oldChildBounds, newChildBounds); + + if (_autoSizeToContentWidthOnChildResize && oldChildBounds.Width != newChildBounds.Width) + { + Invalidate(); + } + + if (_autoSizeToContentHeightOnChildResize && oldChildBounds.Height != newChildBounds.Height) + { + Invalidate(); + } + } + + public bool AutoSizeToContentWidth + { + get => _autoSizeToContentWidth; + set => _autoSizeToContentWidth = value; + } + + public bool AutoSizeToContentHeight + { + get => _autoSizeToContentHeight; + set => _autoSizeToContentHeight = value; + } + + public bool AutoSizeToContentWidthOnChildResize + { + get => _autoSizeToContentWidthOnChildResize; + set => _autoSizeToContentWidthOnChildResize = value; + } + + public bool AutoSizeToContentHeightOnChildResize + { + get => _autoSizeToContentHeightOnChildResize; + set => _autoSizeToContentHeightOnChildResize = value; + } } diff --git a/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs b/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs index 2fec7f62cb..6cb4f31ba3 100644 --- a/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs +++ b/Intersect.Client.Framework/Gwen/Control/Layout/TableRow.cs @@ -1,6 +1,8 @@ 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; +using Intersect.Client.Framework.Input; namespace Intersect.Client.Framework.Gwen.Control.Layout; @@ -11,12 +13,12 @@ namespace Intersect.Client.Framework.Gwen.Control.Layout; public partial class TableRow : Base, IColorableText { private readonly List mDisposalActions; - - private readonly List /// Parent control. - /// the number of columns this row has - public TableRow(Base parent, int columns) : base(parent) + /// the number of columns this row has + /// + public TableRow(Base parent, int columnCount, string? name = null) : base(parent: parent, name: name) { mDisposalActions = []; - mColumns = new List