Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
3 changes: 3 additions & 0 deletions GeonBit.UI/GeonBit.UI.csproj
Original file line number Diff line number Diff line change
Expand Up @@ -86,12 +86,15 @@
<Compile Include="Source\Input\IKeyboardInput.cs" />
<Compile Include="Source\Input\IMouseInput.cs" />
<Compile Include="Source\Resources.cs" />
<Compile Include="Source\Systems\ISystem.cs" />
<Compile Include="Source\Systems\TabList.cs" />
<Compile Include="Source\UserInterface.cs" />
<Compile Include="Source\Utils\MessageBox.cs" />
<Compile Include="Source\Utils\Forms.cs" />
<Compile Include="Source\Utils\PanelsGrid.cs" />
<Compile Include="Source\Utils\SerializedDictionary.cs" />
<Compile Include="Source\Utils\SimpleFileMenu.cs" />
<Compile Include="Source\Validators\IgnoreKeyValidator.cs" />
</ItemGroup>
<ItemGroup>
<Reference Include="MonoGame.Framework">
Expand Down
15 changes: 15 additions & 0 deletions GeonBit.UI/Source/Systems/ISystem.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,15 @@
namespace GeonBit.UI.Systems
{
/// <summary>
/// System interface for performing some logic.
/// </summary>
public interface ISystem
{

/// <summary>
/// Update logic for every frame.
/// </summary>
void Update();
}

}
172 changes: 172 additions & 0 deletions GeonBit.UI/Source/Systems/TabList.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,172 @@
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
{
/// <summary>
/// Allows you to add an collection of entities to this list, and then have the ability to tab through them.
/// </summary>
public class TabList : ISystem
{

/// <summary>
/// Wraps an entity for use with tab list, to keep track of an entities default properties.
/// </summary>
public class TabEntity
{
/// <summary>
/// The wrapped entity
/// </summary>
public Entity Entity { get; private set; }
/// <summary>
/// The entities default fill
/// </summary>
public Color Fill { get; private set; }

/// <summary>
/// Wraps an entity with its default properties.
/// </summary>
/// <param name="entity">The entity to wrap</param>
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 int _currentSelection = -1;


/// <summary>
/// Creates a tab list for a given set of entities.
/// </summary>
/// <param name="entities">The entities to store in the tab list</param>
/// <param name="cycleFill">The fill color of the entity when cycled to</param>
/// <param name="cycleKey">The key that must be pressed to focus next entity</param>
/// <param name="selectKey">The key that must be pressed to select entity</param>
/// <param name="wraparound">Whether or not tab will reset to zero at end of the list</param>
public TabList(IEnumerable<Entity> entities, Color cycleFill = default, Keys cycleKey = Keys.Tab, Keys selectKey = Keys.Enter, bool wraparound = true)
{
_cycleKey = cycleKey;
_selectKey = selectKey;
_wraparound = wraparound;
_cycleFill = cycleFill;

SetupEntities(entities);
}

/// <summary>
/// Performs cycle and select logic based on keyboard input.
/// </summary>
public void Update()
{
if (UserInterface.Active == null) return;

_keyState = Keyboard.GetState();

if (KeyPressed(_cycleKey))
{
CycleNext();
} else if (KeyPressed(_selectKey))
{
UseCurrent();
}

_lastKeyState = _keyState;
}

/// <summary>
/// Creates a new list of entities and wraps them in an tab entity object.
/// </summary>
/// <param name="entities">The entities to wrap.</param>
private void SetupEntities(IEnumerable<Entity> entities)
{
var enumerable = entities as Entity[] ?? entities.ToArray();
AddIgnoreKeyValidator(enumerable);
_entities = enumerable.Select(entity => new TabEntity(entity)).ToArray();
}

/// <summary>
/// Add the ignore key validator to each applicable entity.
/// </summary>
/// <param name="entities"></param>
private void AddIgnoreKeyValidator(IEnumerable<Entity> entities)
{
foreach (var tabEntity in entities)
{
if (tabEntity.GetType() != typeof(TextInput)) continue;

var textInput = tabEntity as TextInput;
textInput?.Validators.Add(new IgnoreKeyValidator(_cycleKey));
}
}

/// <summary>
/// Resets the styling of a given entity to its original properties, and removes the focus.
/// </summary>
private void DeselectLastCycled()
{
if (!IsValidSelection()) return;

_entities[_currentSelection].Entity.IsFocused = false;
_entities[_currentSelection].Entity.FillColor = _entities[_currentSelection].Fill;
}

/// <summary>
/// Keeps the current selection in the bounds of the list, or wraps around to zero.
/// </summary>
private void ConstrainSelection()
{
if (_wraparound && _currentSelection >= _entities.Length)
_currentSelection = 0;
else
MathHelper.Clamp(_currentSelection, 0, _entities.Length);
}

/// <summary>
/// Cycles to the next selection, removes focus from the old, and styles the new.
/// </summary>
private void CycleNext()
{
DeselectLastCycled();
_currentSelection++;
ConstrainSelection();

_entities[_currentSelection].Entity.FillColor = _cycleFill;
_entities[_currentSelection].Entity.IsFocused = true;
}

/// <summary>
/// Activates any of the actions of a given entity.
/// </summary>
private void UseCurrent()
{
if (!IsValidSelection()) return;
_entities[_currentSelection].Entity.OnClick?.Invoke(_entities[_currentSelection].Entity);
}

/// <summary>
/// Checks if a key has been pressed this update.
/// </summary>
private bool KeyPressed(Keys key) => _keyState.IsKeyDown(key) && _lastKeyState.IsKeyUp(key);

/// <summary>
/// Checks if the current selection is within the bounds of the list.
/// </summary>
private bool IsValidSelection() => _currentSelection >= 0 && _currentSelection < _entities.Length;

}

}
42 changes: 42 additions & 0 deletions GeonBit.UI/Source/UserInterface.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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
Expand Down Expand Up @@ -183,6 +186,11 @@ public RenderTarget2D RenderTarget
/// </summary>
public RootPanel Root { get; private set; }

/// <summary>
/// A list of systems attached to the user interface.
/// </summary>
public IList<ISystem> Systems { get; private set; }

/// <summary>
/// Blend state to use when rendering UI.
/// </summary>
Expand Down Expand Up @@ -436,6 +444,8 @@ public UserInterface()

// create the root panel
Root = new RootPanel();

Systems = new List<ISystem>();

// set default cursor
SetCursor(CursorType.Default);
Expand Down Expand Up @@ -506,12 +516,31 @@ public void RemoveEntity(Entity entity)
Root.RemoveChild(entity);
}

/// <summary>
/// Adds a system to the user interface.
/// </summary>
/// <param name="system">The system to add.</param>
public void AddSystem(ISystem system)
{
Systems.Add(system);
}

/// <summary>
/// Removes a system from the user interface.
/// </summary>
/// <param name="system">The system to remove from the user interface.</param>
public void RemoveSystem(ISystem system)
{
Systems.Remove(system);
}

/// <summary>
/// Remove all entities from screen.
/// </summary>
public void Clear()
{
Root.ClearChildren();
Systems.Clear();
}

/// <summary>
Expand All @@ -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))
{
Expand All @@ -553,6 +584,17 @@ public void Update(GameTime gameTime)
TargetEntity = target;
}

/// <summary>
/// Updates each system attached to the user interface.
/// </summary>
private void UpdateSystems()
{
foreach (var system in Systems)
{
system.Update();
}
}

/// <summary>
/// Update tooltip text related stuff.
/// </summary>
Expand Down
33 changes: 33 additions & 0 deletions GeonBit.UI/Source/Validators/IgnoreKeyValidator.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,33 @@
using GeonBit.UI.Entities.TextValidators;
using Microsoft.Xna.Framework.Input;

namespace GeonBit.UI.Validators
{
/// <summary>
/// Ignores a given key
/// </summary>
public class IgnoreKeyValidator : ITextValidator
{
private readonly Keys _key;

/// <summary>
/// Ignores a given key
/// </summary>
/// <param name="key">The key to ignore</param>
public IgnoreKeyValidator(Keys key)
{
_key = key;
}

/// <summary>
/// Checks if the given key has been pressed, and if so, resets the text to its original text.
/// </summary>
public override bool ValidateText(ref string text, string oldText)
{
if (Keyboard.GetState().IsKeyDown(_key))
text = oldText;

return base.ValidateText(ref text, oldText);
}
}
}