From 8b528373a7715f0d2edd2aa651b344e3cb7daf7d Mon Sep 17 00:00:00 2001 From: Rixium Date: Wed, 6 May 2020 19:39:29 +0100 Subject: [PATCH 1/7] Added system interface --- GeonBit.UI/GeonBit.UI.csproj | 1 + GeonBit.UI/Source/Systems/ISystem.cs | 15 +++++++++++++++ 2 files changed, 16 insertions(+) create mode 100644 GeonBit.UI/Source/Systems/ISystem.cs diff --git a/GeonBit.UI/GeonBit.UI.csproj b/GeonBit.UI/GeonBit.UI.csproj index 16ff9d8..0d6cd9f 100644 --- a/GeonBit.UI/GeonBit.UI.csproj +++ b/GeonBit.UI/GeonBit.UI.csproj @@ -86,6 +86,7 @@ + diff --git a/GeonBit.UI/Source/Systems/ISystem.cs b/GeonBit.UI/Source/Systems/ISystem.cs new file mode 100644 index 0000000..552321c --- /dev/null +++ b/GeonBit.UI/Source/Systems/ISystem.cs @@ -0,0 +1,15 @@ +namespace GeonBit.UI.Systems +{ + /// + /// System interface for performing some logic. + /// + public interface ISystem + { + + /// + /// Update logic for every frame. + /// + void Update(); + } + +} \ No newline at end of file From 92b275b75de7d125d32fd277f5531afabcfd462a Mon Sep 17 00:00:00 2001 From: Rixium Date: Wed, 6 May 2020 19:42:29 +0100 Subject: [PATCH 2/7] Add systems to the user interface --- GeonBit.UI/Source/UserInterface.cs | 42 ++++++++++++++++++++++++++++++ 1 file changed, 42 insertions(+) diff --git a/GeonBit.UI/Source/UserInterface.cs b/GeonBit.UI/Source/UserInterface.cs index c99ab81..deee7d8 100644 --- a/GeonBit.UI/Source/UserInterface.cs +++ b/GeonBit.UI/Source/UserInterface.cs @@ -8,12 +8,15 @@ // Since: 2016. //----------------------------------------------------------------------------- #endregion + +using System.Collections.Generic; using Microsoft.Xna.Framework; using Microsoft.Xna.Framework.Graphics; using GeonBit.UI.Entities; using Microsoft.Xna.Framework.Content; using System.Runtime.Serialization; using System.Xml.Serialization; +using GeonBit.UI.Systems; namespace GeonBit.UI @@ -183,6 +186,11 @@ public RenderTarget2D RenderTarget /// public RootPanel Root { get; private set; } + /// + /// A list of systems attached to the user interface. + /// + public IList Systems { get; private set; } + /// /// Blend state to use when rendering UI. /// @@ -436,6 +444,8 @@ public UserInterface() // create the root panel Root = new RootPanel(); + + Systems = new List(); // set default cursor SetCursor(CursorType.Default); @@ -506,12 +516,31 @@ public void RemoveEntity(Entity entity) Root.RemoveChild(entity); } + /// + /// Adds a system to the user interface. + /// + /// The system to add. + public void AddSystem(ISystem system) + { + Systems.Add(system); + } + + /// + /// Removes a system from the user interface. + /// + /// The system to remove from the user interface. + public void RemoveSystem(ISystem system) + { + Systems.Remove(system); + } + /// /// Remove all entities from screen. /// public void Clear() { Root.ClearChildren(); + Systems.Clear(); } /// @@ -537,6 +566,8 @@ public void Update(GameTime gameTime) bool wasEventHandled = false; Root.Update(ref target, ref _dragTarget, ref wasEventHandled, Point.Zero); + UpdateSystems(); + // set active entity if (MouseInputProvider.MouseButtonDown(MouseButton.Left)) { @@ -553,6 +584,17 @@ public void Update(GameTime gameTime) TargetEntity = target; } + /// + /// Updates each system attached to the user interface. + /// + private void UpdateSystems() + { + foreach (var system in Systems) + { + system.Update(); + } + } + /// /// Update tooltip text related stuff. /// From 89307b19e2b92f20a7f682c916f7ae1f4b9d00f4 Mon Sep 17 00:00:00 2001 From: Rixium Date: Wed, 6 May 2020 19:43:03 +0100 Subject: [PATCH 3/7] Add the ignore key validator --- GeonBit.UI/GeonBit.UI.csproj | 1 + .../Source/Validators/IgnoreKeyValidator.cs | 33 +++++++++++++++++++ 2 files changed, 34 insertions(+) create mode 100644 GeonBit.UI/Source/Validators/IgnoreKeyValidator.cs diff --git a/GeonBit.UI/GeonBit.UI.csproj b/GeonBit.UI/GeonBit.UI.csproj index 0d6cd9f..f889ef7 100644 --- a/GeonBit.UI/GeonBit.UI.csproj +++ b/GeonBit.UI/GeonBit.UI.csproj @@ -93,6 +93,7 @@ + diff --git a/GeonBit.UI/Source/Validators/IgnoreKeyValidator.cs b/GeonBit.UI/Source/Validators/IgnoreKeyValidator.cs new file mode 100644 index 0000000..94e37ad --- /dev/null +++ b/GeonBit.UI/Source/Validators/IgnoreKeyValidator.cs @@ -0,0 +1,33 @@ +using GeonBit.UI.Entities.TextValidators; +using Microsoft.Xna.Framework.Input; + +namespace GeonBit.UI.Validators +{ + /// + /// Ignores a given key + /// + public class IgnoreKeyValidator : ITextValidator + { + private readonly Keys _key; + + /// + /// Ignores a given key + /// + /// The key to ignore + public IgnoreKeyValidator(Keys key) + { + _key = key; + } + + /// + /// Checks if the given key has been pressed, and if so, resets the text to its original text. + /// + public override bool ValidateText(ref string text, string oldText) + { + if (Keyboard.GetState().IsKeyDown(_key)) + text = oldText; + + return base.ValidateText(ref text, oldText); + } + } +} \ No newline at end of file From 20871c2088dfcfe9d648818d3304317ab2b063b9 Mon Sep 17 00:00:00 2001 From: Rixium Date: Wed, 6 May 2020 19:43:51 +0100 Subject: [PATCH 4/7] Add the tab list system --- GeonBit.UI/GeonBit.UI.csproj | 1 + 1 file changed, 1 insertion(+) diff --git a/GeonBit.UI/GeonBit.UI.csproj b/GeonBit.UI/GeonBit.UI.csproj index f889ef7..6bcbaa1 100644 --- a/GeonBit.UI/GeonBit.UI.csproj +++ b/GeonBit.UI/GeonBit.UI.csproj @@ -87,6 +87,7 @@ + From b0d1c2c543a5e42b5433c99fb8a6ad3a6ff2ab4f Mon Sep 17 00:00:00 2001 From: Rixium Date: Wed, 6 May 2020 19:53:56 +0100 Subject: [PATCH 5/7] Revert "Add the tab list system" This reverts commit 20871c20 --- GeonBit.UI/GeonBit.UI.csproj | 1 - 1 file changed, 1 deletion(-) diff --git a/GeonBit.UI/GeonBit.UI.csproj b/GeonBit.UI/GeonBit.UI.csproj index 6bcbaa1..f889ef7 100644 --- a/GeonBit.UI/GeonBit.UI.csproj +++ b/GeonBit.UI/GeonBit.UI.csproj @@ -87,7 +87,6 @@ - From cb675bab2636d577c0d945d8fef2deeea2cb95d7 Mon Sep 17 00:00:00 2001 From: Rixium Date: Wed, 6 May 2020 19:54:51 +0100 Subject: [PATCH 6/7] Add tab list system --- GeonBit.UI/GeonBit.UI.csproj | 1 + GeonBit.UI/Source/Systems/TabList.cs | 190 +++++++++++++++++++++++++++ 2 files changed, 191 insertions(+) create mode 100644 GeonBit.UI/Source/Systems/TabList.cs diff --git a/GeonBit.UI/GeonBit.UI.csproj b/GeonBit.UI/GeonBit.UI.csproj index f889ef7..6bcbaa1 100644 --- a/GeonBit.UI/GeonBit.UI.csproj +++ b/GeonBit.UI/GeonBit.UI.csproj @@ -87,6 +87,7 @@ + diff --git a/GeonBit.UI/Source/Systems/TabList.cs b/GeonBit.UI/Source/Systems/TabList.cs new file mode 100644 index 0000000..435963d --- /dev/null +++ b/GeonBit.UI/Source/Systems/TabList.cs @@ -0,0 +1,190 @@ +using System.Collections.Generic; +using System.Linq; +using GeonBit.UI.Entities; +using GeonBit.UI.Validators; +using Microsoft.Xna.Framework; +using Microsoft.Xna.Framework.Input; + +namespace GeonBit.UI.Systems +{ + /// + /// Allows you to add an collection of entities to this list, and then have the ability to tab through them. + /// + public class TabList : ISystem + { + + /// + /// Wraps an entity for use with tab list, to keep track of an entities default properties. + /// + public class TabEntity + { + /// + /// The wrapped entity + /// + public Entity Entity { get; private set; } + /// + /// The entities default fill + /// + public Color Fill { get; private set; } + + /// + /// Wraps an entity with its default properties. + /// + /// The entity to wrap + public TabEntity(Entity entity) + { + Entity = entity; + Fill = entity.FillColor; + } + } + + private KeyboardState _lastKeyState; + private KeyboardState _keyState; + + private TabEntity[] _entities; + private readonly Keys _cycleKey; + private readonly Keys _selectKey; + private readonly bool _wraparound; + private readonly Color _cycleFill; + private readonly Color _selectFill; + private int _currentSelection = -1; + + + /// + /// Creates a tab list for a given set of entities. + /// + /// The entities to store in the tab list + /// The fill color of the entity when cycled to + /// The fill color of the entity when selected + /// The key that must be pressed to focus next entity + /// The key that must be pressed to select entity + /// Whether or not tab will reset to zero at end of the list + public TabList(IEnumerable entities, Color cycleFill = default, Color selectFill = default, Keys cycleKey = Keys.Tab, Keys selectKey = Keys.Enter, bool wraparound = true) + { + _cycleKey = cycleKey; + _selectKey = selectKey; + _wraparound = wraparound; + _cycleFill = cycleFill; + _selectFill = selectFill; + + SetupEntities(entities); + } + + /// + /// Performs cycle and select logic based on keyboard input. + /// + public void Update() + { + if (UserInterface.Active == null) return; + + _keyState = Keyboard.GetState(); + + if (KeyPressed(_cycleKey)) + { + DeselectLastCycled(); + _currentSelection++; + ConstrainSelection(); + SetCurrentCycled(); + } else if (KeyPressed(_selectKey)) + { + SelectOrDeselectCurrent(); + } + + _lastKeyState = _keyState; + } + + private void SetupEntities(IEnumerable entities) + { + var enumerable = entities as Entity[] ?? entities.ToArray(); + AddIgnoreKeyValidator(enumerable); + _entities = enumerable.Select(entity => new TabEntity(entity)).ToArray(); + } + + private void AddIgnoreKeyValidator(IEnumerable entities) + { + foreach (var tabEntity in entities) + { + if (tabEntity.GetType() != typeof(TextInput)) continue; + + var textInput = tabEntity as TextInput; + textInput?.Validators.Add(new IgnoreKeyValidator(_cycleKey)); + } + } + + /// + /// Updates the set of entities depending on their tab index. + /// + + + /// + /// Resets the styling of a given entity to its original properties, and removes the focus. + /// + private void DeselectLastCycled() + { + if (!IsValidSelection()) return; + + _entities[_currentSelection].Entity.IsFocused = false; + _entities[_currentSelection].Entity.FillColor = _entities[_currentSelection].Fill; + } + + /// + /// Keeps the current selection in the bounds of the list, or wraps around to zero. + /// + private void ConstrainSelection() + { + if (_wraparound && _currentSelection >= _entities.Length) + _currentSelection = 0; + else + MathHelper.Clamp(_currentSelection, 0, _entities.Length); + } + + /// + /// Styles the current selection. + /// + private void SetCurrentCycled() + { + _entities[_currentSelection].Entity.FillColor = _cycleFill; + } + + /// + /// Selects or deselects depending on whether or not an entity is already selected. + /// If selected, set the styling of the entity and fires any eligible events. + /// + private void SelectOrDeselectCurrent() + { + if (!IsValidSelection()) return; + if (CycledAlreadySelected()) + { + DeselectLastCycled(); + SetCurrentCycled(); + return; + } + + UserInterface.Active.ActiveEntity.IsFocused = false; + _entities[_currentSelection].Entity.FillColor = _selectFill; + UserInterface.Active.ActiveEntity = _entities[_currentSelection].Entity; + UserInterface.Active.ActiveEntity.IsFocused = true; + UserInterface.Active.ActiveEntity.OnClick?.Invoke(UserInterface.Active.ActiveEntity); + } + + /// + /// Checks if the current cycled index already has focus. + /// + /// + private bool CycledAlreadySelected() => + _entities[_currentSelection].Entity == UserInterface.Active.ActiveEntity && + _entities[_currentSelection].Entity.IsFocused; + + /// + /// Checks if a key has been pressed this update. + /// + private bool KeyPressed(Keys key) => _keyState.IsKeyDown(key) && _lastKeyState.IsKeyUp(key); + + /// + /// Checks if the current selection is within the bounds of the list. + /// + private bool IsValidSelection() => _currentSelection >= 0 && _currentSelection < _entities.Length; + + } + +} \ No newline at end of file From 398e7c0383d84d3fa6a54a0e1b4b05153c499fdf Mon Sep 17 00:00:00 2001 From: Rixium Date: Wed, 6 May 2020 20:21:35 +0100 Subject: [PATCH 7/7] Remove unnecessary select fill, and automatically focus elements on tab --- GeonBit.UI/Source/Systems/TabList.cs | 62 ++++++++++------------------ 1 file changed, 22 insertions(+), 40 deletions(-) diff --git a/GeonBit.UI/Source/Systems/TabList.cs b/GeonBit.UI/Source/Systems/TabList.cs index 435963d..7663210 100644 --- a/GeonBit.UI/Source/Systems/TabList.cs +++ b/GeonBit.UI/Source/Systems/TabList.cs @@ -46,7 +46,6 @@ public TabEntity(Entity entity) private readonly Keys _selectKey; private readonly bool _wraparound; private readonly Color _cycleFill; - private readonly Color _selectFill; private int _currentSelection = -1; @@ -55,17 +54,15 @@ public TabEntity(Entity entity) /// /// The entities to store in the tab list /// The fill color of the entity when cycled to - /// The fill color of the entity when selected /// The key that must be pressed to focus next entity /// The key that must be pressed to select entity /// Whether or not tab will reset to zero at end of the list - public TabList(IEnumerable entities, Color cycleFill = default, Color selectFill = default, Keys cycleKey = Keys.Tab, Keys selectKey = Keys.Enter, bool wraparound = true) + public TabList(IEnumerable entities, Color cycleFill = default, Keys cycleKey = Keys.Tab, Keys selectKey = Keys.Enter, bool wraparound = true) { _cycleKey = cycleKey; _selectKey = selectKey; _wraparound = wraparound; _cycleFill = cycleFill; - _selectFill = selectFill; SetupEntities(entities); } @@ -81,18 +78,19 @@ public void Update() if (KeyPressed(_cycleKey)) { - DeselectLastCycled(); - _currentSelection++; - ConstrainSelection(); - SetCurrentCycled(); + CycleNext(); } else if (KeyPressed(_selectKey)) { - SelectOrDeselectCurrent(); + UseCurrent(); } _lastKeyState = _keyState; } + /// + /// Creates a new list of entities and wraps them in an tab entity object. + /// + /// The entities to wrap. private void SetupEntities(IEnumerable entities) { var enumerable = entities as Entity[] ?? entities.ToArray(); @@ -100,6 +98,10 @@ private void SetupEntities(IEnumerable entities) _entities = enumerable.Select(entity => new TabEntity(entity)).ToArray(); } + /// + /// Add the ignore key validator to each applicable entity. + /// + /// private void AddIgnoreKeyValidator(IEnumerable entities) { foreach (var tabEntity in entities) @@ -111,18 +113,13 @@ private void AddIgnoreKeyValidator(IEnumerable entities) } } - /// - /// Updates the set of entities depending on their tab index. - /// - - /// /// Resets the styling of a given entity to its original properties, and removes the focus. /// private void DeselectLastCycled() { if (!IsValidSelection()) return; - + _entities[_currentSelection].Entity.IsFocused = false; _entities[_currentSelection].Entity.FillColor = _entities[_currentSelection].Fill; } @@ -139,42 +136,27 @@ private void ConstrainSelection() } /// - /// Styles the current selection. + /// Cycles to the next selection, removes focus from the old, and styles the new. /// - private void SetCurrentCycled() + private void CycleNext() { + DeselectLastCycled(); + _currentSelection++; + ConstrainSelection(); + _entities[_currentSelection].Entity.FillColor = _cycleFill; + _entities[_currentSelection].Entity.IsFocused = true; } /// - /// Selects or deselects depending on whether or not an entity is already selected. - /// If selected, set the styling of the entity and fires any eligible events. + /// Activates any of the actions of a given entity. /// - private void SelectOrDeselectCurrent() + private void UseCurrent() { if (!IsValidSelection()) return; - if (CycledAlreadySelected()) - { - DeselectLastCycled(); - SetCurrentCycled(); - return; - } - - UserInterface.Active.ActiveEntity.IsFocused = false; - _entities[_currentSelection].Entity.FillColor = _selectFill; - UserInterface.Active.ActiveEntity = _entities[_currentSelection].Entity; - UserInterface.Active.ActiveEntity.IsFocused = true; - UserInterface.Active.ActiveEntity.OnClick?.Invoke(UserInterface.Active.ActiveEntity); + _entities[_currentSelection].Entity.OnClick?.Invoke(_entities[_currentSelection].Entity); } - /// - /// Checks if the current cycled index already has focus. - /// - /// - private bool CycledAlreadySelected() => - _entities[_currentSelection].Entity == UserInterface.Active.ActiveEntity && - _entities[_currentSelection].Entity.IsFocused; - /// /// Checks if a key has been pressed this update. ///