Remove NuclexGui

This commit is contained in:
Lucas Girouard-Stranks
2021-08-06 00:42:52 -04:00
parent 8ee7ac9ece
commit 42aac653fa
63 changed files with 0 additions and 9106 deletions
-10
View File
@@ -19,8 +19,6 @@ Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.Graphics"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.Tiled", "src\cs\MonoGame.Extended.Tiled\MonoGame.Extended.Tiled.csproj", "{07B2ADE2-73E3-41C4-AEA1-D5566A5AB902}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.NuclexGui", "src\cs\MonoGame.Extended.NuclexGui\MonoGame.Extended.NuclexGui.csproj", "{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.Particles", "src\cs\MonoGame.Extended.Particles\MonoGame.Extended.Particles.csproj", "{6C8B9E29-D09B-4901-80FD-45AAA35882C6}"
EndProject
Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "MonoGame.Extended.Entities", "src\cs\MonoGame.Extended.Entities\MonoGame.Extended.Entities.csproj", "{35FD1F05-AF04-469A-B37A-F9B36C34401C}"
@@ -93,14 +91,6 @@ Global
{07B2ADE2-73E3-41C4-AEA1-D5566A5AB902}.Release|Any CPU.Build.0 = Release|Any CPU
{07B2ADE2-73E3-41C4-AEA1-D5566A5AB902}.Release|x86.ActiveCfg = Release|Any CPU
{07B2ADE2-73E3-41C4-AEA1-D5566A5AB902}.Release|x86.Build.0 = Release|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Debug|Any CPU.Build.0 = Debug|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Debug|x86.ActiveCfg = Debug|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Debug|x86.Build.0 = Debug|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Release|Any CPU.ActiveCfg = Release|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Release|Any CPU.Build.0 = Release|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Release|x86.ActiveCfg = Release|Any CPU
{D8BC4F21-E71D-46CE-B6D3-259F1E0DFABA}.Release|x86.Build.0 = Release|Any CPU
{6C8B9E29-D09B-4901-80FD-45AAA35882C6}.Debug|Any CPU.ActiveCfg = Debug|Any CPU
{6C8B9E29-D09B-4901-80FD-45AAA35882C6}.Debug|Any CPU.Build.0 = Debug|Any CPU
{6C8B9E29-D09B-4901-80FD-45AAA35882C6}.Debug|x86.ActiveCfg = Debug|Any CPU
@@ -1,30 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
public class GuiButtonControl : GuiPressableControl
{
/// <summary>Text that will be shown on the button</summary>
public string Text;
public Texture2D Texture;
public Rectangle SourceRectangle;
/// <summary>Will be triggered when the button is pressed</summary>
public event EventHandler Pressed;
/// <summary>Called when the button is pressed</summary>
protected override void OnPressed()
{
if (Pressed != null)
Pressed(this, EventArgs.Empty);
}
public GuiButtonControl() : base()
{
SourceRectangle = new Rectangle();
}
}
}
@@ -1,65 +0,0 @@
using System;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>Control displaying an exclusive choice the user can select</summary>
/// <remarks>
/// The choice control is equivalent to a radio button - if more than one
/// choice control is on a dialog, only one can be selected at a time.
/// To have several choice groups on a dialog, use panels to group them.
/// </remarks>
public class GuiChoiceControl : GuiPressableControl
{
/// <summary>Whether the choice is currently selected</summary>
public bool Selected;
/// <summary>Text that will be shown on the button</summary>
public string Text;
/// <summary>Determines where text or image will be shown relative to control</summary>
public GuiPressableDescriptionPosition DescriptionPosition;
/// <summary>Will be triggered when the choice is changed</summary>
public event EventHandler Changed;
/// <summary>Called when the button is pressed</summary>
protected override void OnPressed()
{
if (!Selected)
{
Selected = true;
// Unselect all sibling choice controls in the same container
UnselectSiblings();
OnChanged();
}
}
/// <summary>Triggers the changed event</summary>
protected virtual void OnChanged()
{
if (Changed != null)
Changed(this, EventArgs.Empty);
}
/// <summary>Disables all sibling choices on the same level</summary>
private void UnselectSiblings()
{
// Disable any other choices in the same frame
if (Parent != null)
{
var siblings = Parent.Children;
for (var index = 0; index < siblings.Count; ++index)
{
var control = siblings[index] as GuiChoiceControl;
if ((control != null) && (control != this) && control.Selected)
{
control.Selected = false;
control.OnChanged();
}
}
}
}
}
}
@@ -1,7 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>Control for the X button in the top right corner of a window</summary>
public class GuiCloseWindowButtonControl : GuiButtonControl
{
}
}
@@ -1,96 +0,0 @@
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
// Always move in absolute (offset) coordinates?
// Or always move in fractional coordinates?
//
// Preferring b), because I restores the user's display to the exact
// state it was if the resolution is changed, including that fact that
// lower resolutions would cause the windows to go off-screen.
//
// However, b) would mean a call to GetAbsolutePosition() each frame.
// Which isn't so bad, but... avoidable with a)
// Properties:
// Boundaries (for constraining a control to a region)
// Moveable (turn moveability on or off)
/// <summary>Control the user can drag around with the mouse</summary>
public abstract class GuiDraggableControl : GuiControl
{
/// <summary>Whether the control is currently being dragged</summary>
private bool _beingDragged;
/// <summary>Whether the control can be dragged</summary>
private bool _enableDragging;
/// <summary>X coordinate at which the control was picked up</summary>
private float _pickupX;
/// <summary>Y coordinate at which the control was picked up</summary>
private float _pickupY;
/// <summary>Initializes a new draggable control</summary>
public GuiDraggableControl()
{
EnableDragging = true;
}
/// <summary>Initializes a new draggable control</summary>
/// <param name="canGetFocus">Whether the control can obtain the input focus</param>
public GuiDraggableControl(bool canGetFocus) : base(canGetFocus)
{
EnableDragging = true;
}
/// <summary>Whether the control can be dragged with the mouse</summary>
protected bool EnableDragging
{
get { return _enableDragging; }
set
{
_enableDragging = value;
_beingDragged &= value;
}
}
/// <summary>Called when the mouse position is updated</summary>
/// <param name="x">X coordinate of the mouse cursor on the GUI</param>
/// <param name="y">Y coordinate of the mouse cursor on the GUI</param>
protected override void OnMouseMoved(float x, float y)
{
if (_beingDragged)
{
// Adjust the control's position within the container
var dx = x - _pickupX;
var dy = y - _pickupY;
Bounds.AbsoluteOffset(dx, dy);
}
else
{
// Remember the current mouse position so we know where the user picked
// up the control when a drag operation begins
_pickupX = x;
_pickupY = y;
}
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
protected override void OnMousePressed(MouseButton button)
{
if (button == MouseButton.Left)
_beingDragged = _enableDragging;
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
protected override void OnMouseReleased(MouseButton button)
{
if (button == MouseButton.Left)
_beingDragged = false;
}
}
}
@@ -1,40 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>Horizontal slider that can be moved using the mouse</summary>
public class GuiHorizontalSliderControl : GuiSliderControl
{
/// <summary>Obtains the region covered by the slider's thumb</summary>
/// <returns>The region covered by the slider's thumb</returns>
protected override RectangleF GetThumbRegion()
{
var bounds = GetAbsoluteBounds();
if (ThumbLocator != null)
return ThumbLocator.GetThumbPosition(bounds, ThumbPosition, ThumbSize);
var thumbWidth = bounds.Width*ThumbSize;
var thumbX = (bounds.Width - thumbWidth)*ThumbPosition;
return new RectangleF(thumbX, 0, thumbWidth, bounds.Height);
}
/// <summary>Moves the thumb to the specified location</summary>
/// <returns>Location the thumb will be moved to</returns>
protected override void MoveThumb(float x, float y)
{
var bounds = GetAbsoluteBounds();
var thumbWidth = bounds.Width*ThumbSize;
var maxX = bounds.Width - thumbWidth;
// Prevent divide-by-zero if the thumb fills out the whole rail
if (maxX > 0.0f)
ThumbPosition = MathHelper.Clamp(x/maxX, 0.0f, 1.0f);
else
ThumbPosition = 0.0f;
OnMoved();
}
}
}
@@ -1,294 +0,0 @@
using System;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>Control through which the user can enter text</summary>
/// <remarks>
/// <para>
/// Through this control, users can be asked to enter an arbitrary string
/// of characters, their name for example. Desktop users can enter text through
/// their normal keyboard where Windows' own key translation is used to
/// support regional settings and custom keyboard layouts.
/// </para>
/// <para>
/// XBox 360 users will open the virtual keyboard when the input box gets
/// the input focus and can add characters by selecting them from the virtual
/// keyboard's character matrix.
/// </para>
/// </remarks>
public class GuiInputControl : GuiControl, IWritable
{
/// <summary>Position of the cursor within the text</summary>
private int _caretPosition;
/// <summary>Tick count at the time the caret was last moved</summary>
private int _lastCaretMovementTicks;
/// <summary>X coordinate of the last known mouse position</summary>
private float _mouseX;
/// <summary>Y coordinate of the last known mouse position</summary>
private float _mouseY;
/// <summary>Array used to store characters before they are appended</summary>
private readonly char[ /*1*/] _singleCharArray;
/// <summary>Text the user has entered into the text input control</summary>
private readonly StringBuilder _text;
/// <summary>Whether user interaction with the control is allowed</summary>
public bool Enabled;
/// <summary>Description to be displayed in the on-screen keyboard</summary>
public string GuideDescription;
/// <summary>Title to be displayed in the on-screen keyboard</summary>
public string GuideTitle;
/// <summary>
/// Can be set by renderers to enable cursor positioning by the mouse
/// </summary>
public IOpeningLocator OpeningLocator;
/// <summary>Initializes a new text input control</summary>
public GuiInputControl()
{
_singleCharArray = new char[1];
_text = new StringBuilder(64);
Enabled = true;
GuideTitle = "Text Entry";
GuideDescription = "Please enter the text for this input field";
}
/// <summary>Position of the cursor within the text</summary>
public int CaretPosition
{
get { return _caretPosition; }
set
{
if ((value < 0) || (value > Text.Length))
throw new ArgumentException("Invalid caret position", "CaretPosition");
_caretPosition = value;
}
}
/// <summary>Whether the control currently has the input focus</summary>
public bool HasFocus => (Screen != null) &&
ReferenceEquals(Screen.FocusedControl, this);
/// <summary>Elapsed milliseconds since the user last moved the caret</summary>
/// <remarks>
/// This is an unusual property for an input box to have. It is retrieved by
/// the renderer and could be used for several purposes, such as lighting up
/// a control when text is entered to provide better visual tracking or
/// preventing the cursor from blinking whilst the user is typing.
/// </remarks>
public int MillisecondsSinceLastCaretMovement => Environment.TickCount - _lastCaretMovementTicks;
/// <summary>Text that is being displayed on the control</summary>
public string Text
{
get { return _text.ToString(); }
set
{
_text.Remove(0, _text.Length);
_text.Append(value);
// Cursor index is in openings between letters, including before first
// and after last letter, so text.Length is a valid position.
if (_caretPosition > _text.Length)
_caretPosition = _text.Length;
}
}
/// <summary>Called when the user has entered a character</summary>
/// <param name="character">Character that has been entered</param>
void IWritable.OnCharacterEntered(char character)
{
OnCharacterEntered(character);
}
/// <summary>Whether the control can currently obtain the input focus</summary>
bool IFocusable.CanGetFocus => Enabled;
/// <summary>Title to be displayed in the on-screen keyboard</summary>
string IWritable.GuideTitle => GuideTitle;
/// <summary>Description to be displayed in the on-screen keyboard</summary>
string IWritable.GuideDescription => GuideDescription;
/// <summary>Called when the user has entered a character</summary>
/// <param name="character">Character that has been entered</param>
protected virtual void OnCharacterEntered(char character)
{
// For some reason, Windows translates Backspace to a character :)
if (character != '\b')
{
UpdateLastCaretMovementTicks();
// There's no single-character overload on the XBox 360...
_singleCharArray[0] = character;
_text.Insert(_caretPosition, _singleCharArray);
++_caretPosition;
}
}
/// <summary>Called when a key on the keyboard has been pressed down</summary>
/// <param name="keyCode">Code of the key that was pressed</param>
/// <returns>
/// True if the key press was handles by the control, otherwise false.
/// </returns>
/// <remarks>
/// If the control indicates that it didn't handle the key press, it will not
/// receive the associated key release notification.
/// </remarks>
protected override bool OnKeyPressed(Keys keyCode)
{
// We only accept keys if we have the focus. If the notification is sent in search
// for a key handler without the input box being focused, we will not respond to
// the key press in order to not sabotage shortcut keys for other controls.
if (!HasFocus)
return false;
switch (keyCode)
{
// Backspace: erase the character left of the caret
case Keys.Back:
{
if (_caretPosition > 0)
{
UpdateLastCaretMovementTicks();
_text.Remove(_caretPosition - 1, 1);
--_caretPosition;
}
break;
}
// Delete: erase the character right of the caret
case Keys.Delete:
{
if (_caretPosition < _text.Length)
{
UpdateLastCaretMovementTicks();
_text.Remove(_caretPosition, 1);
}
break;
}
// Cursor left: move the caret to the left by one character
case Keys.Left:
{
if (_caretPosition > 0)
{
UpdateLastCaretMovementTicks();
--_caretPosition;
}
break;
}
// Cursor right: move the caret to the right by one character
case Keys.Right:
{
if (_caretPosition < _text.Length)
{
UpdateLastCaretMovementTicks();
++_caretPosition;
}
break;
}
// Home: place the caret before the first character
case Keys.Home:
{
UpdateLastCaretMovementTicks();
_caretPosition = 0;
break;
}
// Home: place the caret behind the last character
case Keys.End:
{
UpdateLastCaretMovementTicks();
_caretPosition = _text.Length;
break;
}
// Keys that can be used to navigate the dialog
case Keys.Tab:
case Keys.Up:
case Keys.Down:
case Keys.Enter:
{
return false;
}
}
return true;
}
/// <summary>Called when the mouse position is updated</summary>
/// <param name="x">X coordinate of the mouse cursor on the control</param>
/// <param name="y">Y coordinate of the mouse cursor on the control</param>
protected override void OnMouseMoved(float x, float y)
{
_mouseX = x;
_mouseY = y;
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
protected override void OnMousePressed(MouseButton button)
{
if (button == MouseButton.Left)
{
if (OpeningLocator != null)
{
var absoluteBounds = GetAbsoluteBounds();
var absolutePosition = new Vector2(absoluteBounds.X + _mouseX, absoluteBounds.Y + _mouseY);
_caretPosition = OpeningLocator.GetClosestOpening(absoluteBounds, Text, absolutePosition);
}
else
{
// Nope, our renderer is being secretive
MoveCaretToEnd();
}
}
}
/// <summary>Handles user text input by a physical keyboard</summary>
/// <param name="character">Character that has been entered</param>
internal void ProcessCharacter(char character)
{
// This notifications always concerns ourselves because it is only sent
// to the focused control
OnCharacterEntered(character);
}
/// <summary>Moves the caret to the end of the text</summary>
private void MoveCaretToEnd()
{
UpdateLastCaretMovementTicks();
_caretPosition = _text.Length;
}
/// <summary>Updates the tick count when the caret was last moved</summary>
/// <remarks>
/// Used to prevent the caret from blinking when
/// </remarks>
private void UpdateLastCaretMovementTicks()
{
_lastCaretMovementTicks = Environment.TickCount;
}
}
}
@@ -1,270 +0,0 @@
using System;
using System.Collections.Generic;
using Microsoft.Xna.Framework;
using MonoGame.Extended.Collections;
using MonoGame.Extended.Input;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>How the list lets the user select items</summary>
public enum ListSelectionMode
{
/// <summary>The user is not allowed to select an item</summary>
None,
/// <summary>The user can select only one item</summary>
Single,
/// <summary>The user can pick any number of items</summary>
Multi
}
/// <summary>List showing a sequence of items</summary>
public class GuiListControl : GuiControl, IFocusable
{
/// <summary>Items contained in the list</summary>
private readonly ObservableCollection<string> _items;
/// <summary>
/// Row locator through which the list can detect which row the mouse has
/// been pressed down on
/// </summary>
private IListRowLocator _listRowLocator;
/// <summary>Last known Y coordinate of the mouse</summary>
private float _mouseY;
/// <summary>Items currently selected in the list</summary>
private readonly ObservableCollection<int> _selectedItems;
/// <summary>How the list lets the user select from its items</summary>
private ListSelectionMode _selectionMode;
/// <summary>Slider the lists uses to scroll through its items</summary>
private readonly GuiVerticalSliderControl _slider;
public GuiListControl()
{
_items = new ObservableCollection<string>();
_items.Cleared += ItemsCleared;
_items.ItemAdded += ItemAdded;
_items.ItemRemoved += ItemRemoved;
_selectedItems = new ObservableCollection<int>();
_selectedItems.Cleared += SelectionCleared;
_selectedItems.ItemAdded += SelectionAdded;
_selectedItems.ItemRemoved += SelectionRemoved;
_slider = new GuiVerticalSliderControl
{
Bounds = new UniRectangle(
new UniScalar(1.0f, -20.0f), new UniScalar(0.0f, 0.0f),
new UniScalar(0.0f, 20.0f), new UniScalar(1.0f, 0.0f)
)
};
Children.Add(_slider);
}
/// <summary>How the user can select items in the list</summary>
public ListSelectionMode SelectionMode
{
get { return _selectionMode; }
set { _selectionMode = value; }
}
/// <summary>Slider the list uses to scroll through its items</summary>
public GuiVerticalSliderControl Slider => _slider;
/// <summary>Items being displayed in the list</summary>
public IList<string> Items => _items;
/// <summary>Indices of the items current selected in the list</summary>
public IList<int> SelectedItems => _selectedItems;
/// <summary>
/// Can be set by renderers to enable selection of list items by mouse
/// </summary>
public IListRowLocator ListRowLocator
{
get { return _listRowLocator; }
set
{
if (value != _listRowLocator)
{
_listRowLocator = value;
UpdateSlider();
}
}
}
/// <summary>Whether the control can currently obtain the input focus</summary>
bool IFocusable.CanGetFocus => true;
/// <summary>Triggered when the selected items in list have changed</summary>
public event EventHandler SelectionChanged;
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
/// <remarks>
/// If this method states that a mouse press is processed by returning
/// true, that means the control did something with it and the mouse press
/// should not be acted upon by any other listener.
/// </remarks>
protected override void OnMousePressed(MouseButton button)
{
if (_listRowLocator != null)
{
var row = _listRowLocator.GetRow(GetAbsoluteBounds(), _slider.ThumbPosition, _items.Count, _mouseY);
if ((row >= 0) && (row < _items.Count))
OnRowClicked(row);
}
}
/// <summary>Called when the user has clicked on a row in the list</summary>
/// <param name="row">Row the user has clicked on</param>
/// <remarks>
/// The default behavior of the list control in multi select mode is to
/// toggle items that are clicked between selected and unselected. If you
/// need different behavior (for example, dragging a selected region or
/// selecting sequences of items by holding the shift key), you can override
/// this method and handle the selection behavior yourself.
/// </remarks>
protected virtual void OnRowClicked(int row)
{
switch (_selectionMode)
{
// The user isn't allowed to select items in the list
case ListSelectionMode.None:
{
break;
}
// Only a single item can be selected at a time
case ListSelectionMode.Single:
{
if (_selectedItems.Count == 1)
{
if (_selectedItems[0] == row)
break; // do not fire the SelectionChanged event
_selectedItems[0] = row;
}
else
{
_selectedItems.Clear();
_selectedItems.Add(row);
}
OnSelectionChanged();
break;
}
// Any number of items can be selected
case ListSelectionMode.Multi:
{
if (!_selectedItems.Remove(row))
_selectedItems.Add(row);
OnSelectionChanged();
break;
}
}
}
/// <summary>Called when the mouse wheel has been rotated</summary>
/// <param name="ticks">Number of ticks that the mouse wheel has been rotated</param>
protected override void OnMouseWheel(float ticks)
{
const float itemsPerTick = 1.0f;
if (_listRowLocator != null)
{
var bounds = GetAbsoluteBounds();
float totalitems = _items.Count;
var itemsInView = bounds.Height/_listRowLocator.GetRowHeight(bounds);
var scrollableItems = totalitems - itemsInView;
_slider.ThumbPosition -= itemsPerTick/scrollableItems*ticks;
_slider.ThumbPosition = MathHelper.Clamp(_slider.ThumbPosition, 0.0f, 1.0f);
}
}
/// <summary>Called when the mouse position is updated</summary>
/// <param name="x">X coordinate of the mouse cursor on the control</param>
/// <param name="y">Y coordinate of the mouse cursor on the control</param>
protected override void OnMouseMoved(float x, float y)
{
_mouseY = y;
}
/// <summary>Called when the selected items in the list have changed</summary>
protected virtual void OnSelectionChanged()
{
SelectionChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>Called when an item is removed from the items list</summary>
/// <param name="sender">List the item has been removed from</param>
/// <param name="arguments">Contains the item that has been removed</param>
private void ItemRemoved(object sender, ItemEventArgs<string> arguments)
{
UpdateSlider();
}
/// <summary>Called when an item is added to the items list</summary>
/// <param name="sender">List the item has been added to</param>
/// <param name="arguments">Contains the item that has been added</param>
private void ItemAdded(object sender, ItemEventArgs<string> arguments)
{
UpdateSlider();
}
/// <summary>Called when the items list is about to clear itself</summary>
/// <param name="sender">Items list that is about to clear itself</param>
/// <param name="arguments">Not used</param>
private void ItemsCleared(object sender, EventArgs arguments)
{
UpdateSlider();
}
/// <summary>Called when an entry is added to the list of selected items</summary>
/// <param name="sender">List to which an item was added to</param>
/// <param name="arguments">Contains the added item</param>
private void SelectionAdded(object sender, ItemEventArgs<int> arguments)
{
OnSelectionChanged();
}
/// <summary>
/// Called when an entry is removed from the list of selected items
/// </summary>
/// <param name="sender">List from which an item was removed</param>
/// <param name="arguments">Contains the removed item</param>
private void SelectionRemoved(object sender, ItemEventArgs<int> arguments)
{
OnSelectionChanged();
}
/// <summary>Called when the selected items list is about to clear itself</summary>
/// <param name="sender">List that is about to clear itself</param>
/// <param name="arguments">Not Used</param>
private void SelectionCleared(object sender, EventArgs arguments)
{
OnSelectionChanged();
}
/// <summary>Updates the size and position of the list's slider</summary>
private void UpdateSlider()
{
if ((Screen != null) && (_listRowLocator != null))
{
var bounds = GetAbsoluteBounds();
float totalitems = _items.Count;
var itemsInView = bounds.Height/_listRowLocator.GetRowHeight(bounds);
_slider.ThumbSize = Math.Min(1.0f, itemsInView/totalitems);
}
}
}
}
@@ -1,34 +0,0 @@
using System;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>Control displaying an option the user can toggle on and off</summary>
public class GuiOptionControl : GuiPressableControl
{
/// <summary>Whether the option is currently selected</summary>
public bool Selected;
/// <summary>Text that will be shown on the button</summary>
public string Text;
/// <summary>Determines where text or image will be shown relative to control</summary>
public GuiPressableDescriptionPosition DescriptionPosition;
/// <summary>Will be triggered when the choice is changed</summary>
public event EventHandler Changed;
/// <summary>Called when the button is pressed</summary>
protected override void OnPressed()
{
Selected = !Selected;
OnChanged();
}
/// <summary>Triggers the changed event</summary>
protected virtual void OnChanged()
{
if (Changed != null)
Changed(this, EventArgs.Empty);
}
}
}
@@ -1,248 +0,0 @@
using MonoGame.Extended.Input.InputListeners;
using MonoGame.Extended;
using System;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
[Flags]
public enum ResizingSides
{
None = 0,
Top = 1,
Right = 2,
TopRight = 3,
Bottom = 4,
BottomRight = 6,
Left = 8,
TopLeft = 9,
BottomLeft = 12,
}
[Flags]
public enum ResizingDirections
{
None = 0,
Vertical = 1,
Horizontal = 2,
Both = 3
}
// Properties:
// Boundaries (for constraining a control to a region)
// Resizable (turn moveability on or off)
/// <summary>Control the user can drag around with the mouse</summary>
public abstract class GuiResizableControl : GuiDraggableControl
{
/// <summary>Which sides are being resized</summary>
private ResizingSides _resizingSides = ResizingSides.None;
/// <summary>Which Directions are being resized/summary>
private ResizingDirections _resizingDirections = ResizingDirections.None;
/// <summary>Whether the control is currently being dragged</summary>
private bool _beingResized;
/// <summary>Whether the control can be dragged</summary>
private bool _enableResizing;
/// <summary>X coordinate at which the control was grabbed</summary>
private float _grabX;
/// <summary>Y coordinate at which the control was grabbed</summary>
private float _grabY;
/// <summary>Width of Border</summary>
private float _borderThickness = 8;
/// <summary>Initializes a new draggable control</summary>
public GuiResizableControl()
{
EnableResizing = true;
}
/// <summary>Initializes a new draggable control</summary>
/// <param name="canGetFocus">Whether the control can obtain the input focus</param>
public GuiResizableControl(bool canGetFocus) : base(canGetFocus)
{
EnableResizing = true;
}
/// <summary>Whether the control can be dragged with the mouse</summary>
protected bool EnableResizing
{
get { return _enableResizing; }
set
{
_enableResizing = value;
_beingResized &= value;
}
}
protected override void OnMouseLeft(float x, float y)
{
if (_beingResized)
{
ResizeTo(x, y);
}
else
{
base.OnMouseLeft(x, y);
_resizingSides = ResizingSides.None;
SetCursorForResize(_resizingSides);
}
}
/// <summary>Called when the mouse position is updated</summary>
/// <param name="x">X coordinate of the mouse cursor on the GUI</param>
/// <param name="y">Y coordinate of the mouse cursor on the GUI</param>
protected override void OnMouseMoved(float x, float y)
{
base.OnMouseMoved(x, y);
if (_beingResized)
{
ResizeTo(x, y);
}
else
{
// Remember the current mouse position so we know where the user picked
// up the control when a drag operation begins
_grabX = x;
_grabY = y;
}
if (IsMouseOnBorder(x, y) && !_beingResized && _enableResizing)
{
SetResizingDirection();
}
else if (_resizingSides != ResizingSides.None && !_beingResized)
{
_resizingSides = ResizingSides.None;
SetCursorForResize(_resizingSides);
}
}
private void ResizeTo(float x, float y)
{
var OldBounds = Bounds;
var diffX = x - _grabX;
var diffY = y - _grabY;
var boundsX = Bounds.Size.X;
var boundsY = Bounds.Size.Y;
var locX = Bounds.Location.X;
var locY = Bounds.Location.Y;
if (_resizingSides.HasFlag(ResizingSides.Right))
{
boundsX = new UniScalar(boundsX.Fraction, boundsX.Offset + diffX);
}
if (_resizingSides.HasFlag(ResizingSides.Left))
{
boundsX = new UniScalar(boundsX.Fraction, boundsX.Offset - diffX);
locX = new UniScalar(locX.Fraction, locX.Offset + diffX);
}
if (_resizingSides.HasFlag(ResizingSides.Bottom))
{
boundsY = new UniScalar(boundsY.Fraction, boundsY.Offset + diffY);
}
if (_resizingSides.HasFlag(ResizingSides.Top))
{
boundsY = new UniScalar(boundsY.Fraction, boundsY.Offset - diffY);
locY = new UniScalar(locY.Fraction, locY.Offset + diffY);
}
Bounds = new UniRectangle(new UniVector(locX, locY), new UniVector(boundsX, boundsY));
if (GetAbsoluteBounds().Size.Width < 20 || GetAbsoluteBounds().Size.Height < 20) //Too Small
{
Bounds = OldBounds;
}
else
{
if (_resizingSides.HasFlag(ResizingSides.Right))
_grabX = x;
if (_resizingSides.HasFlag(ResizingSides.Bottom))
_grabY = y;
OnResize(OldBounds);
}
}
public virtual void OnResize(UniRectangle oldBounds)
{
}
public virtual void SetCursorForResize(ResizingSides resizingSides)
{
//No Default behavior
}
private void SetResizingDirection()
{
var _oldResizingSides = _resizingSides;
_resizingDirections = ResizingDirections.None;
_resizingSides = ResizingSides.None;
if (_grabX >= GetAbsoluteBounds().Size.Width - _borderThickness)
{
_resizingDirections |= ResizingDirections.Horizontal;
_resizingSides |= ResizingSides.Right;
}
if (_grabX <= _borderThickness)
{
_resizingDirections |= ResizingDirections.Horizontal;
_resizingSides |= ResizingSides.Left;
}
if (_grabY >= GetAbsoluteBounds().Size.Height - _borderThickness)
{
_resizingDirections |= ResizingDirections.Vertical;
_resizingSides |= ResizingSides.Bottom;
}
if (_grabY <= _borderThickness)
{
_resizingDirections |= ResizingDirections.Vertical;
_resizingSides |= ResizingSides.Top;
}
if (_oldResizingSides != _resizingSides)
{
SetCursorForResize(_resizingSides);
}
}
private bool IsMouseOnBorder(float x, float y)
{
var absoluteSize = GetAbsoluteBounds().Size;
return x < _borderThickness || y < _borderThickness || x > absoluteSize.Width - _borderThickness || y > absoluteSize.Height - _borderThickness;
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
protected override void OnMousePressed(MouseButton button)
{
if (button == MouseButton.Left && IsMouseOnBorder(_grabX, _grabY))
{
_beingResized = EnableResizing;
}
else
{
base.OnMousePressed(button);
}
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
protected override void OnMouseReleased(MouseButton button)
{
base.OnMouseReleased(button);
if (button == MouseButton.Left)
_beingResized = false;
}
}
}
@@ -1,118 +0,0 @@
using System;
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>Base class for a slider that can be moved using the mouse</summary>
/// <remarks>
/// Implements the common functionality for a slider moving either the direction
/// of the X or the Y axis (but not both). Derive any scroll bar-like controls
/// from this class to simplify their implementation.
/// </remarks>
public abstract class GuiSliderControl : GuiControl
{
/// <summary>Whether the mouse cursor is hovering over the thumb</summary>
private bool _mouseOverThumb;
/// <summary>X coordinate at which the thumb was picked up</summary>
private float _pickupX;
/// <summary>Y coordinate at which the thumb was picked up</summary>
private float _pickupY;
/// <summary>Whether the slider's thumb is currently in the depressed state</summary>
private bool _pressedDown;
/// <summary>Can be set by renderers to allow the control to locate its thumb</summary>
public IThumbLocator ThumbLocator;
/// <summary>Position of the thumb within the slider (0.0 .. 1.0)</summary>
public float ThumbPosition;
/// <summary>Fraction of the slider filled by the thumb (0.0 .. 1.0)</summary>
public float ThumbSize;
/// <summary>Initializes a new slider control</summary>
public GuiSliderControl()
{
ThumbPosition = 0.0f;
ThumbSize = 1.0f;
}
/// <summary>whether the mouse is currently hovering over the thumb</summary>
public bool MouseOverThumb => _mouseOverThumb;
/// <summary>Whether the pressable control is in the depressed state</summary>
public virtual bool ThumbDepressed => _pressedDown && _mouseOverThumb;
/// <summary>Triggered when the slider has been moved</summary>
public event EventHandler Moved;
/// <summary>Moves the thumb to the specified location</summary>
/// <returns>Location the thumb will be moved to</returns>
protected abstract void MoveThumb(float x, float y);
/// <summary>Obtains the region covered by the slider's thumb</summary>
/// <returns>The region covered by the slider's thumb</returns>
protected abstract RectangleF GetThumbRegion();
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
protected override void OnMousePressed(MouseButton button)
{
if (button == MouseButton.Left)
{
var thumbRegion = GetThumbRegion();
if (thumbRegion.Contains(new Point2(_pickupX, _pickupY)))
{
_pressedDown = true;
_pickupX -= thumbRegion.X;
_pickupY -= thumbRegion.Y;
}
}
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
protected override void OnMouseReleased(MouseButton button)
{
if (button == MouseButton.Left)
_pressedDown = false;
}
/// <summary>Called when the mouse position is updated</summary>
/// <param name="x">X coordinate of the mouse cursor on the control</param>
/// <param name="y">Y coordinate of the mouse cursor on the control</param>
protected override void OnMouseMoved(float x, float y)
{
if (_pressedDown)
{
//RectangleF bounds = GetAbsoluteBounds();
MoveThumb(x - _pickupX, y - _pickupY);
}
else
{
_pickupX = x;
_pickupY = y;
}
_mouseOverThumb = GetThumbRegion().Contains(new Point2(x, y));
}
/// <summary>
/// Called when the mouse has left the control and is no longer hovering over it
/// </summary>
protected override void OnMouseLeft(float x, float y)
{
_mouseOverThumb = false;
}
/// <summary>Fires the slider's Moved event</summary>
protected virtual void OnMoved()
{
Moved?.Invoke(this, EventArgs.Empty);
}
}
}
@@ -1,40 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>Vertical slider that can be moved using the mouse</summary>
public class GuiVerticalSliderControl : GuiSliderControl
{
/// <summary>Obtains the region covered by the slider's thumb</summary>
/// <returns>The region covered by the slider's thumb</returns>
protected override RectangleF GetThumbRegion()
{
var bounds = GetAbsoluteBounds();
if (ThumbLocator != null)
return ThumbLocator.GetThumbPosition(bounds, ThumbPosition, ThumbSize);
var thumbHeight = bounds.Height*ThumbSize;
var thumbY = (bounds.Height - thumbHeight)*ThumbPosition;
return new RectangleF(0, thumbY, bounds.Width, thumbHeight);
}
/// <summary>Moves the thumb to the specified location</summary>
/// <param name="x">X coordinate for the new left border of the thumb</param>
/// <param name="y">Y coordinate for the new upper border of the thumb</param>
protected override void MoveThumb(float x, float y)
{
var bounds = GetAbsoluteBounds();
var thumbHeight = bounds.Height*ThumbSize;
var maxY = bounds.Height - thumbHeight;
// Prevent divide-by-zero if the thumb fills out the whole rail
if (maxY > 0.0f)
ThumbPosition = MathHelper.Clamp(y/maxY, 0.0f, 1.0f);
else ThumbPosition = 0.0f;
OnMoved();
}
}
}
@@ -1,38 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>A window for hosting other controls</summary>
public class GuiWindowControl : GuiResizableControl
{
/// <summary>Text in the title bar of the window</summary>
public string Title;
/// <summary>Initializes a new window control</summary>
public GuiWindowControl() : base(true)
{
}
/// <summary>Whether the window is currently open</summary>
public bool IsOpen => Screen != null;
/// <summary>Whether the window can be dragged with the mouse</summary>
public new bool EnableDragging
{
get { return base.EnableDragging; }
set { base.EnableDragging = value; }
}
/// <summary>Whether the window can be resized with the mouse</summary>
public new bool EnableResizing
{
get { return base.EnableResizing; }
set { base.EnableResizing = value; }
}
/// <summary>Closes the window</summary>
public void Close()
{
if (IsOpen)
Parent.Children.Remove(this);
}
}
}
@@ -1,38 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>
/// Interface which can be established between a control and its renderer to
/// allow a list control to locate the list row the cursor is in
/// </summary>
/// <remarks>
/// A renderer can implement this interface and assign it to a control that
/// it renders so the control can ask the renderer for extended informations
/// regarding the look of its text. If this interface is provided, certain
/// controls will be able to correctly place the caret in user-editable text
/// when they are clicked by the mouse.
/// </remarks>
public interface IListRowLocator
{
/// <summary>Calculates the list row the cursor is in</summary>
/// <param name="bounds">
/// Boundaries of the control, should be in absolute coordinates
/// </param>
/// <param name="thumbPosition">
/// Position of the thumb in the list's slider
/// </param>
/// <param name="itemCount">
/// Number of items contained in the list
/// </param>
/// <param name="y">Vertical position of the cursor</param>
/// <returns>The row the cursor is over</returns>
int GetRow(RectangleF bounds, float thumbPosition, int itemCount, float y);
/// <summary>Determines the height of a row displayed in the list</summary>
/// <param name="bounds">
/// Boundaries of the control, should be in absolute coordinates
/// </param>
/// <returns>The height of a single row in the list</returns>
float GetRowHeight(RectangleF bounds);
}
}
@@ -1,32 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>
/// Interface which can be established between a control and its renderer to
/// allow the control to locate openings between letters
/// </summary>
/// <remarks>
/// A renderer can implement this interface and assign it to a control that
/// it renders so the control can ask the renderer for extended informations
/// regarding the look of its text. If this interface is provided, certain
/// controls will be able to correctly place the caret in user-editable text
/// when they are clicked by the mouse.
/// </remarks>
public interface IOpeningLocator
{
/// <summary>
/// Calculates which opening between two letters is closest to a position
/// </summary>
/// <param name="bounds">
/// Boundaries of the control, should be in absolute coordinates
/// </param>
/// <param name="text">Text in which the nearest opening will be located</param>
/// <param name="position">
/// Position to which the closest opening will be found,
/// should be in absolute coordinates
/// </param>
/// <returns>The index of the opening closest to the provided position</returns>
int GetClosestOpening(RectangleF bounds, string text, Vector2 position);
}
}
@@ -1,28 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls.Desktop
{
/// <summary>
/// Interface which can be established between a control and its renderer to
/// allow a slider control to locate its thumb
/// </summary>
/// <remarks>
/// A renderer can implement this interface and assign it to a control that
/// it renders so the control can ask the renderer for extended informations
/// regarding the look of its text. If this interface is provided, certain
/// controls will be able to correctly place the caret in user-editable text
/// when they are clicked by the mouse.
/// </remarks>
public interface IThumbLocator
{
/// <summary>
/// Calculates the position of the thumb on a slider
/// </summary>
/// <param name="bounds">
/// Boundaries of the control, should be in absolute coordinates
/// </param>
/// <param name="thumbPosition">Relative position of the thumb (0.0 .. 1.0)</param>
/// <param name="thumbSize">Relative size of the thumb (0.0 .. 1.0)</param>
/// <returns>The region covered by the slider's thumb</returns>
RectangleF GetThumbPosition(RectangleF bounds, float thumbPosition, float thumbSize);
}
}
@@ -1,361 +0,0 @@
using System;
using System.Collections.ObjectModel;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
using MonoGame.Extended.NuclexGui.Input;
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>Represents an element in the user interface</summary>
/// <remarks>
/// <para>
/// Controls are always arranged in a tree where each control except the one at
/// the root of the tree has exactly one owner (the one at the root has no owner).
/// The design actively prevents you from assigning a control as child to
/// multiple parents.
/// </para>
/// <para>
/// The controls in the Nuclex.UserInterface library are fully independent of
/// their graphical representation. That means you can construct a dialog
/// without even having a graphics device in place, that you can move your
/// dialogs between different graphics devices and that you do not have to
/// even think about graphics device resets and similar trouble.
/// </para>
/// </remarks>
public partial class GuiControl
{
/// <summary>Whether this control can obtain the input focus</summary>
private readonly bool _affectsOrdering;
/// <summary>Child controls belonging to this control</summary>
/// <remarks>
/// Child controls are any controls that belong to this control. They don't
/// neccessarily need to be situated in this control's client area, but
/// their positioning will be relative to the parent's location.
/// </remarks>
private readonly ParentingControlCollection _children;
/// <summary>Name of the control instance (for programmatic identification)</summary>
private string _name;
/// <summary>Control this control is contained in</summary>
private GuiControl _parent;
/// <summary>GUI instance this control has been added to. Can be null.</summary>
private GuiScreen _screen;
/// <summary>Initializes a new control</summary>
public GuiControl() : this(false)
{
}
/// <summary>Initializes a new control</summary>
/// <param name="affectsOrdering">
/// Whether the control comes to the top of the hierarchy when clicked
/// </param>
/// <remarks>
/// <para>
/// The <paramref name="affectsOrdering" /> parameter should be set for windows
/// and other free-floating panels which exist in parallel and which the user
/// might want to put on top of their siblings by clicking them. If the user
/// clicks on a child control of such a panel/window control, the panel/window
/// control will also be moved into the foreground.
/// </para>
/// <para>
/// It should not be set for normal controls which usually have no overlap,
/// like buttons. Otherwise, a button placed on the desktop could overdraw a
/// window when the button is clicked. The behavior would be well-defined and
/// controlled, but the user probably doesn't expect this ;-)
/// </para>
/// </remarks>
protected GuiControl(bool affectsOrdering)
{
_affectsOrdering = affectsOrdering;
_children = new ParentingControlCollection(this);
}
/// <summary>Location and extents of the control</summary>
public UniRectangle Bounds;
/// <summary>Children of the control</summary>
public Collection<GuiControl> Children => _children;
/// <summary>
/// True if clicking the control or its children moves the control into
/// the foreground of the drawing hierarchy
/// </summary>
public bool AffectsOrdering => _affectsOrdering;
/// <summary>Parent control this control is contained in</summary>
/// <remarks>
/// Can be null, but this is only the case for free-floating controls that have
/// not been added into a Gui. The only control that really keeps this field
/// set to null whilst the Gui is active is the root control in the Gui class.
/// </remarks>
public GuiControl Parent => _parent;
/// <summary>Name that can be used to uniquely identify the control</summary>
/// <remarks>
/// This name acts as an unique identifier for a control. It primarily serves
/// as a means to programmatically identify the control and as a debugging aid.
/// Duplicate names are not allowed and will result in an exception being
/// thrown, the only exception is when the control's name is set to null.
/// </remarks>
public string Name
{
get { return _name; }
set
{
// Don't do anything if we're given the same name we already have. This
// is not a pure performance optimization, it also prevents the control
// from reporting an name collision with itself in this special case :)
if (value != _name)
{
// Look for name collisions with our siblings
var parent = Parent;
if (parent != null)
{
if (parent._children.IsNameTaken(value))
throw new InvalidOperationException("Another control is already using this name");
}
// Everything seems to be ok, accept the new name
_name = value;
}
}
}
/// <summary>GUI instance this control belongs to. Can be null.</summary>
internal GuiScreen Screen => _screen;
/// <summary>Control the mouse is currently over</summary>
protected internal GuiControl MouseOverControl => _mouseOverControl;
/// <summary>Control that currently captured incoming input</summary>
protected internal GuiControl ActivatedControl => _activatedControl;
/// <summary>Moves the control into the foreground</summary>
public void BringToFront()
{
// Doing nothing if we don't have a parent is okay since in that case,
// we're the root and we're the frontmost control in any case. If the user
// calls BringToFront() on a control before he integrates it into the GUI
// tree, this is expected behavior and only logical.
var control = this;
while (!ReferenceEquals(control._parent, null))
{
var siblings = control._parent._children;
siblings.MoveToStart(siblings.IndexOf(control));
control = control._parent;
}
}
/// <summary>Obtains the absolute boundaries of the control in screen coordinates</summary>
/// <returns>The control's absolute screen coordinate boundaries</returns>
/// <remarks>
/// This method resolves the unified coordinates into absolute screen coordinates
/// that can be used to do hit-testing and rendering. The control is required to
/// be part of a GUI hierarchy that is assigned to a screen for this to work
/// since otherwise, there's no absolute coordinate frame into which the
/// unified coordinates could be resolved.
/// </remarks>
public RectangleF GetAbsoluteBounds()
{
// Is this the topmost control in the hierarchy (the desktop control)?
if (ReferenceEquals(_parent, null))
{
// Make sure the control is attached to a screen, otherwise, it's a free
// control not living in any GUI hierarchy and thus, does not have
// absolute bounds yet.
if (ReferenceEquals(_screen, null))
{
throw new InvalidOperationException(
"Obtaining absolute bounds requires the control to be part of a screen"
);
}
// Transform the unified coordinate bounds into absolute pixel coordinates
// for the screen's dimensions
return Bounds.ToOffset(_screen.Width, _screen.Height);
}
// Control is the child of another control
// Recursively determine the bounds of the parent control until we end up
// at the desktop control (or not, if this is a free living hierarchy, in
// which case the exception above will be triggered as soon as the top of
// the hierarchy is reached)
var parentBounds = _parent.GetAbsoluteBounds();
// Determine the controls absolute position based on the absolute
// dimensions and position of the parent control
var controlBounds = Bounds.ToOffset(parentBounds.Width, parentBounds.Height);
controlBounds.Offset(parentBounds.X, parentBounds.Y);
// Done, controlBounds now contains the absolute screen coordinates of
// the control's boundaries.
return controlBounds;
}
/// <summary>Called when an input command was sent to the control</summary>
/// <param name="command">Input command that has been triggered</param>
/// <returns>Whether the command has been processed by the control</returns>
protected virtual bool OnCommand(Command command)
{
return false;
}
/// <summary>Called when a button on the gamepad has been pressed</summary>
/// <param name="button">Button that has been pressed</param>
/// <returns>True if the button press was handled by the control, otherwise false.</returns>
/// <remarks>
/// If the control indicates that it didn't handle the key press, it will not
/// receive the associated key release notification.
/// </remarks>
protected virtual bool OnButtonPressed(Buttons button)
{
return false;
}
/// <summary>Called when a button on the gamepad has been released</summary>
/// <param name="button">Button that has been released</param>
protected virtual void OnButtonReleased(Buttons button)
{
}
/// <summary>Called when the mouse position is updated</summary>
/// <param name="x">X coordinate of the mouse cursor on the control</param>
/// <param name="y">Y coordinate of the mouse cursor on the control</param>
protected virtual void OnMouseMoved(float x, float y)
{
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
/// <returns>Whether the control has processed the mouse press</returns>
/// <remarks>
/// If this method states that a mouse press is processed by returning
/// true, that means the control did something with it and the mouse press
/// should not be acted upon by any other listener.
/// </remarks>
protected virtual void OnMousePressed(MouseButton button)
{
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
protected virtual void OnMouseReleased(MouseButton button)
{
}
/// <summary>
/// Called when the mouse has left the control and is no longer hovering over it
/// </summary>
protected virtual void OnMouseLeft(float x, float y)
{
}
/// <summary>
/// Called when the mouse has entered the control and is now hovering over it
/// </summary>
protected virtual void OnMouseEntered()
{
}
/// <summary>Called when the mouse wheel has been rotated</summary>
/// <param name="ticks">Number of ticks that the mouse wheel has been rotated</param>
protected virtual void OnMouseWheel(float ticks)
{
}
/// <summary>Called when a key on the keyboard has been pressed down</summary>
/// <param name="keyCode">Code of the key that was pressed</param>
/// <returns>
/// True if the key press was handled by the control, otherwise false.
/// </returns>
/// <remarks>
/// If the control indicates that it didn't handle the key press, it will not
/// receive the associated key release notification. This means that if you
/// return false from this method, you should under no circumstances do anything
/// with the information - you will not know when the key is released again
/// and another control might pick it up, causing a second key response.
/// </remarks>
protected virtual bool OnKeyPressed(Keys keyCode)
{
return false;
}
/// <summary>Called when a key on the keyboard has been released again</summary>
/// <param name="keyCode">Code of the key that was released</param>
protected virtual void OnKeyReleased(Keys keyCode)
{
}
/// <summary>Called when a command was sent to the control</summary>
/// <param name="command">Command to be injected</param>
/// <returns>Whether the command has been processed</returns>
internal bool ProcessCommand(Command command)
{
switch (command)
{
// These are not supported on the control level
case Command.SelectPrevious:
case Command.SelectNext:
{
return false;
}
// These can be handled by user code if he so wishes
case Command.Up:
case Command.Down:
case Command.Left:
case Command.Right:
case Command.Accept:
case Command.Cancel:
{
return OnCommand(command);
}
// Value not contained in enumation - should not be happening!
default:
{
throw new ArgumentException("Invalid command", "command");
}
}
}
/// <summary>Assigns a new parent to the control</summary>
/// <param name="parent">New parent to assign to the control</param>
internal void SetParent(GuiControl parent)
{
_parent = parent;
// Have we been assigned to a parent?
if (_parent != null)
{
// If this ownership change transferred us to a different gui, we will
// have to migrate our visual and also the visuals of all our children.
if (!ReferenceEquals(_screen, parent._screen))
SetScreen(parent._screen);
}
else
{
// No parent, we're now officially an orphan ;)
// Orphans don't have screens!
SetScreen(null);
}
}
/// <summary>Assigns a new GUI to the control</summary>
/// <param name="gui">New GUI to assign to the control</param>
internal void SetScreen(GuiScreen gui)
{
_screen = gui;
_children.SetScreen(gui);
}
}
}
@@ -1,21 +0,0 @@
using System;
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>Event argument class that carries a control instance</summary>
public class ControlEventArgs : EventArgs
{
/// <summary>Control that will be accessible to the event subscribers</summary>
private readonly GuiControl _control;
/// <summary>Initializes a new control event args instance</summary>
/// <param name="control">Control to provide to the subscribers of the event</param>
public ControlEventArgs(GuiControl control)
{
_control = control;
}
/// <summary>Control that has been provided for the event</summary>
public GuiControl Control => _control;
}
}
@@ -1,460 +0,0 @@
using System.Diagnostics;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
namespace MonoGame.Extended.NuclexGui.Controls
{
public partial class GuiControl
{
/// <summary>Control the mouse was pressed down on</summary>
private GuiControl _activatedControl;
/// <summary>Number of game pad buttons being held down</summary>
private int _heldButtonCount;
/// <summary>Number of keyboard keys being held down</summary>
private int _heldKeyCount;
/// <summary>Mouse buttons the user is holding down over the control</summary>
private MouseButton _heldMouseButtons;
/// <summary>Control the mouse is currently hovering over</summary>
private GuiControl _mouseOverControl;
/// <summary>Whether any keys, mouse buttons or game pad buttons are beind held pressed</summary>
private bool AnyKeysOrButtonsPressed => (_heldMouseButtons != 0) || (_heldKeyCount > 0) || (_heldButtonCount > 0);
/// <summary>Called when a button on the game pad has been pressed</summary>
/// <param name="button">Button that has been pressed</param>
/// <returns>
/// True if the button press was processed by the control and future game pad
/// input belongs to the control until all buttons are released again
/// </returns>
internal bool ProcessButtonPress(Buttons button)
{
// If there's an activated control (one being held down by the mouse or having
// accepted a previous button press), this control will get the button press
// delivered, whether it wants to or not.
if (_activatedControl != null)
{
++_heldButtonCount;
// If one of our children is the activated control, pass on the message
if (_activatedControl != this)
_activatedControl.ProcessButtonPress(button);
else
OnButtonPressed(button);
// We're already activated, so this button press is accepted in any case
return true;
}
// A button has been pressed but no control is activated currently. This means we
// have to look for a control which feels responsible for the button press,
// starting with ourselves.
// Does the user code in our derived class feel responsible for this button?
// If so, we're the new activated control and the button has been handled.
if (OnButtonPressed(button))
{
_activatedControl = this;
++_heldButtonCount;
return true;
}
// Nope, we have to ask our children to find a control that feels responsible.
var encounteredOrderingControl = false;
foreach (var child in _children)
{
// We only process one child that has the affectsOrdering field set. This
// ensures that key presses will not be delivered to windows sitting behind
// another window. Other siblings that are not windows are asked still, so
// a bunch of buttons on the desktop would be asked in addition to a window.
if (child._affectsOrdering)
{
if (encounteredOrderingControl)
continue;
encounteredOrderingControl = true;
}
// Does this child feel responsible for the button press?
if (child.ProcessButtonPress(button))
{
_activatedControl = child;
++_heldButtonCount;
return true;
}
}
// Neither we nor any of our children felt responsible for the button. Give up.
return false;
}
/// <summary>Called when a button on the game pad has been released</summary>
/// <param name="button">Button that has been released</param>
internal void ProcessButtonRelease(Buttons button)
{
// If we're the top level control, we will receive button presses and their related
// releases even if nobody was interested in the button presses. Thus, we silently
// ignore those presses we didn't accept.
if (_heldButtonCount == 0)
return;
// If we receive a release, we must have a control on which the mouse
// was pressed (possibly even ourselves)
Debug.Assert(
_activatedControl != null,
"ProcessButtonRelease() had no control a button was pressed on; " +
"ProcessButtonRelease() was called on a control instance, but the control " +
"did not register a prior button press for itself or any of its child controls"
);
--_heldButtonCount;
if (_activatedControl != this)
_activatedControl.ProcessButtonRelease(button);
else
OnButtonReleased(button);
// If no more keys buttons are being held down, clear the activated control
if (!AnyKeysOrButtonsPressed)
_activatedControl = null;
}
/// <summary>
/// Called when the mouse has left the control and is no longer hovering over it
/// </summary>
internal void ProcessMouseLeave(float x, float y)
{
// Because the mouse has left us, if we have a mouse-over control, it also
// cannot be over one of our children Children leaving the parent container
// are not supported by design and for consistency, the behavior is tweaked
// so the children are left when the parent is left - this avoids strange
// behavior like being able to select a control if entering it with the mouse
// from the container side but being unable to select it if entering from
// the outside.
if (_mouseOverControl != null)
{
if (_mouseOverControl != this)
_mouseOverControl.ProcessMouseLeave(x, y);
else
OnMouseLeft(x, y);
_mouseOverControl = null;
}
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
/// <returns>Whether the control has processed the mouse press</returns>
internal bool ProcessMousePress(MouseButton button)
{
// We remember the control the mouse was pressed over and won't replace it for
// as long as the mouse is being held down. This ensures the mouse release
// notification is always delivered to a control, even if the mouse is released
// after moving it away from the control.
if (_activatedControl == null)
{
_activatedControl = _mouseOverControl;
// If we received an initial mouse press outside of our control area,
// someone is feeding us notifications we shouldn't be receiving. The best
// thing we can do is ignore this notification. This is a normal situation
// for the top level control which does the input filtering.
if (_activatedControl == null)
return false;
// If we're a control that can appear on top of or below our siblings in
// the z order, bring us into foreground since the user just clicked on us.
if (_activatedControl != this)
{
if (_activatedControl._affectsOrdering)
_children.MoveToStart(_children.IndexOf(_activatedControl));
}
}
// Add the buttons to the list of mouse buttons being held down. This is used
// to track when we should clear the mouse-over control again.
_heldMouseButtons |= button;
// If the mouse is over another control, pass on the mouse press.
if (_activatedControl != this)
return _activatedControl.ProcessMousePress(button);
// Otherwise, the mouse press applies to us
// If this control can take the input focus, make it the focused control
if (_screen != null)
{
var focusable = this as IFocusable;
if ((focusable != null) && focusable.CanGetFocus)
_screen.FocusedControl = this;
}
// Deliver the notification to the control deriving from us
OnMousePressed(button);
return true;
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
internal void ProcessMouseRelease(MouseButton button)
{
// When the mouse is clicked on game window's border and the user drags it
// into the GUI area, we will get a rogue mouse release message without
// the related mouse press. We ignore such rogue mouse release messages.
if ((_heldMouseButtons & button) != button)
return;
// If we receive a release, we must have a control on which the mouse
// was pressed (possibly even ourselves)
Debug.Assert(
_activatedControl != null,
"ProcessMouseRelease() had no control the mouse was pressed on; " +
"ProcessMouseRelease() was called on a control instance, but the control " +
"did not register a prior mouse press over itself or any of its child controls"
);
// Remove the button from the list of mouse buttons being held down. This
// allows us to see when we can clear the mouse-press control.
_heldMouseButtons &= ~button;
// If the mouse was held over one of our childs, pass on the notification
if (_activatedControl != this)
_activatedControl.ProcessMouseRelease(button);
else
OnMouseReleased(button);
// If no more mouse buttons are being held down, clear the mouse-press control
if (!AnyKeysOrButtonsPressed)
_activatedControl = null;
}
/// <summary>Processes mouse movement notifications</summary>
/// <param name="containerWidth">Absolute width of the control's container</param>
/// <param name="containerHeight">Absolute height of the control's container</param>
/// <param name="x">Absolute X position of the mouse within the container</param>
/// <param name="y">Absolute Y position of the mouse within the container</param>
internal void ProcessMouseMove(float containerWidth, float containerHeight, float x, float y)
{
// Calculate the absolute pixel position and size of this control
var size = Bounds.Size.ToOffset(containerWidth, containerHeight);
// If a mouse button is being held down, the mouse movement notification is
// delivered to the control the mouse was pressed on first. This guarantees that
// windows can be dragged even if the mouse was close to the window border and
// leaves the window during dragging.
if (_activatedControl != null)
{
var mouseX = x - Bounds.Location.X.ToOffset(containerWidth);
var mouseY = y - Bounds.Location.Y.ToOffset(containerHeight);
// Deliver the mouse move notifcation (either to our own user code or
// to the control the mouse of hovering over)
if (_activatedControl != this)
_activatedControl.ProcessMouseMove(size.X, size.Y, mouseX, mouseY);
else
OnMouseMoved(mouseX, mouseY);
}
// Calculate the absolute mouse position. We cannot reuse the value calculated
// in the mouse-press handling code because the control could have been moved when
// we called OnMouseMoved() - a typical use case for draggable controls.
x -= Bounds.Location.X.ToOffset(containerWidth);
y -= Bounds.Location.Y.ToOffset(containerHeight);
// Check whether the mouse is hovering over one of our children and if so,
// pass on the mouse movement notification to the child.
foreach (var control in _children)
{
var childBounds = control.Bounds.ToOffset(size.X, size.Y);
// Is the mouse over this child?
if (childBounds.Contains(new Point2(x, y)))
{
SwitchMouseOverControl(control, x, y);
// Hand over the mouse movement data to the child control the mouse is
// hovering over. If this is the mouse-press control, do nothing because
// we already delivered the movement notification out of order.
if (_mouseOverControl != _activatedControl)
_mouseOverControl.ProcessMouseMove(size.X, size.Y, x, y);
// We got our mouse-over control, end processing.
return;
}
}
// The mouse was over none of our children, so it must be hovering over us,
// unless we're the control being pressed down, in which case we'd also be
// getting mouse movement data outside of our boundaries. In this case, we
// only should become the mouse-over control is actually over us.
if (
(x >= 0.0f) && (x < size.X) &&
(y >= 0.0f) && (y < size.Y)
)
{
SwitchMouseOverControl(this, x, y);
// If we weren't pressed, we didn't deliver the out-of-order update to
// our implementation. Send our implementation a normal ordered update.
if (_activatedControl == null)
OnMouseMoved(x, y);
}
else
{
// redundant - our parent handles this - but convenient for unit tests
ProcessMouseLeave(x, y);
}
}
/// <summary>Called when the mouse wheel has been rotated</summary>
/// <param name="ticks">Number of ticks that the mouse wheel has been rotated</param>
internal void ProcessMouseWheel(float ticks)
{
// If the mouse is being held down on a control, give it any mouse wheel
// messages. This enables some exotic uses for the mouse wheel, such as holding
// an object with the mouse button and scaling it with the wheel at the same time.
if (_activatedControl != null)
{
if (_activatedControl != this)
{
_activatedControl.ProcessMouseWheel(ticks);
return;
}
}
// If the mouse wheel has been used normally, send the wheel notifications to
// the control the mouse is over.
if (_mouseOverControl != null)
{
if (_mouseOverControl != this)
{
_mouseOverControl.ProcessMouseWheel(ticks);
return;
}
}
// We're the control the mouse is over, let the user code handle
// the mouse wheel rotation
OnMouseWheel(ticks);
}
/// <summary>Called when a key on the keyboard has been pressed down</summary>
/// <param name="keyCode">Code of the key that was pressed</param>
/// <param name="repetition">Whether the key press is due to the user holding down a key</param>
internal bool ProcessKeyPress(Keys keyCode, bool repetition)
{
// If there's an activated control (one being held down by the mouse or having
// accepted a previous key press), this control will get the key press delivered,
// whether it wants to or not. We don't want to track for each key which control
// is currently processing it. ;-)
if (_activatedControl != null)
{
if (!repetition)
++_heldKeyCount;
// If one of our children is the activated control, pass on the message
if (_activatedControl != this)
_activatedControl.ProcessKeyPress(keyCode, repetition);
else
OnKeyPressed(keyCode); // We're the activated control
return true; // Ignore user code and always accept the key press
}
// A key has been pressed but no control is activated currently. This means we
// have to look for a control which feels responsible for the key press, starting
// with ourselves.
// Does the user code in our derived class feel responsible for this key?
// If so, we're the new activated control and the key has been handled.
if (OnKeyPressed(keyCode))
{
_activatedControl = this;
++_heldKeyCount;
return true;
}
// Nope, we have to ask our children (and they, potentially recursively, theirs)
// to find a control that feels responsible.
var encounteredOrderingControl = false;
for (var index = 0; index < _children.Count; ++index)
{
var child = _children[index];
// We only process one child that has the affectsOrdering field set. This
// ensures that key presses will not be delivered to windows sitting behind
// another window. Other siblings that are not windows are asked still.
if (child._affectsOrdering)
{
if (encounteredOrderingControl)
continue;
else
encounteredOrderingControl = true;
}
// Does this child feel responsible for the key press?
if (child.ProcessKeyPress(keyCode, repetition))
{
_activatedControl = child;
++_heldKeyCount;
return true;
}
}
// Neither we nor any of our children felt responsible for the key. Give up.
return false;
}
/// <summary>Called when a key on the keyboard has been released again</summary>
/// <param name="keyCode">Code of the key that was released</param>
internal void ProcessKeyRelease(Keys keyCode)
{
// Any key release should have an associated key press, otherwise, someone
// delivered notifications to us we should not have received.
Debug.Assert(
_heldKeyCount > 0,
"ProcessKeyRelease() called more often then ProcessKeyPress(); " +
"ProcessKeyRelease() was called more often the ProcessKeyPress() has been " +
"called with the repetition parameter set to false"
);
// If we receive a release, we must have a control on which the mouse
// was pressed (possibly even ourselves)
Debug.Assert(
_activatedControl != null,
"ProcessKeyRelease() had no control a key was pressed on; " +
"ProcessKeyRelease() was called on a control instance, but the control " +
"did not register a prior key press for itself or any of its child controls"
);
--_heldKeyCount;
if (_activatedControl != this)
_activatedControl.ProcessKeyRelease(keyCode);
else
OnKeyReleased(keyCode);
// If no more keys buttons are being held down, clear the activated control
if (!AnyKeysOrButtonsPressed)
_activatedControl = null;
}
/// <summary>Switches the mouse over control to a different control</summary>
/// <param name="newMouseOverControl">New control the mouse is hovering over</param>
private void SwitchMouseOverControl(GuiControl newMouseOverControl, float x, float y)
{
if (_mouseOverControl != newMouseOverControl)
{
// Tell the previous mouse-over control that the mouse is no longer
// hovering over it
if (_mouseOverControl != null)
_mouseOverControl.ProcessMouseLeave(x, y);
_mouseOverControl = newMouseOverControl;
// Inform the new mouse-over control that the mouse is now over it
newMouseOverControl.OnMouseEntered();
}
}
}
}
@@ -1,33 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>Control used to represent the desktop</summary>
public class GuiDesktopControl : GuiControl
{
/// <summary>Initializes a new control</summary>
public GuiDesktopControl()
{
}
/// <summary>True if the mouse is currently hovering over a GUI element</summary>
public bool IsMouseOverGui
{
get
{
if (MouseOverControl == null)
return false;
return !ReferenceEquals(MouseOverControl, this);
}
}
/// <summary>Whether the GUI holds ownership of the input devices</summary>
public bool IsInputCaptured
{
get
{
if (ActivatedControl == null)
return false;
return !ReferenceEquals(ActivatedControl, this);
}
}
}
}
@@ -1,34 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>Control that draws a block of text</summary>
public class GuiLabelControl : GuiControl
{
/// <summary>Text to be rendered in the control's frame</summary>
public string Text;
/// <summary>
/// Gets or sets the style to be used with this label when drawing.
/// </summary>
/// <remarks>
/// <para>
/// If you are using the FlatGuiVisualizer, this style
/// corresponds to the 'Frames' used in
/// <see cref="MonoGame.Extended.NuclexGui.Visuals.Flat.FlatGuiGraphics"/>
/// The style can be customized in the skin's json file.
/// </para>
/// </remarks>
public string Style { get; set; }
/// <summary>Initializes a new label control with an empty string</summary>
public GuiLabelControl() : this(string.Empty)
{
}
/// <summary>Initializes a new label control</summary>
/// <param name="text">Text to be printed at the location of the label control</param>
public GuiLabelControl(string text)
{
Text = text;
}
}
}
@@ -1,278 +0,0 @@
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>
/// Determines where the text or image describing control will be located.
/// Used primarily for radio button and checkbox.
/// </summary>
public enum GuiPressableDescriptionPosition
{
North,
South,
West,
East
}
/// <summary>User interface element the user can push down</summary>
public abstract class GuiPressableControl : GuiControl, IFocusable
{
/// <summary>Whether the mouse is hovering over the command</summary>
private bool _mouseHovering;
/// <summary>Whether the command is pressed down using the game pad shortcut</summary>
private bool _pressedDownByGamepadShortcut;
/// <summary>Whether the command is pressed down using the space key</summary>
private bool _pressedDownByKeyboard;
/// <summary>Whether the command is pressed down using the keyboard shortcut</summary>
private bool _pressedDownByKeyboardShortcut;
/// <summary>Whether the command is pressed down using the mouse</summary>
private bool _pressedDownByMouse;
/// <summary>Whether the user can interact with the choice</summary>
public bool Enabled;
/// <summary>Button that can be pressed to activate this command</summary>
public Buttons? ShortcutButton;
/// <summary>Initializes a new command control</summary>
public GuiPressableControl()
{
Enabled = true;
}
/// <summary>Whether the mouse pointer is hovering over the control</summary>
public bool MouseHovering => _mouseHovering;
/// <summary>Whether the pressable control is in the depressed state</summary>
public virtual bool Depressed
{
get
{
var mousePressed = _mouseHovering && _pressedDownByMouse;
return
mousePressed ||
_pressedDownByKeyboard ||
_pressedDownByKeyboardShortcut ||
_pressedDownByGamepadShortcut;
}
}
/// <summary>Whether the control currently has the input focus</summary>
public bool HasFocus => (Screen != null) &&
ReferenceEquals(Screen.FocusedControl, this);
/// <summary>Whether the control can currently obtain the input focus</summary>
bool IFocusable.CanGetFocus => Enabled;
/// <summary>
/// Called when the mouse has entered the control and is now hovering over it
/// </summary>
protected override void OnMouseEntered()
{
_mouseHovering = true;
}
/// <summary>
/// Called when the mouse has left the control and is no longer hovering over it
/// </summary>
protected override void OnMouseLeft(float x, float y)
{
// Intentionally not calling OnActivated() here because the user has moved
// the mouse away from the command while holding the mouse button down -
// a common trick under windows to last-second-abort the clicking of a button
_mouseHovering = false;
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
protected override void OnMousePressed(MouseButton button)
{
if (Enabled)
{
if (button == MouseButton.Left)
_pressedDownByMouse = true;
}
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
protected override void OnMouseReleased(MouseButton button)
{
if (button == MouseButton.Left)
{
_pressedDownByMouse = false;
// Only trigger the pressed event if the mouse was released over the control.
// The user can move the mouse cursor away from the control while still holding
// the mouse button down to do the well-known last-second-abort.
if (_mouseHovering && Enabled)
{
if (!Depressed)
OnPressed();
}
}
}
/// <summary>Called when a button on the gamepad has been pressed</summary>
/// <param name="button">Button that has been pressed</param>
/// <returns>
/// True if the button press was handled by the control, otherwise false.
/// </returns>
protected override bool OnButtonPressed(Buttons button)
{
if (ShortcutButton.HasValue)
{
if (button == ShortcutButton.Value)
{
_pressedDownByGamepadShortcut = true;
return true;
}
}
return false;
}
/// <summary>Called when a button on the gamepad has been released</summary>
/// <param name="button">Button that has been released</param>
protected override void OnButtonReleased(Buttons button)
{
if (ShortcutButton.HasValue)
{
if (_pressedDownByGamepadShortcut)
{
if (button == ShortcutButton.Value)
{
_pressedDownByGamepadShortcut = false;
if (!Depressed)
OnPressed();
}
}
}
}
/// <summary>Called when a key on the keyboard has been pressed down</summary>
/// <param name="keyCode">Code of the key that was pressed</param>
/// <returns>
/// True if the key press was handled by the control, otherwise false.
/// </returns>
protected override bool OnKeyPressed(Keys keyCode)
{
if (ShortcutButton.HasValue)
{
if (keyCode == KeyFromButton(ShortcutButton.Value))
{
_pressedDownByKeyboardShortcut = true;
return true;
}
}
if (HasFocus)
{
if (keyCode == Keys.Space)
{
_pressedDownByKeyboard = true;
return true;
}
}
return false;
}
/// <summary>Called when a key on the keyboard has been released again</summary>
/// <param name="keyCode">Code of the key that was released</param>
protected override void OnKeyReleased(Keys keyCode)
{
if (_pressedDownByKeyboardShortcut)
{
if (ShortcutButton.HasValue)
{
if (keyCode == KeyFromButton(ShortcutButton.Value))
{
_pressedDownByKeyboardShortcut = false;
if (!Depressed)
OnPressed();
}
}
}
if (_pressedDownByKeyboard)
{
if (keyCode == Keys.Space)
{
_pressedDownByKeyboard = false;
if (!Depressed)
OnPressed();
}
}
}
/// <summary>Called when the control is pressed</summary>
/// <remarks>
/// If you were to implement a button, for example, you could trigger a 'Pressed'
/// event here are call a user-provided delegate, depending on your design.
/// </remarks>
protected virtual void OnPressed()
{
}
/// <summary>Looks up the equivalent key to the gamepad button</summary>
/// <param name="button">
/// Gamepad button for which the equivalent key on the keyboard will be found
/// </param>
/// <returns>The key that is equivalent to the specified gamepad button</returns>
private static Keys KeyFromButton(Buttons button)
{
switch (button)
{
case Buttons.A:
{
return Keys.A;
}
case Buttons.B:
{
return Keys.B;
}
case Buttons.Back:
{
return Keys.Back;
}
case Buttons.LeftShoulder:
{
return Keys.L;
}
case Buttons.LeftStick:
{
return Keys.LeftControl;
}
case Buttons.RightShoulder:
{
return Keys.R;
}
case Buttons.RightStick:
{
return Keys.RightControl;
}
case Buttons.Start:
{
return Keys.Enter;
}
case Buttons.X:
{
return Keys.X;
}
case Buttons.Y:
{
return Keys.Y;
}
default:
{
return Keys.None;
}
}
}
}
}
@@ -1,9 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>Visual indicator for the progress of some operation</summary>
public class GuiProgressControl : GuiControl
{
/// <summary>The displayed progress in the range between 0.0 and 1.0</summary>
public float Progress;
}
}
@@ -1,18 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>Interface for controls which can obtain the input focus</summary>
/// <remarks>
/// Implement this interface in any control which can obtain the input focus.
/// </remarks>
public interface IFocusable
{
/// <summary>Whether the control can currently obtain the input focus</summary>
/// <remarks>
/// Usually returns true. For controls that can be disabled to disallow user
/// interaction, false can be returned to prevent the control from being
/// traversed when the user presses the tab key or uses the cursor / game pad
/// to select a control.
/// </remarks>
bool CanGetFocus { get; }
}
}
@@ -1,26 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>
/// Interface for controls that can be written into using the keyboard
/// </summary>
public interface IWritable : IFocusable
{
/// <summary>Title to be displayed in the on-screen keyboard</summary>
string GuideTitle { get; }
/// <summary>Description to be displayed in the on-screen keyboard</summary>
string GuideDescription { get; }
/// <summary>Text currently contained in the control</summary>
/// <remarks>
/// Called before the on-screen keyboard is displayed to get the text currently
/// contained in the control and after the on-screen keyboard has been
/// acknowledged to assign the edited text to the control
/// </remarks>
string Text { get; set; }
/// <summary>Called when the user has entered a character</summary>
/// <param name="character">Character that has been entered</param>
void OnCharacterEntered(char character);
}
}
@@ -1,178 +0,0 @@
using System;
using System.Collections.ObjectModel;
namespace MonoGame.Extended.NuclexGui.Controls
{
/// <summary>Collection of GUI controls</summary>
/// <remarks>
/// This class is for internal use only. Do not expose it to the user. If it was
/// exposed, the user might decide to use it for storing his own controls, causing
/// exceptions because the collection tries to parent the controls which are already
/// belonging to another collection.
/// </remarks>
internal class ParentingControlCollection : Collection<GuiControl>
{
/// <summary> Parent control to assign to all controls in this collection. </summary>
private readonly GuiControl _parent;
/// <summary> GUI this control is currently assigned to. Can be null. </summary>
private GuiScreen _screen;
/// <summary> Initializes a new parenting control collection. </summary>
/// <param name="parent">Parent control to assign to all children.</param>
public ParentingControlCollection(GuiControl parent)
{
_parent = parent;
}
/// <summary>Clears all elements from the collection</summary>
protected override void ClearItems()
{
for (var index = 0; index < Count; ++index)
UnassignParent(base[index]);
base.ClearItems();
}
/// <summary>Inserts a new element into the collection</summary>
/// <param name="index">Index at which to insert the element</param>
/// <param name="item">Item to be inserted</param>
protected override void InsertItem(int index, GuiControl item)
{
EnsureIntegrity(item);
base.InsertItem(index, item);
AssignParent(item);
}
/// <summary>Removes an element from the collection</summary>
/// <param name="index">Index of the element to remove</param>
protected override void RemoveItem(int index)
{
UnassignParent(base[index]);
base.RemoveItem(index);
}
/// <summary>Takes over a new element that is directly assigned</summary>
/// <param name="index">Index of the element that was assigned</param>
/// <param name="item">New item</param>
protected override void SetItem(int index, GuiControl item)
{
EnsureIntegrity(item);
UnassignParent(base[index]);
AssignParent(item);
}
/// <summary>Switches the control to a specific GUI</summary>
/// <param name="screen">Screen that owns the control from now on</param>
internal void SetScreen(GuiScreen screen)
{
_screen = screen;
for (var index = 0; index < Count; ++index)
base[index].SetScreen(screen);
}
/// <summary>Moves the specified control to the start of the list.</summary>
/// <param name="controlIndex">Index of the control that will be moved to the start of the list.</param>
internal void MoveToStart(int controlIndex)
{
var control = base[controlIndex];
// We explicitely circumvent the additional logic for adding and removing items
// in this collection since we're only relocating an item. Removal and readdition
// have no risk of causing an exception in a normal collection, otherwise the
// rollback attempt would be futile anyway since it would mean to repeat exactly
// what has caused failed: adding an item ;)
RemoveAt(controlIndex);
Insert(0, control);
}
/// <summary>Checks whether the provided name is already taken by a control.</summary>
/// <param name="name">Id that will be checked.</param>
/// <returns>True if the id is already taken; otherwise false.</returns>
internal bool IsNameTaken(string name)
{
// Empty names are an exception and will not be checked for duplicates.
if (name == null)
return false;
// Look for any controls with the provided name. This is a stupid sequential
// search, but given the typical number of controls in a Gui and the fact
// that this operation usually only happens once, there's no point in adding
// the overhead of managing a synchronized look-up dictionary here.
for (var index = 0; index < Count; ++index)
{
if (base[index].Name == name)
return true;
}
// If we reach this point, no control is using the specified name.
return false;
}
/// <summary>Gives up the parentage on the item provided.</summary>
/// <param name="item">Item to be unparented.</param>
private void AssignParent(GuiControl item)
{
item.SetParent(_parent);
}
/// <summary>Sets up the parentage on the specified item.</summary>
/// <param name="item">Item to be parented.</param>
private void UnassignParent(GuiControl item)
{
item.SetParent(null);
}
/// <summary>Determines whether the provided control is a parent of this control.</summary>
/// <param name="control">Control to check for parentage.</param>
/// <returns>True if the control is one of our parents; otherwise false.</returns>
/// <remarks>This method takes into account all ancestors up to the tree's root.</remarks>
private bool IsParent(GuiControl control)
{
var parent = _parent;
while (parent != null)
{
// Check if one of parents is control we are looking for
if (ReferenceEquals(parent, control))
return true;
// Walk upwards in the tree
parent = parent.Parent;
}
// Control is not in the tree
return false;
}
/// <summary>Ensures the integrity of the parent/child relationships.</summary>
/// <param name="proposedChild">Control that is to become one of our childs.</param>
private void EnsureIntegrity(GuiControl proposedChild)
{
// The item must not have a parent (otherwise, by being added to this collection,
// it would either be contained twice in the same collection or have two parents).
if (!ReferenceEquals(proposedChild.Parent, null))
throw new InvalidOperationException("Control already is the child of another control");
// The item must not become its own parent. I cannot imagine this ever happenning
// unless someone deliberately tried to crash the GUI library :)
if (ReferenceEquals(_parent, proposedChild))
throw new InvalidOperationException("Attempt to instate control as its own parent");
// The item also must not be any of our parent's parents (and so on). Otherwise,
// a stack overflow is likely to occur.
if (IsParent(proposedChild))
throw new InvalidOperationException("Attempt to instate one of the control's parents as its child");
// We also do not allow a child control to have the same id as an existing
// control (with the exception of an empty name)
if (IsNameTaken(proposedChild.Name))
{
throw new InvalidOperationException(
"The name of the added control has already been taken by another child");
}
}
}
}
@@ -1,390 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using MonoGame.Extended.NuclexGui.Input;
using MonoGame.Extended.NuclexGui.Visuals;
using MonoGame.Extended.NuclexGui.Visuals.Flat;
using GameEventHandler = System.EventHandler<System.EventArgs>;
namespace MonoGame.Extended.NuclexGui
{
// If an instance creates its own GUI visualizer (because the user didn't assign
// a custom one), it belongs to the instance and should be disposed. If the
// use does assign a custom visualizer, it shouldn't be disposed.
// But what if the user stores the current visualizer, then assigns a different
// one and assigns our's back?
public class GuiManager : IGameComponent, IUpdateable, IDrawable, IDisposable, IGuiService
{
/// <summary>Draw order rank relative to other game components</summary>
private int _drawOrder;
/// <summary>Game service container the GUI has registered itself in</summary>
private readonly GameServiceContainer _gameServices;
/// <summary>Graphics device servide the GUI uses</summary>
private IGraphicsDeviceService _graphicsDeviceService;
/// <summary>Draws the GUI</summary>
private IGuiVisualizer _guiVisualizer;
/// <summary>Captures user input for the XNA game</summary>
private IInputCapturer _inputCapturer;
/// <summary>Input service the GUI uses</summary>
private IGuiInputService _inputService;
/// <summary>The GUI screen representing the desktop</summary>
private GuiScreen _screen;
/// <summary>The IGuiVisualizer under its IUpdateable interface, if implemented</summary>
private IUpdateable _updateableGuiVisualizer;
/// <summary>The IInputCapturer under its IUpdateable interface, if implemented</summary>
private IUpdateable _updateableInputCapturer;
/// <summary>Update order rank relative to other game components</summary>
private int _updateOrder;
/// <summary>Whether the GUI should be drawn by Game.Draw()</summary>
private bool _visible = true;
/// <summary>Initializes a new GUI manager using the XNA service container</summary>
/// <param name="gameServices">
/// Game service container the GuiManager will register itself in and
/// to take the services it consumes from.
/// </param>
/// <param name="inputService">The input service used by the GUI</param>
public GuiManager(GameServiceContainer gameServices, IGuiInputService inputService)
{
_gameServices = gameServices;
_inputService = inputService;
gameServices.AddService(typeof(IGuiService), this);
// Do not look for the consumed services yet. XNA uses a two-stage
// initialization where in the first stage all managers register themselves
// before seeking out the services they use in the second stage, marked
// by a call to the Initialize() method.
}
/// <summary>Initializes a new GUI manager without using the XNA service container</summary>
/// <param name="graphicsDeviceService">Graphics device service the GUI will be rendered with</param>
/// <param name="inputService">Input service used to read data from the input devices</param>
/// <remarks>This constructor is provided for users of dependency injection frameworks.</remarks>
public GuiManager(IGraphicsDeviceService graphicsDeviceService, IGuiInputService inputService)
{
_graphicsDeviceService = graphicsDeviceService;
_inputService = inputService;
}
/// <summary>Initializes a new GUI manager using explicit services</summary>
/// <param name="gameServices">Game service container the GuiManager will register itself in</param>
/// <param name="graphicsDeviceService">Graphics device service the GUI will be rendered with</param>
/// <param name="inputService">Input service used to read data from the input devices</param>
/// <remarks>
/// This constructor is provided for users of dependency injection frameworks
/// or if you just want to be more explicit in stating which manager consumes
/// what services.
/// </remarks>
public GuiManager(GameServiceContainer gameServices, IGraphicsDeviceService graphicsDeviceService, IGuiInputService inputService)
: this(gameServices, inputService)
{
_graphicsDeviceService = graphicsDeviceService;
_inputService = inputService;
}
/// <summary>Input capturer that collects data from the input devices</summary>
/// <remarks>
/// The GuiManager will dispose its input capturer together with itself. If you
/// want to keep the input capturer, unset it before disposing the GuiManager.
/// If you want to replace the GuiManager's input capturer after it has constructed
/// the default one, you should dispose the GuiManager's default input capturer
/// after assigning your own.
/// </remarks>
public IInputCapturer InputCapturer
{
get { return _inputCapturer; }
set
{
if (!ReferenceEquals(value, _inputCapturer))
{
if (_inputCapturer != null)
_inputCapturer.InputReceiver = null;
_inputCapturer = value;
_updateableInputCapturer = value as IUpdateable;
if (_inputCapturer != null)
_inputCapturer.InputReceiver = _screen;
}
}
}
/// <summary>Immediately releases all resources used the GUI manager</summary>
public void Dispose()
{
// Unregister the service if we have registered it before
if (_gameServices != null)
{
var registeredService = _gameServices.GetService(typeof(IGuiService));
if (ReferenceEquals(registeredService, this))
_gameServices.RemoveService(typeof(IGuiService));
}
// Dispose the input capturer, if necessary
if (_inputCapturer != null)
{
var disposableInputCapturer = _inputCapturer as IDisposable;
disposableInputCapturer?.Dispose();
_updateableInputCapturer = null;
_inputCapturer = null;
}
// Dispose the GUI visualizer, if necessary
if (_guiVisualizer != null)
{
var disposableguiVisualizer = _guiVisualizer as IDisposable;
disposableguiVisualizer?.Dispose();
_updateableGuiVisualizer = null;
_guiVisualizer = null;
}
}
/// <summary>Fired when the DrawOrder property changes</summary>
public event GameEventHandler DrawOrderChanged;
/// <summary>Fired when the Visible property changes</summary>
public event GameEventHandler VisibleChanged;
/// <summary>Whether the GUI should be drawn during Game.Draw()</summary>
public bool Visible
{
get { return _visible; }
set
{
if (value != _visible)
{
_visible = value;
OnVisibleChanged();
}
}
}
/// <summary>
/// The order in which to draw this object relative to other objects. Objects
/// with a lower value are drawn first.
/// </summary>
public int DrawOrder
{
get { return _drawOrder; }
set
{
if (value != _drawOrder)
{
_drawOrder = value;
OnDrawOrderChanged();
}
}
}
/// <summary>Called when the drawable component needs to draw itself.</summary>
/// <param name="gameTime">Provides a snapshot of the game's timing values</param>
public void Draw(GameTime gameTime)
{
if (_guiVisualizer != null)
{
if (_screen != null)
_guiVisualizer.Draw(_screen);
}
}
/// <summary>Handles second-stage initialization of the GUI manager</summary>
public void Initialize()
{
// Set up a default input capturer if none was assigned by the user.
// We only require an IInputService if the user doesn't use a custom input
// capturer (which could be based on any other input library)
if (_inputCapturer == null)
{
if (_inputService == null)
_inputService = GetInputService(_gameServices);
//_inputCapturer = new Input.DefaultInputCapturer(_inputService);
_inputCapturer = new DefaultInputCapturer(_inputService);
// If a screen was assigned to the GUI before the input capturer was
// created, then the input capturer hasn't been given the screen as its
// input sink yet.
if (_screen != null)
_inputCapturer.InputReceiver = _screen;
}
// Set up a default GUI visualizer if none was assigned by the user.
// We only require an IGraphicsDeviceService if the user doesn't use a
// custom visualizer (which could be using any kind of rendering)
if (_guiVisualizer == null)
{
if (_graphicsDeviceService == null)
_graphicsDeviceService = GetGraphicsDeviceService(_gameServices);
// Use a private service container. We know exactly what will be loaded from
// the content manager our default GUI visualizer creates and if the user is
// being funny, the graphics device service passed to the constructor might
// be different from the one registered in the game service container.
var services = new GameServiceContainer();
services.AddService(typeof(IGraphicsDeviceService), _graphicsDeviceService);
Visualizer = FlatGuiVisualizer.FromResource(services,
"MonoGame.Extended.NuclexGui.Resources.Skins.SuaveSkin.json");
}
}
/// <summary>Visualizer that draws the GUI onto the screen</summary>
/// <remarks>
/// The GuiManager will dispose its visualizer together with itself. If you want
/// to keep the visualizer, unset it before disposing the GuiManager. If you want
/// to replace the GuiManager's visualizer after it has constructed the default
/// one, you should dispose the GuiManager's default visualizer after assigning
/// your own.
/// </remarks>
public IGuiVisualizer Visualizer
{
get { return _guiVisualizer; }
set
{
_guiVisualizer = value;
_updateableGuiVisualizer = value as IUpdateable;
}
}
/// <summary>GUI that is being rendered</summary>
/// <remarks>
/// The GUI manager renders one GUI full-screen onto the primary render target
/// (the backbuffer). This property holds the GUI that is being managed by
/// the GUI manager component. You can replace it at any time, for example,
/// if the player opens or closes your ingame menu.
/// </remarks>
public GuiScreen Screen
{
get { return _screen; }
set
{
_screen = value;
// Someone could assign the screen before the component is initialized.
// If that happens, do nothing here, the Initialize() method will take care
// of assigning the screen to the input capturer once it is called.
if (_inputCapturer != null)
_inputCapturer.InputReceiver = _screen;
}
}
/// <summary>Fired when the UpdateOrder property changes</summary>
public event GameEventHandler UpdateOrderChanged;
/// <summary>Fired when the enabled property changes, which is never</summary>
event GameEventHandler IUpdateable.EnabledChanged
{
add { }
remove { }
}
/// <summary>Whether the component should be updated during Game.Update()</summary>
bool IUpdateable.Enabled => true;
/// <summary>
/// Indicates when the game component should be updated relative to other game
/// components. Lower values are updated first.
/// </summary>
public int UpdateOrder
{
get { return _updateOrder; }
set
{
if (value != _updateOrder)
{
_updateOrder = value;
OnUpdateOrderChanged();
}
}
}
/// <summary>Called when the component needs to update its state.</summary>
/// <param name="gameTime">Provides a snapshot of the Game's timing values</param>
public void Update(GameTime gameTime)
{
_updateableInputCapturer?.Update(gameTime);
_updateableGuiVisualizer?.Update(gameTime);
}
/// <summary>Fires the UpdateOrderChanged event</summary>
protected void OnUpdateOrderChanged()
{
UpdateOrderChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>Fires the DrawOrderChanged event</summary>
protected void OnDrawOrderChanged()
{
DrawOrderChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>Fires the VisibleChanged event</summary>
protected void OnVisibleChanged()
{
VisibleChanged?.Invoke(this, EventArgs.Empty);
}
/// <summary>Retrieves the input service from a service provider</summary>
/// <param name="serviceProvider">Service provider the input service is retrieved from</param>
/// <returns>The retrieved input service</returns>
private static IGuiInputService GetInputService(IServiceProvider serviceProvider)
{
var inputService = (IGuiInputService) serviceProvider.GetService(typeof(IGuiInputService));
if (inputService == null)
{
throw new InvalidOperationException(
"Using the GUI with the default input capturer requires the IInputService. " +
"Please either add the IInputService to Game.Services by using the " +
"Nuclex.Input.InputManager in your game or provide a custom IInputCapturer " +
"implementation for the GUI and assign it before GuiManager.Initialize() " +
"is called."
);
}
return inputService;
}
/// <summary>Retrieves the graphics device service from a service provider</summary>
/// <param name="serviceProvider">Service provider the graphics device service is retrieved from</param>
/// <returns>The retrieved graphics device service</returns>
private static IGraphicsDeviceService GetGraphicsDeviceService(
IServiceProvider serviceProvider
)
{
var graphicsDeviceService =
(IGraphicsDeviceService) serviceProvider.GetService(typeof(IGraphicsDeviceService));
if (graphicsDeviceService == null)
{
throw new InvalidOperationException(
"Using the GUI with the default visualizer requires the IGraphicsDeviceService. " +
"Please either add an IGraphicsDeviceService to Game.Services by using " +
"XNA's GraphicsDeviceManager in your game or provide a custom " +
"IGuiVisualizer implementation for the GUI and assign it before " +
"GuiManager.Initialize() is called."
);
}
return graphicsDeviceService;
}
}
}
@@ -1,615 +0,0 @@
using System;
using System.Collections;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
using MonoGame.Extended.NuclexGui.Controls;
using MonoGame.Extended.NuclexGui.Input;
using MonoGame.Extended.NuclexGui.Support;
namespace MonoGame.Extended.NuclexGui
{
/// <summary>Manages the controls and their state on a GUI screen</summary>
/// <remarks>
/// This class manages the global state of a distinct user interface. Unlike your
/// typical GUI library, the Nuclex.UserInterface library can handle any number of
/// simultaneously active user interfaces at the same time, making the library
/// suitable for usage on virtual ingame computers and multi-client environments
/// such as split-screen games or switchable graphical terminals.
/// </remarks>
public class GuiScreen : IInputReceiver
{
/// <summary>Highest value in the Keys enumeration</summary>
private static readonly int _maxKeyboardKey =
(int) EnumHelper.GetHighestValue<Keys>();
/// <summary>Control responsible for hosting the GUI's top-level controls</summary>
private readonly GuiDesktopControl _desktopControl;
/// <summary>Child that currently has the input focus</summary>
/// <remarks>
/// If this field is non-null, all keyboard input sent to the Gui is handed
/// over to the focused control. Otherwise, keyboard input is discarded.
/// </remarks>
private readonly Support.WeakReference<GuiControl> _focusedControl;
/// <summary>Keys on the keyboard the user is currently holding down</summary>
private readonly BitArray _heldKeys;
/// <summary>Control the user has activated through one of the input devices</summary>
private GuiControl _activatedControl;
/// <summary>Buttons on the game pad the user is currently holding down</summary>
private Buttons _heldButtons;
/// <summary>Number of keys being held down on the keyboard</summary>
private int _heldKeyCount;
/// <summary>Mouse buttons currently being held down</summary>
private MouseButton _heldMouseButtons;
/// <summary>Size of the GUI area in world units or pixels</summary>
private Vector2 _size;
/// <summary>Initializes a new GUI</summary>
public GuiScreen() : this(0, 0)
{
}
/// <summary>Initializes a new GUI</summary>
/// <param name="width">Width of the area the GUI can occupy</param>
/// <param name="height">Height of the area the GUI can occupy</param>
/// <remarks>
/// Width and height should reflect the entire drawable area of your GUI. If you
/// want to limit the region which the GUI is allowed to use (eg. to only use the
/// safe area of a TV) please resize the desktop control accordingly!
/// </remarks>
public GuiScreen(float width, float height)
{
Width = width;
Height = height;
_heldKeys = new BitArray(_maxKeyboardKey + 1);
_heldButtons = 0;
// By default, the desktop control will cover the whole drawing area
_desktopControl = new GuiDesktopControl
{
Bounds = new UniRectangle(new UniVector(0, 0), new UniVector(new UniScalar(1, 0), new UniScalar(1, 0)))
};
_desktopControl.SetScreen(this);
_focusedControl = new Support.WeakReference<GuiControl>(null);
}
/// <summary>Width of the screen in pixels</summary>
public float Width
{
get { return _size.X; }
set { _size.X = value; }
}
/// <summary>Height of the screen in pixels</summary>
public float Height
{
get { return _size.Y; }
set { _size.Y = value; }
}
/// <summary>Control responsible for hosting the GUI's top-level controls</summary>
public GuiControl Desktop => _desktopControl;
/// <summary>
/// Whether any keys, mouse buttons or game pad buttons are beind held pressed
/// </summary>
private bool AnyKeysOrButtonsPressed => (_heldMouseButtons != 0) ||
(_heldKeyCount > 0) ||
(_heldButtons != 0);
/// <summary>Whether the GUI has currently captured the input devices</summary>
/// <remarks>
/// <para>
/// When you mix GUIs and gameplay (for example, in a strategy game where the GUI
/// manages the build menu and the remainder of the screen belongs to the game),
/// it is important to keep control of who currently owns the input devices.
/// </para>
/// <para>
/// Assume the player is drawing a selection rectangle around some units using
/// the mouse. He will press the mouse button outside any GUI elements, keep
/// holding it down and possibly drag over the GUI. Until the player lets go
/// of the mouse button, input exclusively belongs to the game. The same goes
/// vice versa, of course.
/// </para>
/// <para>
/// This property tells whether the GUI currently thinks that all input belongs
/// to it. If it is true, the game should not process any input. The GUI will
/// implement the input model as described here and respect the game's ownership
/// of the input devices if a mouse button is pressed outside of the GUI. To
/// correctly handle input device ownership, send all input to the GUI
/// regardless of this property's value, then check this property and if it
/// returns false let your game process the input.
/// </para>
/// </remarks>
public bool IsInputCaptured => _desktopControl.IsInputCaptured;
/// <summary>True if the mouse is currently hovering over any GUI elements</summary>
/// <remarks>
/// Useful if you mix gameplay with a GUI and use different mouse cursors
/// depending on the location of the mouse. As long as input is not captured
/// (see <see cref="IsInputCaptured" />) you can use this property to know
/// whether you should use the standard GUI mouse cursor or let your game
/// decide which cursor to use.
/// </remarks>
public bool IsMouseOverGui => _desktopControl.IsMouseOverGui;
/// <summary>Child control that currently has the input focus</summary>
public GuiControl FocusedControl
{
get
{
var current = _focusedControl.Target;
if ((current != null) && ReferenceEquals(current.Screen, this))
return current;
return null;
}
set
{
var current = _focusedControl.Target;
if (!ReferenceEquals(value, current))
{
_focusedControl.Target = value;
OnFocusChanged(value);
}
}
}
/// <summary>Injects a command into the processor</summary>
/// <param name="command">Input command that will be injected</param>
public void InjectCommand(Command command)
{
switch (command)
{
// Accept or cancel the current control
case Command.Accept:
case Command.Cancel:
{
var focusedControl = FocusedControl;
if (focusedControl == null)
return; // Also catches when focusedControl is not part of the tree
focusedControl.ProcessCommand(command);
break;
}
// Change focus to another control
case Command.SelectPrevious:
case Command.SelectNext:
{
break;
}
// Control specific. Changes focus if unhandled.
case Command.Up:
case Command.Down:
case Command.Left:
case Command.Right:
{
var focusedControl = FocusedControl;
if (focusedControl == null)
return; // Also catches when focusedControl is not part of the tree
// First send the command to the focused control. If the control handles
// the command, there's nothing for us to do. Otherwise, use the directional
// commands for focus switching.
if (focusedControl.ProcessCommand(command))
return;
// These will be determined in the following code block
var nearestDistance = float.NaN;
GuiControl nearestControl = null;
{
// Determine the center of the focused control
var parentBounds = focusedControl.Parent.GetAbsoluteBounds();
var focusedBounds = focusedControl.Bounds.ToOffset(
parentBounds.Width, parentBounds.Height
);
// Search all siblings of the focused control for the nearest control in the
// direction the command asks to move into
var siblings = focusedControl.Parent.Children;
foreach (var sibling in siblings)
{
// Only consider this sibling if it's focusable
if (!ReferenceEquals(sibling, focusedControl) && CanControlGetFocus(sibling))
{
var siblingBounds = sibling.Bounds.ToOffset(
parentBounds.Width, parentBounds.Height
);
// Calculate the distance the control has in the direction focus is being
// changed to. If the control doesn't lie in that direction, NaN will
// be returned
var distance = GetDirectionalDistance(
ref focusedBounds, ref siblingBounds, command
);
if (float.IsNaN(nearestDistance) || (distance < nearestDistance))
{
nearestControl = sibling;
nearestDistance = distance;
}
}
}
} // beauty scope
// Search completed, if we found a candidate, change focus to it
if (!float.IsNaN(nearestDistance))
FocusedControl = nearestControl;
break;
}
}
}
/// <summary>Called when a key on the keyboard has been pressed down</summary>
/// <param name="keyCode">Code of the key that was pressed</param>
public void InjectKeyPress(Keys keyCode)
{
var repetition = _heldKeys.Get((int) keyCode);
// If a control is activated, it will receive any input notifications
if (_activatedControl != null)
{
_activatedControl.ProcessKeyPress(keyCode, repetition);
if (!repetition)
{
++_heldKeyCount;
_heldKeys.Set((int) keyCode, true);
}
return;
}
// No control is activated, try the focused control before searching
// the entire tree for a responder.
var focusedControl = _focusedControl.Target;
if (focusedControl != null)
{
if (focusedControl.ProcessKeyPress(keyCode, false))
{
_activatedControl = focusedControl;
if (!repetition)
{
++_heldKeyCount;
_heldKeys.Set((int) keyCode, true);
}
return;
}
}
// Focused control didn't process the notification, now let the desktop
// control traverse the entire control tree is earch for a handler.
if (_desktopControl.ProcessKeyPress(keyCode, false))
{
_activatedControl = _desktopControl;
if (!repetition)
{
++_heldKeyCount;
_heldKeys.Set((int) keyCode, true);
}
}
else
{
switch (keyCode)
{
case Keys.Up:
{
InjectCommand(Command.Up);
break;
}
case Keys.Down:
{
InjectCommand(Command.Down);
break;
}
case Keys.Left:
{
InjectCommand(Command.Left);
break;
}
case Keys.Right:
{
InjectCommand(Command.Right);
break;
}
case Keys.Enter:
{
InjectCommand(Command.Accept);
break;
}
case Keys.Escape:
{
InjectCommand(Command.Cancel);
break;
}
}
}
}
/// <summary>Called when a key on the keyboard has been released again</summary>
/// <param name="keyCode">Code of the key that was released</param>
public void InjectKeyRelease(Keys keyCode)
{
if (!_heldKeys.Get((int) keyCode))
return;
--_heldKeyCount;
_heldKeys.Set((int) keyCode, false);
// If a control signed responsible for the earlier key press, it will now
// receive the release notification.
_activatedControl?.ProcessKeyRelease(keyCode);
// Reset the activated control if the user has released all buttons on all
// input devices.
if (!AnyKeysOrButtonsPressed)
_activatedControl = null;
}
/// <summary>Handle user text input by a physical or virtual keyboard</summary>
/// <param name="character">Character that has been entered</param>
public void InjectCharacter(char character)
{
// Send the text to the currently focused control in the GUI
var focusedControl = _focusedControl.Target;
var writable = focusedControl as IWritable;
writable?.OnCharacterEntered(character);
}
/// <summary>Called when a button on the gamepad has been pressed</summary>
/// <param name="button">Button that has been pressed</param>
public void InjectButtonPress(Buttons button)
{
var newHeldButtons = _heldButtons | button;
if (newHeldButtons == _heldButtons)
return;
_heldButtons = newHeldButtons;
// If a control is activated, it will receive any input notifications
if (_activatedControl != null)
{
_activatedControl.ProcessButtonPress(button);
return;
}
// No control is activated, try the focused control before searching
// the entire tree for a responder.
var focusedControl = _focusedControl.Target;
if (focusedControl != null)
{
if (focusedControl.ProcessButtonPress(button))
{
_activatedControl = focusedControl;
return;
}
}
// Focused control didn't process the notification, now let the desktop
// control traverse the entire control tree is earch for a handler.
if (_desktopControl.ProcessButtonPress(button))
_activatedControl = _desktopControl;
}
/// <summary>Called when a button on the gamepad has been released</summary>
/// <param name="button">Button that has been released</param>
public void InjectButtonRelease(Buttons button)
{
if ((_heldButtons & button) == 0)
return;
_heldButtons &= ~button;
// If a control signed responsible for the earlier button press, it will now
// receive the release notification.
_activatedControl?.ProcessButtonRelease(button);
// Reset the activated control if the user has released all buttons on all
// input devices.
if (!AnyKeysOrButtonsPressed)
_activatedControl = null;
}
/// <summary>Injects a mouse position update into the GUI</summary>
/// <param name="x">X coordinate of the mouse cursor within the screen</param>
/// <param name="y">Y coordinate of the mouse cursor within the screen</param>
public void InjectMouseMove(float x, float y)
{
_desktopControl.ProcessMouseMove(_size.X, _size.Y, x, y);
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
public void InjectMousePress(MouseButton button)
{
_heldMouseButtons |= button;
// If a control is activated, it will receive any input notifications
if (_activatedControl != null)
{
_activatedControl.ProcessMousePress(button);
return;
}
// No control was activated, so the desktop control becomes activated and
// is responsible for routing the input to the control under the mouse.
_activatedControl = _desktopControl;
_desktopControl.ProcessMousePress(button);
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
public void InjectMouseRelease(MouseButton button)
{
_heldMouseButtons &= ~button;
// If a control signed responsible for the earlier mouse press, it will now
// receive the release notification.
_activatedControl?.ProcessMouseRelease(button);
// Reset the activated control if the user has released all buttons on all
// input devices.
if (!AnyKeysOrButtonsPressed)
_activatedControl = null;
}
/// <summary>Called when the mouse wheel has been rotated</summary>
/// <param name="ticks">Number of ticks that the mouse wheel has been rotated</param>
public void InjectMouseWheel(float ticks)
{
if (_activatedControl != null)
_activatedControl.ProcessMouseWheel(ticks);
else
_desktopControl.ProcessMouseWheel(ticks);
}
/// <summary>Triggered when the control in focus changes</summary>
public event EventHandler<ControlEventArgs> FocusChanged;
/// <summary>Triggers the FocusChanged event</summary>
/// <param name="focusedControl">Control that has gotten the input focus</param>
private void OnFocusChanged(GuiControl focusedControl)
{
FocusChanged?.Invoke(this, new ControlEventArgs(focusedControl));
}
/// <summary>
/// Determines the distance of one rectangle to the other, also taking direction
/// into account
/// </summary>
/// <param name="ownBounds">Boundaries of the base rectangle</param>
/// <param name="otherBounds">Boundaries of the other rectangle</param>
/// <param name="direction">Direction into which distance will be determined</param>
/// <returns>
/// The direction of the other rectangle of NaN if it didn't lie in that direction
/// </returns>
private static float GetDirectionalDistance(
ref RectangleF ownBounds, ref RectangleF otherBounds, Command direction
)
{
float closestPointX, closestPointY;
float distance;
var isVertical =
(direction == Command.Up) ||
(direction == Command.Down);
if (isVertical)
{
var ownCenterX = ownBounds.X + ownBounds.Width/2.0f;
// Take an imaginary line through the other control's center, perpendicular
// to the specified direction. Then locate the closest point on that line
// to our own center.
closestPointX = Math.Min(Math.Max(ownCenterX, otherBounds.Left), otherBounds.Right);
closestPointY = otherBounds.Y + otherBounds.Height/2.0f;
// Find out whether we need to check the diagonal quadrant boundary
var leavesLeft = closestPointX < ownBounds.Left;
var leavesRight = closestPointX > ownBounds.Right;
//
float sideY;
if (direction == Command.Up)
{
sideY = ownBounds.Top;
if ((closestPointY > sideY) && (leavesLeft || leavesRight))
return float.NaN;
distance = sideY - closestPointY;
}
else
{
sideY = ownBounds.Bottom;
if ((closestPointY < sideY) && (leavesLeft || leavesRight))
return float.NaN;
distance = closestPointY - sideY;
}
var distanceY = Math.Abs(sideY - closestPointY);
if (leavesLeft)
{
var distanceX = Math.Abs(ownBounds.Left - closestPointX);
if (distanceX > distanceY)
return float.NaN;
}
else
{
if (leavesRight)
{
var distanceX = Math.Abs(closestPointX - ownBounds.Right);
if (distanceX > distanceY)
return float.NaN;
}
}
}
else
{
var ownCenterY = ownBounds.Y + ownBounds.Height/2.0f;
// Take an imaginary line through the other control's center, perpendicular
// to the specified direction. Then locate the closest point on that line
// to our own center.
closestPointX = otherBounds.X + otherBounds.Width/2.0f;
closestPointY = Math.Min(Math.Max(ownCenterY, otherBounds.Top), otherBounds.Bottom);
// Find out whether we need to check the diagonal quadrant boundary
var leavesTop = closestPointY < ownBounds.Top;
var leavesBottom = closestPointY > ownBounds.Bottom;
float sideX;
if (direction == Command.Left)
{
sideX = ownBounds.Left;
if ((closestPointX > sideX) && (leavesTop || leavesBottom))
return float.NaN;
distance = sideX - closestPointX;
}
else
{
sideX = ownBounds.Right;
if ((closestPointX < sideX) && (leavesTop || leavesBottom))
return float.NaN;
distance = closestPointX - sideX;
}
var distanceX = Math.Abs(sideX - closestPointX);
if (leavesTop)
{
var distanceY = Math.Abs(ownBounds.Top - closestPointY);
if (distanceY > distanceX)
return float.NaN;
}
else
{
if (leavesBottom)
{
var distanceY = Math.Abs(closestPointY - ownBounds.Bottom);
if (distanceY > distanceX)
return float.NaN;
}
}
}
return distance < 0.0f ? float.NaN : distance;
}
/// <summary>Determines whether a control can obtain the input focus</summary>
/// <param name="control">Control that will be checked for focusability</param>
/// <returns>True if the specified control can obtain the input focus</returns>
private static bool CanControlGetFocus(GuiControl control)
{
var focusableControl = control as IFocusable;
if (focusableControl != null)
return focusableControl.CanGetFocus;
return false;
}
}
}
@@ -1,28 +0,0 @@
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.NuclexGui
{
public interface IGuiInputService
{
KeyboardListener KeyboardListener { get; }
MouseListener MouseListener { get; }
GamePadListener GamePadListener { get; }
TouchListener TouchListener { get; }
}
public class GuiInputService : IGuiInputService
{
public GuiInputService(InputListenerComponent inputListener)
{
inputListener.Listeners.Add(KeyboardListener = new KeyboardListener());
inputListener.Listeners.Add(MouseListener = new MouseListener());
inputListener.Listeners.Add(GamePadListener = new GamePadListener());
inputListener.Listeners.Add(TouchListener = new TouchListener());
}
public KeyboardListener KeyboardListener { get; }
public MouseListener MouseListener { get; }
public GamePadListener GamePadListener { get; }
public TouchListener TouchListener { get; }
}
}
@@ -1,22 +0,0 @@
using MonoGame.Extended.NuclexGui.Visuals;
namespace MonoGame.Extended.NuclexGui
{
/// <summary>Game-wide interface for the GUI manager component</summary>
public interface IGuiService
{
/// <summary>GUI that is being rendered</summary>
/// <remarks>
/// The GUI manager renders one GUI full-screen onto the primary render target
/// (the backbuffer). This property holds the GUI that is being managed by
/// the GUI manager component. You can replace it at any time, for example,
/// if the player opens or closes your ingame menu.
/// </remarks>
GuiScreen Screen { get; set; }
/// <summary>
/// Responsible for creating a visual representation of the GUI on the screen
/// </summary>
IGuiVisualizer Visualizer { get; set; }
}
}
@@ -1,44 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Input
{
/// <summary>Input commands that can be sent to a control</summary>
/// <remarks>
/// <para>
/// The Nuclex GUI library is designed to work even when none of the usual
/// input devices are available. In this case, the entire GUI is controlled
/// through command keys, which might for example directly be linked to
/// the buttons of a gamepad.
/// </para>
/// <para>
/// It is, of course, still the responsibility of the developer to design
/// GUIs in a simple and easy to navigate style. When building GUIs that
/// are intended be used without a mouse, it is best not to use complex
/// controls like lists or text input boxes.
/// </para>
/// </remarks>
public enum Command
{
/// <summary>Accept the current selection (Ok button, Enter key)</summary>
Accept,
/// <summary>Cancel the current selection (Cancel button, Escape key)</summary>
Cancel,
/// <summary>Advance focus to the next control (Tab key)</summary>
SelectNext,
/// <summary>Advance focus to the previous control (Shift+Tab key)</summary>
SelectPrevious,
/// <summary>Go up or focus control above (Cursor Up key)</summary>
Up,
/// <summary>Go down or focus control below (Cursor Down key)</summary>
Down,
/// <summary>Go left or focus control left (Cursor Left key)</summary>
Left,
/// <summary>Go right or focus control right (Cursor Right key)</summary>
Right
}
}
@@ -1,278 +0,0 @@
using System;
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.NuclexGui.Input
{
public class DefaultInputCapturer : IInputCapturer, IDisposable
{
private bool _disposedValue; // To detect redundant calls
private readonly GamePadListener _gamePadListener;
/// <summary>Current receiver of input events</summary>
/// <remarks>
/// Always valid. If no input receiver is assigned, this field will be set
/// to a dummy receiver.
/// </remarks>
private IInputReceiver _inputReceiver;
/// <summary>Input service the capturer is currently subscribed to</summary>
private IGuiInputService _inputService;
private readonly KeyboardListener _keyboardListener;
private readonly MouseListener _mouseListener;
/// <summary>Initializes a new input capturer, taking the input service from a service provider</summary>
/// <param name="serviceProvider">Service provider the input capturer will take the input service from</param>
public DefaultInputCapturer(IServiceProvider serviceProvider) : this(GetInputService(serviceProvider))
{
}
/// <summary>Initializes a new input capturer using the specified input service</summary>
/// <param name="inputService">Input service the capturer will subscribe to</param>
public DefaultInputCapturer(IGuiInputService inputService)
{
_inputService = inputService;
_inputReceiver = new DummyInputReceiver();
_keyboardListener = inputService.KeyboardListener;
_mouseListener = inputService.MouseListener;
_gamePadListener = inputService.GamePadListener;
SubscribeInputDevices();
}
// ~MainInputCapturer() {
// // Do not change this code. Put cleanup code in Dispose(bool disposing) above.
// Dispose(false);
// }
// This code added to correctly implement the disposable pattern.
public void Dispose()
{
// Do not change this code. Put cleanup code in Dispose(bool disposing) above.
Dispose(true);
// GC.SuppressFinalize(this);
}
/// <summary>Input receiver any captured input will be sent to</summary>
public IInputReceiver InputReceiver
{
get
{
if (ReferenceEquals(_inputReceiver, DummyInputReceiver.Default))
return null;
return _inputReceiver;
}
set
{
if (value == null)
_inputReceiver = DummyInputReceiver.Default;
else
_inputReceiver = value;
}
}
protected virtual void Dispose(bool disposing)
{
if (!_disposedValue)
{
if (disposing)
{
if (_inputService != null)
{
UnsubscribeInputDevices();
_inputService = null;
}
}
_disposedValue = true;
}
}
private void SubscribeInputDevices()
{
_keyboardListener.KeyPressed += KeyboardListener_KeyPressed;
_keyboardListener.KeyReleased += KeyboardListener_KeyReleased;
_keyboardListener.KeyTyped += KeyboardListener_KeyTyped;
_mouseListener.MouseDown += MouseListener_MouseDown;
_mouseListener.MouseUp += MouseListener_MouseUp;
_mouseListener.MouseMoved += MouseListener_MouseMoved;
_mouseListener.MouseWheelMoved += MouseListener_MouseWheelMoved;
_gamePadListener.ButtonDown += GamePadListener_ButtonDown;
_gamePadListener.ButtonUp += GamePadListener_ButtonUp;
}
private void UnsubscribeInputDevices()
{
_keyboardListener.KeyPressed -= KeyboardListener_KeyPressed;
_keyboardListener.KeyReleased -= KeyboardListener_KeyReleased;
_keyboardListener.KeyTyped -= KeyboardListener_KeyTyped;
_mouseListener.MouseDown -= MouseListener_MouseDown;
_mouseListener.MouseUp -= MouseListener_MouseUp;
_mouseListener.MouseMoved -= MouseListener_MouseMoved;
_mouseListener.MouseWheelMoved -= MouseListener_MouseWheelMoved;
_gamePadListener.ButtonDown -= GamePadListener_ButtonDown;
_gamePadListener.ButtonUp -= GamePadListener_ButtonUp;
}
private void KeyboardListener_KeyPressed(object sender, KeyboardEventArgs e)
{
_inputReceiver.InjectKeyPress(e.Key);
}
private void KeyboardListener_KeyReleased(object sender, KeyboardEventArgs e)
{
_inputReceiver.InjectKeyRelease(e.Key);
}
private void KeyboardListener_KeyTyped(object sender, KeyboardEventArgs e)
{
_inputReceiver.InjectCharacter(e.Character.GetValueOrDefault());
}
private void MouseListener_MouseDown(object sender, MouseEventArgs e)
{
_inputReceiver.InjectMousePress(e.Button);
}
private void MouseListener_MouseUp(object sender, MouseEventArgs e)
{
_inputReceiver.InjectMouseRelease(e.Button);
}
private void MouseListener_MouseMoved(object sender, MouseEventArgs e)
{
_inputReceiver.InjectMouseMove(e.Position.X, e.Position.Y);
}
private void MouseListener_MouseWheelMoved(object sender, MouseEventArgs e)
{
_inputReceiver.InjectMouseWheel(e.ScrollWheelDelta);
}
private void GamePadListener_ButtonDown(object sender, GamePadEventArgs e)
{
if ((e.Button & Buttons.DPadUp) != 0)
_inputReceiver.InjectCommand(Command.Up);
else
{
if ((e.Button & Buttons.DPadDown) != 0)
_inputReceiver.InjectCommand(Command.Down);
else
{
if ((e.Button & Buttons.DPadLeft) != 0)
_inputReceiver.InjectCommand(Command.Left);
else
{
if ((e.Button & Buttons.DPadRight) != 0)
_inputReceiver.InjectCommand(Command.Right);
else
_inputReceiver.InjectButtonPress(e.Button);
}
}
}
}
private void GamePadListener_ButtonUp(object sender, GamePadEventArgs e)
{
_inputReceiver.InjectButtonRelease(e.Button);
}
/// <summary>Retrieves the input service from a service provider</summary>
/// <param name="serviceProvider">
/// Service provider the service is taken from
/// </param>
/// <returns>The input service stored in the service provider</returns>
private static IGuiInputService GetInputService(IServiceProvider serviceProvider)
{
var inputService = (IGuiInputService) serviceProvider.GetService(typeof(IGuiInputService));
if (inputService == null)
{
throw new InvalidOperationException(
"Using the GUI with the DefaultInputCapturer requires the IInputService. " +
"Please either add the IInputService to Game.Services by using the " +
"Nuclex.Input.InputManager in your game or provide a custom IInputCapturer " +
"implementation for the GUI and assign it before GuiManager.Initialize() " +
"is called."
);
}
return inputService;
}
/// <summary>Dummy receiver for input events</summary>
private class DummyInputReceiver : IInputReceiver
{
/// <summary>Default instance of the dummy receiver</summary>
public static readonly DummyInputReceiver Default = new DummyInputReceiver();
/// <summary>Injects an input command into the input receiver</summary>
/// <param name="command">Input command to be injected</param>
public void InjectCommand(Command command)
{
}
/// <summary>Called when a button on the gamepad has been pressed</summary>
/// <param name="button">Button that has been pressed</param>
public void InjectButtonPress(Buttons button)
{
}
/// <summary>Called when a button on the gamepad has been released</summary>
/// <param name="button">Button that has been released</param>
public void InjectButtonRelease(Buttons button)
{
}
/// <summary>Injects a mouse position update into the receiver</summary>
/// <param name="x">New X coordinate of the mouse cursor on the screen</param>
/// <param name="y">New Y coordinate of the mouse cursor on the screen</param>
public void InjectMouseMove(float x, float y)
{
}
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
public void InjectMousePress(MouseButton button)
{
}
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
public void InjectMouseRelease(MouseButton button)
{
}
/// <summary>Called when the mouse wheel has been rotated</summary>
/// <param name="ticks">Number of ticks that the mouse wheel has been rotated</param>
public void InjectMouseWheel(float ticks)
{
}
/// <summary>Called when a key on the keyboard has been pressed down</summary>
/// <param name="keyCode">Code of the key that was pressed</param>
public void InjectKeyPress(Keys keyCode)
{
}
/// <summary>Called when a key on the keyboard has been released again</summary>
/// <param name="keyCode">Code of the key that was released</param>
public void InjectKeyRelease(Keys keyCode)
{
}
/// <summary>Handle user text input by a physical or virtual keyboard</summary>
/// <param name="character">Character that has been entered</param>
public void InjectCharacter(char character)
{
}
}
}
}
@@ -1,12 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Input
{
/// <summary>
/// Interface for input capturers that monitor user input and forward it to
/// a freely settable input receiver
/// </summary>
public interface IInputCapturer
{
/// <summary>Input receiver any captured input will be sent to</summary>
IInputReceiver InputReceiver { get; set; }
}
}
@@ -1,83 +0,0 @@
using Microsoft.Xna.Framework.Input;
using MonoGame.Extended.Input;
using MonoGame.Extended.Input.InputListeners;
namespace MonoGame.Extended.NuclexGui.Input
{
/// <summary>Interface for classes that can process user input</summary>
/// <remarks>
/// This interface is implemented by any class that can process user input.
/// Normally, user input is directly fed into the <see cref="GuiScreen" /> class
/// which manages the global state of an isolated GUI system. It is also possible,
/// though not recommended, to use this interface for sending input directly
/// to a control, for example, to simulate text input for an input box.
/// </remarks>
public interface IInputReceiver
{
/// <summary>Injects an input command into the input receiver</summary>
/// <param name="command">Input command to be injected</param>
/// <remarks>
/// <para>
/// If the GUI is run without the usual GUI input methods (eg. when a GUI is
/// displayed on a gaming console), this is the sole way to feed input to
/// the controls.
/// </para>
/// <para>
/// By default, normal key presses will generate a command in addition to the
/// KeyPress itself, so unless a control does something very special, it
/// should respond to this method only and leave the KeyPress method alone ;)
/// </para>
/// </remarks>
void InjectCommand(Command command);
/// <summary>Called when a button on the gamepad has been pressed</summary>
/// <param name="button">Button that has been pressed</param>
void InjectButtonPress(Buttons button);
/// <summary>Called when a button on the gamepad has been released</summary>
/// <param name="button">Button that has been released</param>
void InjectButtonRelease(Buttons button);
/// <summary>Injects a mouse position update into the receiver</summary>
/// <param name="x">New X coordinate of the mouse cursor on the screen</param>
/// <param name="y">New Y coordinate of the mouse cursor on the screen</param>
/// <remarks>
/// When the mouse leaves the valid region (eg. if the game runs in windowed mode
/// and the mouse cursor is moved outside of the window), a final mouse move
/// notification is generated with the coordinates -1, -1
/// </remarks>
void InjectMouseMove(float x, float y);
/// <summary>Called when a mouse button has been pressed down</summary>
/// <param name="button">Index of the button that has been pressed</param>
void InjectMousePress(MouseButton button);
/// <summary>Called when a mouse button has been released again</summary>
/// <param name="button">Index of the button that has been released</param>
void InjectMouseRelease(MouseButton button);
/// <summary>Called when the mouse wheel has been rotated</summary>
/// <param name="ticks">Number of ticks that the mouse wheel has been rotated</param>
void InjectMouseWheel(float ticks);
/// <summary>Called when a key on the keyboard has been pressed down</summary>
/// <param name="keyCode">Code of the key that was pressed</param>
/// <remarks>
/// Only handle this if you need it for some special purpose. For standard commands
/// like confirmation and cancellation, simply respond to InjectCommand()
/// </remarks>
void InjectKeyPress(Keys keyCode);
/// <summary>Called when a key on the keyboard has been released again</summary>
/// <param name="keyCode">Code of the key that was released</param>
/// <remarks>
/// Only handle this if you need it for some special purpose. For standard commands
/// like confirmation and cancellation, simply respond to InjectCommand()
/// </remarks>
void InjectKeyRelease(Keys keyCode);
/// <summary>Handle user text input by a physical or virtual keyboard</summary>
/// <param name="character">Character that has been entered</param>
void InjectCharacter(char character);
}
}
@@ -1,36 +0,0 @@
<Project Sdk="Microsoft.NET.Sdk">
<PropertyGroup>
<TargetFramework>netstandard2.0</TargetFramework>
</PropertyGroup>
<PropertyGroup>
<Authors>craftworkgames</Authors>
<Description>A port of the Nuclex GUI system (https://nuclexframework.codeplex.com/) to make MonoGame more awesome.</Description>
<PackageTags>monogame nuclex gui</PackageTags>
<PackageProjectUrl>https://github.com/craftworkgames/MonoGame.Extended</PackageProjectUrl>
<RepositoryUrl>https://github.com/craftworkgames/MonoGame.Extended</RepositoryUrl>
<PackageIconUrl>https://raw.githubusercontent.com/craftworkgames/MonoGame.Extended/master/Logos/logo-nuget-128.png</PackageIconUrl>
<GenerateDocumentationFile>true</GenerateDocumentationFile>
</PropertyGroup>
<PropertyGroup>
<NoWarn>NU1701</NoWarn>
</PropertyGroup>
<ItemGroup>
<ProjectReference Include="..\MonoGame.Extended.Input\MonoGame.Extended.Input.csproj" />
<ProjectReference Include="..\MonoGame.Extended\MonoGame.Extended.csproj" />
</ItemGroup>
<ItemGroup>
<EmbeddedResource Include="Resources\Skins\SuaveSkin.json">
<CopyToOutputDirectory>PreserveNewest</CopyToOutputDirectory>
</EmbeddedResource>
</ItemGroup>
<ItemGroup>
<PackageReference Include="MonoGame.Framework.DesktopGL" Version="3.8.0.1641">
<PrivateAssets>All</PrivateAssets>
</PackageReference>
</ItemGroup>
</Project>
File diff suppressed because it is too large Load Diff
@@ -1,74 +0,0 @@
using System;
namespace MonoGame.Extended.NuclexGui.Support
{
/// <summary>Helper methods for enumerations</summary>
public static class EnumHelper
{
/// <summary>Returns the highest value encountered in an enumeration</summary>
/// <typeparam name="T">
/// Enumeration of which the highest value will be returned
/// </typeparam>
/// <returns>The highest value in the enumeration</returns>
public static T GetHighestValue<T>() where T : IComparable
{
var values = GetValues<T>();
// If the enumeration is empty, return nothing
if (values.Length == 0)
return default(T);
// Look for the highest value in the enumeration. We initialize the highest value
// to the first enumeration value so we don't have to use some arbitrary starting
// value which might actually appear in the enumeration.
var highestValue = values[0];
for (var index = 1; index < values.Length; ++index)
{
if (values[index].CompareTo(highestValue) > 0)
highestValue = values[index];
}
return highestValue;
}
/// <summary>Returns the lowest value encountered in an enumeration</summary>
/// <typeparam name="T">
/// Enumeration of which the lowest value will be returned
/// </typeparam>
/// <returns>The lowest value in the enumeration</returns>
public static T GetLowestValue<T>() where T : IComparable
{
var values = GetValues<T>();
// If the enumeration is empty, return nothing
if (values.Length == 0)
return default(T);
// Look for the lowest value in the enumeration. We initialize the lowest value
// to the first enumeration value so we don't have to use some arbitrary starting
// value which might actually appear in the enumeration.
var lowestValue = values[0];
for (var index = 1; index < values.Length; ++index)
{
if (values[index].CompareTo(lowestValue) < 0)
lowestValue = values[index];
}
return lowestValue;
}
/// <summary>Retrieves a list of all values contained in an enumeration</summary>
/// <typeparam name="T">
/// Type of the enumeration whose values will be returned
/// </typeparam>
/// <returns>All values contained in the specified enumeration</returns>
/// <remarks>
/// This method produces collectable garbage so it's best to only call it once
/// and cache the result.
/// </remarks>
public static T[] GetValues<T>()
{
return (T[]) Enum.GetValues(typeof(T));
}
}
}
@@ -1,70 +0,0 @@
using System;
namespace MonoGame.Extended.NuclexGui.Support
{
/// <summary>
/// Type-safe weak reference, referencing an object while still allowing
/// that object to be garbage collected.
/// </summary>
public class WeakReference<TReferencedType> : WeakReference
where TReferencedType : class
{
/// <summary>
/// Initializes a new instance of the WeakReference class, referencing
/// the specified object.
/// </summary>
/// <param name="target">The object to track or null.</param>
public WeakReference(TReferencedType target) :
base(target)
{
}
/// <summary>
/// Initializes a new instance of the WeakReference class, referencing
/// the specified object optionally using resurrection tracking.
/// </summary>
/// <param name="target">An object to track.</param>
/// <param name="trackResurrection">
/// Indicates when to stop tracking the object. If true, the object is tracked
/// after finalization; if false, the object is only tracked until finalization.
/// </param>
public WeakReference(TReferencedType target, bool trackResurrection) :
base(target, trackResurrection)
{
}
/// <summary>
/// Initializes a new instance of the WeakReference class, using deserialized
/// data from the specified serialization and stream objects.
/// </summary>
/// <param name="info">
/// An object that holds all the data needed to serialize or deserialize the
/// current System.WeakReference object.
/// </param>
/// <param name="context">
/// (Reserved) Describes the source and destination of the serialized stream
/// specified by info.
/// </param>
/// <exception cref="System.ArgumentNullException">
/// The info parameter is null.
/// </exception>
/// <summary>
/// Gets or sets the object (the target) referenced by the current WeakReference
/// object.
/// </summary>
/// <remarks>
/// Is null if the object referenced by the current System.WeakReference object
/// has been garbage collected; otherwise, a reference to the object referenced
/// by the current System.WeakReference object.
/// </remarks>
/// <exception cref="System.InvalidOperationException">
/// The reference to the target object is invalid. This can occur if the current
/// System.WeakReference object has been finalized
/// </exception>
public new TReferencedType Target
{
get { return base.Target as TReferencedType; }
set { base.Target = value; }
}
}
}
@@ -1,196 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.NuclexGui
{
/// <summary>Two-dimensional rectangle of combined fraction and offset coordinates</summary>
public struct UniRectangle
{
/// <summary>An empty unified rectangle</summary>
public static readonly UniRectangle Empty = new UniRectangle();
/// <summary>X coordinate of the rectangle's left border</summary>
public UniScalar Left
{
get { return Location.X; }
set { Location.X = value; }
}
/// <summary>Y coordinate of the rectangle's upper border</summary>
public UniScalar Top
{
get { return Location.Y; }
set { Location.Y = value; }
}
/// <summary>X coordinate of the rectangle's right border</summary>
public UniScalar Right
{
get { return Location.X + Size.X; }
set { Size.X = value - Location.X; }
}
/// <summary>Y coordinate of the rectangle's lower border</summary>
public UniScalar Bottom
{
get { return Location.Y + Size.Y; }
set { Size.Y = value - Location.Y; }
}
/// <summary>Point consisting of the lesser coordinates of the rectangle</summary>
public UniVector Min
{
get { return Location; }
set
{
// In short: this.Size += this.Location - value;
// Done for performance reasons
Size.X.Fraction += Location.X.Fraction - value.X.Fraction;
Size.X.Offset += Location.X.Offset - value.X.Offset;
Size.Y.Fraction += Location.Y.Fraction - value.Y.Fraction;
Size.Y.Offset += Location.Y.Offset - value.Y.Offset;
Location = value;
}
}
/// <summary>Point consisting of the greater coordinates of the rectangle</summary>
public UniVector Max
{
get { return Location + Size; }
set
{
// In short: this.Size = value - this.Location;
// Done for performance reasons
Size.X.Fraction = value.X.Fraction - Location.X.Fraction;
Size.X.Offset = value.X.Offset - Location.X.Offset;
Size.Y.Fraction = value.Y.Fraction - Location.X.Fraction;
Size.Y.Offset = value.Y.Offset - Location.Y.Offset;
}
}
/// <summary>The location of the rectangle's upper left corner</summary>
public UniVector Location;
/// <summary>The size of the rectangle</summary>
public UniVector Size;
/// <summary>Initializes a new rectangle from a location and a size</summary>
/// <param name="location">Location of the rectangle's upper left corner</param>
/// <param name="size">Size of the area covered by the rectangle</param>
public UniRectangle(UniVector location, UniVector size)
{
Location = location;
Size = size;
}
/// <summary>Initializes a new rectangle from the provided individual coordinates</summary>
/// <param name="x">X coordinate of the rectangle's left border</param>
/// <param name="y">Y coordinate of the rectangle's upper border</param>
/// <param name="width">Width of the area covered by the rectangle</param>
/// <param name="height">Height of the area covered by the rectangle</param>
public UniRectangle(UniScalar x, UniScalar y, UniScalar width, UniScalar height)
{
Location = new UniVector(x, y);
Size = new UniVector(width, height);
}
/// <summary>Converts the rectangle into pure offset coordinates</summary>
/// <param name="containerSize">Dimensions of the container the fractional part of the rectangle count for</param>
/// <returns>A rectangle with the pure offset coordinates of the rectangle</returns>
public RectangleF ToOffset(Vector2 containerSize)
{
return ToOffset(containerSize.X, containerSize.Y);
}
/// <summary>Converts the rectangle into pure offset coordinates</summary>
/// <param name="containerWidth">Width of the container the fractional part of the rectangle counts for</param>
/// <param name="containerHeight">Height of the container the fractional part of the rectangle counts for</param>
/// <returns>A rectangle with the pure offset coordinates of the rectangle</returns>
public RectangleF ToOffset(float containerWidth, float containerHeight)
{
var leftOffset = Left.Fraction*containerWidth + Left.Offset;
var topOffset = Top.Fraction*containerHeight + Top.Offset;
return new RectangleF(
leftOffset,
topOffset,
Right.Fraction*containerWidth + Right.Offset - leftOffset,
Bottom.Fraction*containerHeight + Bottom.Offset - topOffset
);
}
/// <summary>Checks two rectangles for inequality</summary>
/// <param name="first">First rectangle to be compared</param>
/// <param name="second">Second rectangle to be compared</param>
/// <returns>True if the instances differ or exactly one reference is set to null</returns>
public static bool operator !=(UniRectangle first, UniRectangle second)
{
return !(first == second);
}
/// <summary>Checks two rectangles for equality</summary>
/// <param name="first">First rectangle to be compared</param>
/// <param name="second">Second rectangle to be compared</param>
/// <returns>True if both instances are equal or both references are null</returns>
public static bool operator ==(UniRectangle first, UniRectangle second)
{
// For a struct, neither 'first' nor 'second' can be null
return first.Equals(second);
}
/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public override bool Equals(object other)
{
if (!(other is UniRectangle))
return false;
return Equals((UniRectangle) other);
}
/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public bool Equals(UniRectangle other)
{
// For a struct, 'other' cannot be null
return (Location == other.Location) && (Size == other.Size);
}
/// <summary>Obtains a hash code of this instance</summary>
/// <returns>The hash code of the instance</returns>
public override int GetHashCode()
{
return Location.GetHashCode() ^ Size.GetHashCode();
}
/// <summary>
/// Returns a human-readable string representation for the unified rectangle
/// </summary>
/// <returns>The human-readable string representation of the unified rectangle</returns>
public override string ToString()
{
return string.Format(
"{{Location:{0}, Size:{1}}}",
Location,
Size
);
}
/// <summary>
/// Moves unified rectangle by the absolute values
/// </summary>
public void AbsoluteOffset(float x, float y)
{
Location.X.Offset += x;
Location.Y.Offset += y;
}
public void FractionalOffset(float x, float y)
{
Location.X.Fraction += x;
Location.Y.Fraction += y;
}
}
}
@@ -1,191 +0,0 @@
namespace MonoGame.Extended.NuclexGui
{
/// <summary>Stores a size or location on one axis</summary>
/// <remarks>
/// <para>
/// Any position or size in GUI uses a combined position consisting
/// of a fraction and an offset. The fraction specifies the position or size as a
/// fraction of the parent frame's bounds and usually is in the range between 0.0 and
/// 1.0. The offset simply is the number of pixels to divert from the thusly
/// determined location.
/// </para>
/// <para>
/// Through the use of both fraction and offset, any kind of anchoring behavior can be
/// achieved that normally would require a complex anchoring and docking system as can
/// be seen in System.Windows.Forms.
/// </para>
/// <para>
/// If you, for example, wanted to always place a control 20 pixels from the right
/// border of its parent container, set the fraction of its position to 1.0 (always
/// on the right border) and the offset to -20.0 (go 20 units to the left from there).
/// </para>
/// <para>
/// You can achieve traditional absolute positioning by leaving the fraction at 0.0,
/// which is equivalent to the upper or left border of the parent container.
/// </para>
/// </remarks>
public struct UniScalar
{
/// <summary>A scalar that has been initialized to zero</summary>
public static readonly UniScalar Zero = new UniScalar();
/// <summary>Position of the scalar as fraction of the parent frame's bounds</summary>
/// <remarks>
/// The relative part is normally in the 0.0 .. 1.0 range, denoting the
/// fraction of the parent container's size the scalar will indicate.
/// </remarks>
public float Fraction;
/// <summary>Offset of the scalar in pixels relative to its fractional position</summary>
/// <remarks>
/// This part is taken literally without paying attention to the size of
/// the parent container the coordinate is used in.
/// </remarks>
public float Offset;
/// <summary>Initializes a new scalar from an offset only</summary>
/// <param name="offset">Offset in pixels this scalar indicates</param>
public UniScalar(float offset) : this(0.0f, offset)
{
}
/// <summary>Initializes a new dimension from an absolute and a relative part</summary>
/// <param name="fraction">Fractional position within the parent frame</param>
/// <param name="offset">Offset in pixels from the fractional position</param>
public UniScalar(float fraction, float offset)
{
Fraction = fraction;
Offset = offset;
}
/// <summary>Implicitely constructs a scalar using a float as the absolute part</summary>
/// <param name="offset">Float that will be used for the scalar's absolute value</param>
/// <returns>
/// A new scalar constructed with the original float as its absolute part
/// </returns>
public static implicit operator UniScalar(float offset)
{
return new UniScalar(offset);
}
/// <summary>Converts the scalar into a pure offset position</summary>
/// <param name="containerSize">
/// Absolute dimension of the parent that the relative coordinate relates to
/// </param>
/// <returns>
/// The absolute position in the parent container denoted by the dimension
/// </returns>
public float ToOffset(float containerSize)
{
return Fraction*containerSize + Offset;
}
/// <summary>Adds one scalar to another</summary>
/// <param name="scalar">Base scalar to add to</param>
/// <param name="summand">Scalar to add to the base</param>
/// <returns>The result of the addition</returns>
public static UniScalar operator +(UniScalar scalar, UniScalar summand)
{
return new UniScalar(
scalar.Fraction + summand.Fraction,
scalar.Offset + summand.Offset
);
}
/// <summary>Subtracts one scalar from another</summary>
/// <param name="scalar">Base scalar to subtract from</param>
/// <param name="subtrahend">Scalar to subtract from the base</param>
/// <returns>The result of the subtraction</returns>
public static UniScalar operator -(UniScalar scalar, UniScalar subtrahend)
{
return new UniScalar(
scalar.Fraction - subtrahend.Fraction,
scalar.Offset - subtrahend.Offset
);
}
/// <summary>Divides one scalar by another</summary>
/// <param name="scalar">Base scalar to be divided</param>
/// <param name="divisor">Divisor to divide by</param>
/// <returns>The result of the division</returns>
public static UniScalar operator /(UniScalar scalar, UniScalar divisor)
{
return new UniScalar(
scalar.Fraction/divisor.Fraction,
scalar.Offset/divisor.Offset
);
}
/// <summary>Multiplies one scalar with another</summary>
/// <param name="scalar">Base scalar to be multiplied</param>
/// <param name="factor">Factor to multiply by</param>
/// <returns>The result of the multiplication</returns>
public static UniScalar operator *(UniScalar scalar, UniScalar factor)
{
return new UniScalar(
scalar.Fraction*factor.Fraction,
scalar.Offset*factor.Offset
);
}
/// <summary>Checks two scalars for inequality</summary>
/// <param name="first">First scalar to be compared</param>
/// <param name="second">Second scalar to be compared</param>
/// <returns>True if the instances differ or exactly one reference is set to null</returns>
public static bool operator !=(UniScalar first, UniScalar second)
{
return !(first == second);
}
/// <summary>Checks two scalars for equality</summary>
/// <param name="first">First scalar to be compared</param>
/// <param name="second">Second scalar to be compared</param>
/// <returns>True if both instances are equal or both references are null</returns>
public static bool operator ==(UniScalar first, UniScalar second)
{
// For a struct, neither 'first' nor 'second' can be null
return first.Equals(second);
}
/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public override bool Equals(object other)
{
if (!(other is UniScalar))
return false;
return Equals((UniScalar) other);
}
/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public bool Equals(UniScalar other)
{
// For a struct, 'other' cannot be null
return (Fraction == other.Fraction) && (Offset == other.Offset);
}
/// <summary>Obtains a hash code of this instance</summary>
/// <returns>The hash code of the instance</returns>
public override int GetHashCode()
{
return Fraction.GetHashCode() ^ Offset.GetHashCode();
}
/// <summary>
/// Returns a human-readable string representation for the unified scalar
/// </summary>
/// <returns>The human-readable string representation of the unified scalar</returns>
public override string ToString()
{
return string.Format(
"{{{0}% {1}{2}}}",
Fraction*100.0f,
Offset >= 0.0f ? "+" : string.Empty,
Offset
);
}
}
}
@@ -1,165 +0,0 @@
using Microsoft.Xna.Framework;
namespace MonoGame.Extended.NuclexGui
{
/// <summary>Stores a two-dimensional position or size</summary>
public struct UniVector
{
/// <summary>A vector that has been initialized to zero</summary>
public static readonly UniVector Zero = new UniVector();
/// <summary>The vector's X coordinate</summary>
public UniScalar X;
/// <summary>The vector's Y coordinate</summary>
public UniScalar Y;
/// <summary>Initializes a new vector from the provided components</summary>
/// <param name="x">Absolute and relative X coordinate of the vector</param>
/// <param name="y">Absolute and relative Y coordinate of the vector</param>
public UniVector(UniScalar x, UniScalar y)
{
X = x;
Y = y;
}
/// <summary>Converts the vector into pure offset coordinates</summary>
/// <param name="containerSize">
/// Dimensions of the container the relative part of the vector counts for
/// </param>
/// <returns>An XNA vector with the pure offset coordinates of the vector</returns>
public Vector2 ToOffset(Vector2 containerSize)
{
return ToOffset(containerSize.X, containerSize.Y);
}
/// <summary>Converts the vector into pure offset coordinates</summary>
/// <param name="containerWidth">
/// Width of the container the fractional part of the vector counts for
/// </param>
/// <param name="containerHeight">
/// Height of the container the fractional part of the vector counts for
/// </param>
/// <returns>An XNA vector with the pure offset coordinates of the vector</returns>
public Vector2 ToOffset(float containerWidth, float containerHeight)
{
return new Vector2(
X.Fraction*containerWidth + X.Offset,
Y.Fraction*containerHeight + Y.Offset
);
}
/// <summary>Adds one vector to another</summary>
/// <param name="vector">Base vector to add to</param>
/// <param name="summand">Vector to add to the base</param>
/// <returns>The result of the addition</returns>
public static UniVector operator +(UniVector vector, UniVector summand)
{
return new UniVector(vector.X + summand.X, vector.Y + summand.Y);
}
/// <summary>Subtracts one vector from another</summary>
/// <param name="vector">Base vector to subtract from</param>
/// <param name="subtrahend">Vector to subtract from the base</param>
/// <returns>The result of the subtraction</returns>
public static UniVector operator -(UniVector vector, UniVector subtrahend)
{
return new UniVector(vector.X - subtrahend.X, vector.Y - subtrahend.Y);
}
/// <summary>Divides one vector by another</summary>
/// <param name="vector">Base vector to be divided</param>
/// <param name="divisor">Divisor to divide by</param>
/// <returns>The result of the division</returns>
public static UniVector operator /(UniVector vector, UniVector divisor)
{
return new UniVector(vector.X/divisor.X, vector.Y/divisor.Y);
}
/// <summary>Multiplies one vector with another</summary>
/// <param name="vector">Base vector to be multiplied</param>
/// <param name="factor">Factor to multiply by</param>
/// <returns>The result of the multiplication</returns>
public static UniVector operator *(UniVector vector, UniVector factor)
{
return new UniVector(vector.X*factor.X, vector.Y*factor.Y);
}
/// <summary>Scales a vector by a scalar factor</summary>
/// <param name="factor">Factor by which to scale the vector</param>
/// <param name="vector">Vector to be Scaled</param>
/// <returns>The result of the multiplication</returns>
public static UniVector operator *(UniScalar factor, UniVector vector)
{
return new UniVector(factor*vector.X, factor*vector.Y);
}
/// <summary>Scales a vector by a scalar factor</summary>
/// <param name="vector">Vector to be Scaled</param>
/// <param name="factor">Factor by which to scale the vector</param>
/// <returns>The result of the multiplication</returns>
public static UniVector operator *(UniVector vector, UniScalar factor)
{
return new UniVector(vector.X*factor, vector.Y*factor);
}
/// <summary>Checks two vectors for inequality</summary>
/// <param name="first">First vector to be compared</param>
/// <param name="second">Second vector to be compared</param>
/// <returns>True if the instances differ or exactly one reference is set to null</returns>
public static bool operator !=(UniVector first, UniVector second)
{
return !(first == second);
}
/// <summary>Checks two vectors for equality</summary>
/// <param name="first">First vector to be compared</param>
/// <param name="second">Second vector to be compared</param>
/// <returns>True if both instances are equal or both references are null</returns>
public static bool operator ==(UniVector first, UniVector second)
{
// For a struct, neither 'first' nor 'second' can be null
return first.Equals(second);
}
/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public override bool Equals(object other)
{
if (!(other is UniVector))
return false;
return Equals((UniVector) other);
}
/// <summary>Checks whether another instance is equal to this instance</summary>
/// <param name="other">Other instance to compare to this instance</param>
/// <returns>True if the other instance is equal to this instance</returns>
public bool Equals(UniVector other)
{
// For a struct, 'other' cannot be null
return (X == other.X) && (Y == other.Y);
}
/// <summary>Obtains a hash code of this instance</summary>
/// <returns>The hash code of the instance</returns>
public override int GetHashCode()
{
return X.GetHashCode() ^ Y.GetHashCode();
}
/// <summary>
/// Returns a human-readable string representation for the unified vector
/// </summary>
/// <returns>The human-readable string representation of the unified vector</returns>
public override string ToString()
{
return string.Format(
"{{X:{0}, Y:{1}}}",
X,
Y
);
}
}
}
@@ -1,267 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Text;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Content;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
public partial class FlatGuiGraphics : IFlatGuiGraphics, IDisposable
{
/// <summary>Width of the caret used for text input</summary>
private const float _caretWidth = 2.0f;
/// <summary>Bitmaps containing resources for the GUI</summary>
private readonly Dictionary<string, Texture2D> _bitmaps;
/// <summary>Font styles known to the GUI</summary>
private readonly Dictionary<string, SpriteFont> _fonts;
/// <summary>Types of frames the painter can draw</summary>
private readonly Dictionary<string, Frame> _frames;
/// <summary>Locates openings between letters in strings</summary>
private readonly OpeningLocator _openingLocator;
/// <summary>Rasterizer state used for drawing the GUI</summary>
private readonly RasterizerState _rasterizerState;
/// <summary>Manages the scissor rectangle and its assignment time</summary>
private readonly ScissorKeeper _scissorManager;
/// <summary>String builder used for various purposes in this class</summary>
private readonly StringBuilder _stringBuilder;
/// <summary>Manages the content used to draw the GUI</summary>
private ContentManager _contentManager;
/// <summary>Batches GUI elements for faster drawing</summary>
private SpriteBatch _spriteBatch;
/// <summary>Initializes a new gui painter</summary>
/// <param name="contentManager">
/// Content manager containing the resources for the GUI. The instance takes
/// ownership of the content manager and will dispose it.
/// </param>
/// <param name="skinStream">
/// Stream from which the skin description will be read
/// </param>
public FlatGuiGraphics(ContentManager contentManager, Stream skinStream)
{
var graphicsDeviceService =
(IGraphicsDeviceService) contentManager.ServiceProvider.GetService(
typeof(IGraphicsDeviceService)
);
_spriteBatch = new SpriteBatch(graphicsDeviceService.GraphicsDevice);
_contentManager = contentManager;
_openingLocator = new OpeningLocator();
_stringBuilder = new StringBuilder(64);
_scissorManager = new ScissorKeeper(this);
_rasterizerState = new RasterizerState
{
ScissorTestEnable = true
};
_fonts = new Dictionary<string, SpriteFont>();
_bitmaps = new Dictionary<string, Texture2D>();
_frames = new Dictionary<string, Frame>();
LoadSkin(skinStream);
}
/// <summary>Immediately releases all resources owned by the instance</summary>
public void Dispose()
{
if (_contentManager != null)
{
_contentManager.Dispose();
_contentManager = null;
}
if (_spriteBatch != null)
{
_spriteBatch.Dispose();
_spriteBatch = null;
}
}
private void LoadSkin(Stream skinStream)
{
var skin = GuiSkin.FromStream(skinStream);
foreach (var font in skin.resources.font)
_fonts.Add(font.Name, _contentManager.Load<SpriteFont>(font.ContentPath));
foreach (var texture in skin.resources.bitmap)
_bitmaps.Add(texture.Name, _contentManager.Load<Texture2D>(texture.ContentPath));
foreach (var frame in skin.frames)
{
if (frame.Regions != null)
{
for (var i = 0; i < frame.Regions.Length; i++)
frame.Regions[i].Texture = _bitmaps[frame.Regions[i].Source];
_frames.Add(frame.Name, frame);
}
for (var i = 0; i < frame.Texts.Length; i++)
_fonts.TryGetValue(frame.Texts[i].Source, out frame.Texts[i].Font);
}
}
/// <summary>
/// Positions a string within a frame according to the positioning instructions
/// stored in the provided text anchor.
/// </summary>
/// <param name="anchor">Text anchor the string will be positioned for</param>
/// <param name="bounds">Boundaries of the control the string is rendered in</param>
/// <param name="text">String that will be positioned</param>
/// <returns>The position of the string within the control</returns>
private Vector2 PositionText(ref Frame.Text anchor, RectangleF bounds, string text)
{
var textSize = anchor.Font.MeasureString(text);
float x, y;
switch (anchor.HorizontalPlacement)
{
case Frame.HorizontalTextAlignment.Left:
{
x = bounds.Left;
break;
}
case Frame.HorizontalTextAlignment.Right:
{
x = bounds.Right - textSize.X;
break;
}
case Frame.HorizontalTextAlignment.Center:
default:
{
x = (bounds.Width - textSize.X)/2.0f + bounds.Left;
break;
}
}
switch (anchor.VerticalPlacement)
{
case Frame.VerticalTextAlignment.Top:
{
y = bounds.Top;
break;
}
case Frame.VerticalTextAlignment.Bottom:
{
y = bounds.Bottom - textSize.Y;
break;
}
case Frame.VerticalTextAlignment.Center:
default:
{
y = (bounds.Height - textSize.Y)/2.0f + bounds.Top;
break;
}
}
return new Vector2(Floor(x + anchor.Offset.X), Floor(y + anchor.Offset.Y));
}
/// <summary>
/// Calculates the absolute pixel position of a rectangle in unified coordinates
/// </summary>
/// <param name="bounds">Bounds of the drawing area in pixels</param>
/// <param name="destination">Destination rectangle in unified coordinates</param>
/// <returns>
/// The destination rectangle converted to absolute pixel coordinates
/// </returns>
private static Rectangle CalculateDestinationRectangle(ref RectangleF bounds, ref UniRectangle destination)
{
var x = (int) (bounds.X + destination.Location.X.Offset);
x += (int) (bounds.Width*destination.Location.X.Fraction);
var y = (int) (bounds.Y + destination.Location.Y.Offset);
y += (int) (bounds.Height*destination.Location.Y.Fraction);
var width = (int) destination.Size.X.Offset;
width += (int) (bounds.Width*destination.Size.X.Fraction);
var height = (int) destination.Size.Y.Offset;
height += (int) (bounds.Height*destination.Size.Y.Fraction);
return new Rectangle(x, y, width, height);
}
/// <summary>Looks up the frame with the specified name</summary>
/// <param name="frameName">Frame that will be looked up</param>
/// <returns>The frame with the specified name</returns>
private Frame LookupFrame(string frameName)
{
// Make sure the renderer specified a valid frame name. If someone modifies
// the skin or uses a skin which does not support all required controls,
// this will provide the user with a clear error message.
Frame frame;
if (!_frames.TryGetValue(frameName, out frame))
throw new ArgumentException("Unknown frame type: '" + frameName + "'", "frameName");
return frame;
}
/// <summary>Removes the fractional part from the floating point value</summary>
/// <param name="value">Value whose fractional part will be removed</param>
/// <returns>The floating point value without its fractional part</returns>
private static float Floor(float value)
{
return (float) Math.Floor(value);
}
/// <summary>Manages the scissor rectangle for the GUI graphics interface</summary>
private class ScissorKeeper : IDisposable
{
/// <summary>GUI graphics interface for which the scissor rectangle is managed</summary>
private readonly FlatGuiGraphics _flatGuiGraphics;
/// <summary>Scissor rectangle that was previously assigned to the graphics device</summary>
private Rectangle _oldScissorRectangle;
/// <summary>Initializes a new scissor manager</summary>
/// <param name="flatGuiGraphics">GUI graphics interface the scissor rectangle will be managed for</param>
public ScissorKeeper(FlatGuiGraphics flatGuiGraphics)
{
_flatGuiGraphics = flatGuiGraphics;
}
/// <summary>Releases the currently assigned scissor rectangle again</summary>
public void Dispose()
{
_flatGuiGraphics.EndSpriteBatch();
try
{
var graphics = _flatGuiGraphics._spriteBatch.GraphicsDevice;
graphics.ScissorRectangle = _oldScissorRectangle;
}
finally
{
_flatGuiGraphics.BeginSpriteBatch();
}
}
/// <summary>Assigns the scissor rectangle to the graphics device</summary>
/// <param name="clipRegion">Scissor rectangle that will be assigned</param>
public void Assign(ref Rectangle clipRegion)
{
_flatGuiGraphics.EndSpriteBatch();
try
{
var graphics = _flatGuiGraphics._spriteBatch.GraphicsDevice;
_oldScissorRectangle = graphics.ScissorRectangle;
graphics.ScissorRectangle = clipRegion;
}
finally
{
_flatGuiGraphics.BeginSpriteBatch();
}
}
}
}
}
@@ -1,235 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
public partial class FlatGuiGraphics
{
/// <summary>Sets the clipping region for any future drawing commands</summary>
/// <param name="clipRegion">Clipping region that will be set</param>
/// <returns>
/// An object that will unset the clipping region upon its destruction.
/// </returns>
/// <remarks>
/// Clipping regions can be stacked, though this is not very typical for
/// a game GUI and also not recommended practice due to performance constraints.
/// Unless clipping is implemented in software, setting up a clip region
/// on current hardware requires the drawing queue to be flushed, negatively
/// impacting rendering performance (in technical terms, a clipping region
/// change likely causes 2 more DrawPrimitive() calls from the painter).
/// </remarks>
public IDisposable SetClipRegion(RectangleF clipRegion)
{
// Cache the integer values of the clipping region's boundaries
var clipX = (int) clipRegion.X;
var clipY = (int) clipRegion.Y;
var clipRight = clipX + (int) clipRegion.Width;
var clipBottom = clipY + (int) clipRegion.Height;
// Calculate the viewport's right and bottom coordinates
var viewport = _spriteBatch.GraphicsDevice.Viewport;
var viewportRight = viewport.X + viewport.Width;
var viewportBottom = viewport.Y + viewport.Height;
// Extract the part of the clipping region that lies within the viewport
var scissorRegion = new Rectangle(
Math.Max(clipX, viewport.X),
Math.Max(clipY, viewport.Y),
Math.Min(clipRight, viewportRight) - clipX,
Math.Min(clipBottom, viewportBottom) - clipY
);
scissorRegion.Width += clipX - scissorRegion.X;
scissorRegion.Height += clipY - scissorRegion.Y;
// If the clipping region was entirely outside of the viewport (meaning
// the calculated width and/or height are negative), use an empty scissor
// rectangle instead because XNA doesn't like scissor rectangles with
// negative coordinates.
if ((scissorRegion.Width <= 0) || (scissorRegion.Height <= 0))
scissorRegion = Rectangle.Empty;
// All done, take over the new scissor rectangle
_scissorManager.Assign(ref scissorRegion);
return _scissorManager;
}
/// <summary>Draws a GUI element onto the drawing buffer</summary>
/// <param name="frameName">Class of the element to draw</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <remarks>
/// <para>
/// GUI elements are the basic building blocks of a GUI:
/// </para>
/// </remarks>
public void DrawElement(string frameName, RectangleF bounds)
{
var frame = LookupFrame(frameName);
// Draw all the regions defined for the element. Each region is a small bitmap
// that needs to be blit somewhere into the element to form the element's
// visual representation step by step.
for (var index = 0; index < frame.Regions.Length; ++index)
{
var destinationRegion = CalculateDestinationRectangle(ref bounds,
ref frame.Regions[index].DestinationRegion);
_spriteBatch.Draw(frame.Regions[index].Texture, destinationRegion, frame.Regions[index].SourceRegion,
Color.White);
}
}
/// <summary>Draws text into the drawing buffer for the specified element</summary>
/// <param name="frameName">Class of the element for which to draw text</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text that will be drawn</param>
public void DrawString(string frameName, RectangleF bounds, string text)
{
var frame = LookupFrame(frameName);
// Draw the text in all anchor locations defined by the skin
for (var index = 0; index < frame.Texts.Length; ++index)
{
_spriteBatch.DrawString(frame.Texts[index].Font, text,
PositionText(ref frame.Texts[index], bounds, text), frame.Texts[index].Color);
}
}
public void DrawImage(RectangleF bounds, Texture2D texture, Rectangle sourceRectangle)
{
var destinationRectangle = new Rectangle();
if (bounds.Width > bounds.Height)
{
destinationRectangle.Height = Convert.ToInt32(Math.Round(bounds.Height * 0.8));
destinationRectangle.Width = Convert.ToInt32(Math.Round(bounds.Width * 0.8 * (bounds.Height / sourceRectangle.Height)));
destinationRectangle.X = Convert.ToInt32(Math.Round(bounds.Center.X)) - destinationRectangle.Width / 2;
destinationRectangle.Y = Convert.ToInt32(Math.Round(bounds.Center.Y)) - destinationRectangle.Height / 2;
}
else
{
destinationRectangle.Width = Convert.ToInt32(Math.Round(bounds.Width * 0.8));
destinationRectangle.Height = Convert.ToInt32(Math.Round(bounds.Height * 0.8 * (bounds.Width / sourceRectangle.Width)));
destinationRectangle.X = Convert.ToInt32(Math.Round(bounds.Center.X)) - destinationRectangle.Width / 2;
destinationRectangle.Y = Convert.ToInt32(Math.Round(bounds.Center.Y)) - destinationRectangle.Height / 2;
}
_spriteBatch.Draw(texture, destinationRectangle, sourceRectangle, Color.White);
}
/// <summary>Draws a caret for text input at the specified index</summary>
/// <param name="frameName">Class of the element for which to draw a caret</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text for which a caret will be drawn</param>
/// <param name="caretIndex">Index the caret will be drawn at</param>
public void DrawCaret(string frameName, RectangleF bounds, string text, int caretIndex)
{
var frame = LookupFrame(frameName);
_stringBuilder.Remove(0, _stringBuilder.Length);
_stringBuilder.Append(text, 0, caretIndex);
Vector2 caretPosition, textPosition;
for (var index = 0; index < frame.Texts.Length; ++index)
{
textPosition = PositionText(ref frame.Texts[index], bounds, text);
caretPosition = frame.Texts[index].Font.MeasureString(_stringBuilder);
caretPosition.X -= _caretWidth;
caretPosition.Y = 0.0f;
_spriteBatch.DrawString(frame.Texts[index].Font, "|", textPosition + caretPosition,
frame.Texts[index].Color);
}
}
/// <summary>Measures the extents of a string in the frame's area</summary>
/// <param name="frameName">Class of the element whose text will be measured</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text that will be measured</param>
/// <returns>
/// The size and extents of the specified string within the frame
/// </returns>
public RectangleF MeasureString(string frameName, RectangleF bounds, string text)
{
var frame = LookupFrame(frameName);
Vector2 size;
if (frame.Texts.Length > 0)
size = frame.Texts[0].Font.MeasureString(text);
else size = Vector2.Zero;
return new RectangleF(0.0f, 0.0f, size.X, size.Y);
}
/// <summary>
/// Locates the closest gap between two letters to the provided position
/// </summary>
/// <param name="frameName">Class of the element in which to find the gap</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text in which the closest gap will be found</param>
/// <param name="position">Position of which to determien the closest gap</param>
/// <returns>The index of the gap the position is closest to</returns>
public int GetClosestOpening(
string frameName, RectangleF bounds, string text, Vector2 position
)
{
var frame = LookupFrame(frameName);
// Frames can repeat their text in several places. Though this is probably
// not used very often (if at all), it should work here consistently.
var closestGap = -1;
for (var index = 0; index < frame.Texts.Length; ++index)
{
var textPosition = PositionText(ref frame.Texts[index], bounds, text);
position.X -= textPosition.X;
position.Y -= textPosition.Y;
var openingX = position.X;
var openingIndex = _openingLocator.FindClosestOpening(
frame.Texts[index].Font, text, position.X + _caretWidth
);
closestGap = openingIndex;
}
return closestGap;
}
/// <summary>Needs to be called before the GUI drawing process begins</summary>
public void BeginDrawing()
{
var graphics = _spriteBatch.GraphicsDevice;
var viewport = graphics.Viewport;
graphics.ScissorRectangle = new Rectangle(0, 0, viewport.Width, viewport.Height);
// On Windows Phone 7, if only the GUI is rendered (no other SpriteBatches)
// and the initial spriteBatch.Begin() includes the scissor rectangle,
// nothing will be drawn at all, so we don't use beginSpriteBatch() here
// and instead call SpriteBatch.Begin() ourselves. Care has to be taken
// if something ever gets added to the beginSpriteBatch() method.
_spriteBatch.Begin();
}
/// <summary>Needs to be called when the GUI drawing process has ended</summary>
public void EndDrawing()
{
EndSpriteBatch();
}
/// <summary>Starts drawing on the sprite batch</summary>
private void BeginSpriteBatch()
{
_spriteBatch.Begin(SpriteSortMode.Deferred, BlendState.AlphaBlend, null, null, _rasterizerState);
}
/// <summary>Stops drawing on the sprite batch</summary>
private void EndSpriteBatch()
{
_spriteBatch.End();
}
}
}
@@ -1,291 +0,0 @@
using System;
using System.Collections.Generic;
using System.IO;
using System.Linq;
using System.Reflection;
using Microsoft.Xna.Framework.Content;
using MonoGame.Extended.NuclexGui.Controls;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
// Having two overloads of the Draw() method, one doing nothing, is confusing
// and should be avoided. Find a better solution. Perhaps we can rely completely
// on the virtualized graphics device here.
/// <summary>Draws traditional flat GUIs using 2D bitmaps</summary>
public class FlatGuiVisualizer : IGuiVisualizer, IDisposable
{
/// <summary>Helps draw the GUI controls in the hierarchically correct order</summary>
/// <remarks>
/// This is a field and not a local variable because the stack allocates
/// heap memory and we don't want that to happen in a frame-by-frame basis on
/// the compact framework. By reusing the same stack over and over, the amount
/// of heap allocations required will amortize itself.
/// </remarks>
private readonly Stack<ControlWithBounds> _controlStack;
/// <summary>Used to draw the individual building elements of the GUI</summary>
private FlatGuiGraphics _flatGuiGraphics;
/// <summary>Avaiable renderers</summary>
private readonly Dictionary<Type, IControlRendererAdapter> _renderers;
/// <summary>Assemblies which has renderer implementations</summary>
public static readonly List<Assembly> ExternalAssembliesWithRenderers = new List<Assembly>();
/// <summary>Initializes a new gui painter for traditional GUIs</summary>
/// <param name="contentManager">Content manager that will be used to load the skin resources</param>
/// <param name="skinStream">Stream from which the GUI Visualizer will read the skin description</param>
protected FlatGuiVisualizer(ContentManager contentManager, Stream skinStream)
{
_renderers = new Dictionary<Type, IControlRendererAdapter>();
// Obtain default GUI renderers
FetchRenderers();
_flatGuiGraphics = new FlatGuiGraphics(contentManager, skinStream);
_controlStack = new Stack<ControlWithBounds>();
}
/// <summary>Immediately releases all resources owned by the instance</summary>
public void Dispose()
{
if (_flatGuiGraphics != null)
{
_flatGuiGraphics.Dispose();
_flatGuiGraphics = null;
}
}
/// <summary>Draws an entire GUI hierarchy</summary>
/// <param name="screen">Screen containing the GUI that will be drawn</param>
public void Draw(GuiScreen screen)
{
_flatGuiGraphics.BeginDrawing();
try
{
_controlStack.Push(ControlWithBounds.FromScreen(screen));
while (_controlStack.Count > 0)
{
var controlWithBounds = _controlStack.Pop();
var currentControl = controlWithBounds.Control;
var currentBounds = controlWithBounds.Bounds;
// Add the controls in normal order, so the first control in the collection will
// be taken off the stack last, ensuring it's rendered on top of all others.
for (var index = 0; index < currentControl.Children.Count; ++index)
_controlStack.Push(ControlWithBounds.FromControl(currentControl.Children[index], currentBounds));
RenderControl(currentControl);
}
}
finally
{
_flatGuiGraphics.EndDrawing();
}
}
/// <summary>Renders a single control</summary>
/// <param name="controlToRender">Control that will be rendered</param>
private void RenderControl(GuiControl controlToRender)
{
IControlRendererAdapter renderer = null;
var controlType = controlToRender.GetType();
// If this is an actual instance of the 'Control' class, don't render it.
// Such instances can be used to construct invisible containers, and are most
// prominently embodied in the 'desktop' control that hosts the whole GUI.
if ((controlType == typeof(GuiControl)) || (controlType == typeof(GuiDesktopControl)))
return;
// Find a renderer for this control. If no renderer for the control itself can
// be found, look for a renderer then can render its base class. This allows
// controls to inherit from existing controls, remaining renderable (but also
// gaining the ability to accept a specialized renderer for the new derived
// control class!). Normally, this loop will finish without any repetitions.
while (controlType != typeof(object))
{
var found = _renderers.TryGetValue(controlType, out renderer);
if (found) break;
// Next, try the base class of this type
controlType = controlType.GetTypeInfo().BaseType;
}
// If we found a renderer, use it to render the control
if (renderer != null)
renderer.Render(controlToRender, _flatGuiGraphics);
}
private void FetchRenderers()
{
FetchRenderers(typeof(FlatGuiVisualizer).GetTypeInfo().Assembly);
foreach(var assembly in ExternalAssembliesWithRenderers)
FetchRenderers(assembly);
}
private void FetchRenderers(Assembly assembly)
{
foreach (
var typeinfo in
assembly.DefinedTypes.Where(e => e.IsPublic && !e.IsAbstract))
{
// If the type doesn't implement the IFlatcontrolRenderer interface, there's
// no chance that it will implement one of the generic control drawers
if (!typeof(IFlatControlRenderer).GetTypeInfo().IsAssignableFrom(typeinfo))
continue;
// We also need a default constructor in order to be able to create an instance of this renderer
if (typeinfo.DeclaredConstructors.Count(e => e.IsPublic && (e.GetParameters().Length == 0)) == 0)
continue;
// Look for the IFlatControlRenderer<> interface in all interfaces implemented this type
var genericType =
typeinfo.ImplementedInterfaces.Where(
e =>
e.IsConstructedGenericType &&
(e.GetGenericTypeDefinition() == typeof(IFlatControlRenderer<>))).FirstOrDefault();
if (genericType == default(Type)) continue;
// Find out which control type the renderer is specialized for
var controlType = genericType.GenericTypeArguments;
// Do we already have a renderer for this control type?
if (!_renderers.ContainsKey(controlType[0]))
{
// Type of the downcast adapter we need to bring to life
var adapterType = typeof(ControlRendererAdapter<>).MakeGenericType(controlType[0]);
var adapterConstructor = adapterType.GetTypeInfo().DeclaredConstructors.FirstOrDefault();
// Now use that constructor to create an instance
var adapterInstance = adapterConstructor.Invoke(new[] {Activator.CreateInstance(typeinfo.AsType())});
// Employ the new adapter and thereby the control renderer it adapts
_renderers.Add(controlType[0], (IControlRendererAdapter) adapterInstance);
}
}
}
/// <summary>Initializes a new gui visualizer from a skin stored as a resource</summary>
/// <param name="serviceProvider">Game service provider containing the graphics device service</param>
/// <param name="skinJsonFile">Name of the Json file containing the skin description</param>
public static FlatGuiVisualizer FromResource(IServiceProvider serviceProvider, string skinJsonFile)
{
var assembly = typeof(FlatGuiVisualizer).GetTypeInfo().Assembly;
var resources = assembly.GetManifestResourceNames();
if (!resources.Contains(skinJsonFile))
throw new ArgumentException("Resource '" + skinJsonFile + "' not found", "skinJsonFile");
using (var skinStream = assembly.GetManifestResourceStream(skinJsonFile))
{
var contentManager = new ContentManager(serviceProvider, "Content");
try
{
return new FlatGuiVisualizer(contentManager, skinStream);
}
catch (Exception)
{
contentManager.Dispose();
throw;
}
}
}
/// <summary>Container for a control and its absolute boundaries</summary>
private struct ControlWithBounds
{
/// <summary>Control stored in the container</summary>
public readonly GuiControl Control;
/// <summary>Absolute boundaries of the stored control</summary>
public readonly RectangleF Bounds;
/// <summary>Initializes a new control and absolute boundary container</summary>
/// <param name="control">Control being store in the container</param>
/// <param name="bounds">Absolute boundaries the control lives in</param>
public ControlWithBounds(GuiControl control, RectangleF bounds)
{
Control = control;
Bounds = bounds;
}
/// <summary>Builds an absolute boundary container from the provided control</summary>
/// <param name="control">Control from which a container will be created</param>
/// <param name="containerBounds">Absolute boundaries of the control's parent</param>
/// <returns>A new container with the control</returns>
public static ControlWithBounds FromControl(GuiControl control, RectangleF containerBounds)
{
containerBounds.X += control.Bounds.Location.X.Fraction*containerBounds.Width;
containerBounds.X += control.Bounds.Location.X.Offset;
containerBounds.Y += control.Bounds.Location.Y.Fraction*containerBounds.Height;
containerBounds.Y += control.Bounds.Location.Y.Offset;
containerBounds.Width = control.Bounds.Size.X.ToOffset(containerBounds.Width);
containerBounds.Height = control.Bounds.Size.Y.ToOffset(containerBounds.Height);
return new ControlWithBounds(control, containerBounds);
}
/// <summary>Builds a control and absolute boundary container from a screen</summary>
/// <param name="screen">Screen whose desktop control and absolute boundaries are used to construct the container</param>
/// <returns>A new container with the screen's desktop control</returns>
public static ControlWithBounds FromScreen(GuiScreen screen)
{
return new ControlWithBounds(screen.Desktop, screen.Desktop.Bounds.ToOffset(screen.Width, screen.Height));
}
}
/// <summary>Interface for a generic (typeless) control renderer</summary>
internal interface IControlRendererAdapter
{
/// <summary>The type of the control renderer being adapted</summary>
Type AdaptedType { get; }
/// <summary>Renders the specified control using the provided graphics interface</summary>
/// <param name="controlToRender">Control that will be rendered</param>
/// <param name="graphics">Graphics interface that will be used to render the control</param>
void Render(GuiControl controlToRender, IFlatGuiGraphics graphics);
}
/// <summary>
/// Adapter that automatically casts a control down to the renderer's supported
/// control type
/// </summary>
/// <typeparam name="ControlType">
/// Type of control the control renderer casts down to
/// </typeparam>
/// <remarks>
/// This is simply an optimization to avoid invoking the control renderer
/// by reflection (using the Invoke() method) which would require us to construct
/// an object[] array on the heap to pass its arguments.
/// </remarks>
private class ControlRendererAdapter<TControlType> : IControlRendererAdapter where TControlType : GuiControl
{
/// <summary>Control renderer this adapter is performing the downcast for</summary>
private readonly IFlatControlRenderer<TControlType> _controlRenderer;
/// <summary>Initializes a new control renderer adapter</summary>
/// <param name="controlRenderer">Control renderer the adapter is used for</param>
public ControlRendererAdapter(IFlatControlRenderer<TControlType> controlRenderer)
{
_controlRenderer = controlRenderer;
}
/// <summary>Renders the specified control using the provided graphics interface</summary>
/// <param name="controlToRender">Control that will be rendered</param>
/// <param name="graphics">Graphics interface that will be used to render the control</param>
public void Render(GuiControl controlToRender, IFlatGuiGraphics graphics)
{
_controlRenderer.Render((TControlType) controlToRender, graphics);
}
/// <summary>The type of the control renderer being adapted</summary>
public Type AdaptedType => _controlRenderer.GetType();
}
}
}
@@ -1,178 +0,0 @@
using System.Collections.Generic;
using System.IO;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
using Newtonsoft.Json;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
/// <summary>Frame that can be drawn by the GUI painter</summary>
public class Frame
{
/// <summary>Modes in which text can be horizontally aligned</summary>
public enum HorizontalTextAlignment
{
/// <summary>The text's base offset is placed at the left of the frame</summary>
/// <remarks>
/// The base offset is normally identical to the text's leftmost pixel.
/// However, a glyph may have some eccentrics like an arc that extends to
/// the left over the letter's actual starting position.
/// </remarks>
Left,
/// <summary>
/// The text's ending offset is placed at the right of the frame
/// </summary>
/// <remarks>
/// The ending offset is normally identical to the text's rightmost pixel.
/// However, a glyph may have some eccentrics like an arc that extends to
/// the right over the last letter's actual ending position.
/// </remarks>
Right,
/// <summary>The text is centered horizontally in the frame</summary>
Center
}
/// <summary>Modes in which text can be vertically aligned</summary>
public enum VerticalTextAlignment
{
/// <summary>The text's baseline is placed at the top of the frame</summary>
Top,
/// <summary>The text's baseline is placed at the bottom of the frame</summary>
Bottom,
/// <summary>The text's baseline is centered vertically in the frame</summary>
Center
}
[JsonProperty("name")]
public string Name;
/// <summary>Regions that need to be drawn to render the frame</summary>
[JsonProperty("region")]
public Region[] Regions;
/// <summary>Locations where text can be drawn into the frame</summary>
[JsonProperty("text")]
public Text[] Texts;
/// <summary>Initializes a new frame</summary>
/// <param name="regions">Regions needed to be drawn to render the frame</param>
/// <param name="texts">Location in the frame where text can be drawn</param>
public Frame(Region[] regions, Text[] texts)
{
Regions = regions;
Texts = texts;
if (regions == null)
Regions = new Region[0];
if (texts == null)
Texts = new Text[0];
}
/// <summary>Defines a picture region drawn into a frame</summary>
public struct Region
{
/// <summary>Identification string for the region</summary>
/// <remarks>
/// Used to associate regions with specific behavior
/// </remarks>
public string Id;
/// <summary>Texture the picture region is taken from</summary>
public Texture2D Texture;
/// <summary>Area within the texture containing the picture region</summary>
public Rectangle SourceRegion;
/// <summary>Location in the frame where the picture region will be drawn</summary>
public UniRectangle DestinationRegion;
/// <summary>Name of the texture the picture region is taken from</summary>
public string Source;
}
/// <summary>Describes where within the frame text should be drawn</summary>
public struct Text
{
/// <summary>Font to use for drawing the text</summary>
public SpriteFont Font;
/// <summary>Offset of the text relative to its specified placement</summary>
public Point Offset;
/// <summary>Horizontal placement of the text within the frame</summary>
public HorizontalTextAlignment HorizontalPlacement;
/// <summary>Vertical placement of the text within the frame</summary>
public VerticalTextAlignment VerticalPlacement;
/// <summary>Color the text will have</summary>
public Color Color;
/// <summary> Name of the font used for drawing the text </summary>
public string Source;
}
}
public class GuiSkin
{
public GuiSkin()
{
frames = new List<Frame>();
resources = new Resources
{
bitmap = new List<Resources.Bitmap>(),
font = new List<Resources.Font>()
};
}
public Resources resources { get; set; }
public List<Frame> frames { get; set; }
public static GuiSkin FromFile(string path)
{
using (var stream = TitleContainer.OpenStream(path))
{
return FromStream(stream);
}
}
public static GuiSkin FromStream(Stream stream)
{
using (var streamReader = new StreamReader(stream))
{
var json = streamReader.ReadToEnd();
var converters = new JsonConverter[]
{
new GuiSkinJsonConverter()
};
return JsonConvert.DeserializeObject<GuiSkin>(json, converters);
}
}
public class Resources
{
public List<Font> font { get; set; }
public List<Bitmap> bitmap { get; set; }
public class Font
{
public string Name { get; set; }
public string ContentPath { get; set; }
}
public class Bitmap
{
public string Name { get; set; }
public string ContentPath { get; set; }
}
}
}
}
@@ -1,236 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Newtonsoft.Json;
using Newtonsoft.Json.Linq;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
public class GuiSkinJsonConverter : JsonConverter
{
/// <summary>Width of the frame's bottom border regions</summary>
private int _bottomBorderWidth;
/// <summary>Width of the frame's left border regions</summary>
private int _leftBorderWidth;
/// <summary>Width of the frame's right border regions</summary>
private int _rightBorderWidth;
/// <summary>Width of the frame's top border regions</summary>
private int _topBorderWidth;
public override bool CanConvert(Type objectType)
{
if (objectType == typeof(Frame.Region[]))
return true;
if (objectType == typeof(Frame.Text))
return true;
return false;
}
public override object ReadJson(JsonReader reader, Type objectType, object existingValue,
JsonSerializer serializer)
{
if (objectType == typeof(Frame.Region[]))
return ParseRegions(JArray.Load(reader));
if (objectType == typeof(Frame.Text))
return ParseText(JToken.Load(reader));
return (string) reader.Value;
}
public override void WriteJson(JsonWriter writer, object value, JsonSerializer serializer)
{
}
private Frame.Region[] ParseRegions(JArray jArray)
{
var regions = new Frame.Region[jArray.Count];
_leftBorderWidth = 0;
_rightBorderWidth = 0;
_topBorderWidth = 0;
_bottomBorderWidth = 0;
// Detect borders
foreach (var token in jArray)
{
var hPlacement = token.Value<string>("hplacement");
var vPlacement = token.Value<string>("vplacement");
if (hPlacement == "left")
_leftBorderWidth = Math.Max(_leftBorderWidth, token.Value<int>("w"));
else
{
if (hPlacement == "right")
_rightBorderWidth = Math.Max(_rightBorderWidth, token.Value<int>("w"));
}
if (vPlacement == "top")
_topBorderWidth = Math.Max(_topBorderWidth, token.Value<int>("h"));
else
{
if (vPlacement == "bottom")
_bottomBorderWidth = Math.Max(_bottomBorderWidth, token.Value<int>("h"));
}
}
// Parse each region
for (var i = 0; i < regions.Length; i++)
regions[i] = ParseRegion(jArray[i]);
return regions;
}
private Frame.Region ParseRegion(JToken token)
{
var region = new Frame.Region();
var hAlignment = token.Value<string>("hplacement");
var vAlignment = token.Value<string>("vplacement");
var x = token.Value<int>("x");
var y = token.Value<int>("y");
var w = token.Value<int>("w");
var h = token.Value<int>("h");
// Assign trivialities
region.Source = token.Value<string>("source");
region.Id = token.Value<string>("id");
region.SourceRegion.X = x;
region.SourceRegion.Y = y;
region.SourceRegion.Width = w;
region.SourceRegion.Height = h;
// Process each region's placement and set up the unified coordinates
CalculateRegionPlacement(GetHorizontalPlacementIndex(hAlignment), w, _leftBorderWidth, _rightBorderWidth,
ref region.DestinationRegion.Location.X, ref region.DestinationRegion.Size.X);
CalculateRegionPlacement(GetVerticalPlacementIndex(vAlignment), h, _topBorderWidth, _bottomBorderWidth,
ref region.DestinationRegion.Location.Y, ref region.DestinationRegion.Size.Y);
return region;
}
/// <summary>
/// Calculates the unified coordinates a region needs to be placed at
/// </summary>
/// <param name="placementIndex">
/// Placement index indicating where in a frame the region will be located
/// </param>
/// <param name="width">Width of the region in pixels</param>
/// <param name="lowBorderWidth">
/// Width of the border on the lower end of the coordinate range
/// </param>
/// <param name="highBorderWidth">
/// Width of the border on the higher end of the coordinate range
/// </param>
/// <param name="location">
/// Receives the target location of the region in unified coordinates
/// </param>
/// <param name="size">
/// Receives the size of the region in unified coordinates
/// </param>
private void CalculateRegionPlacement(
int placementIndex, int width,
int lowBorderWidth, int highBorderWidth,
ref UniScalar location, ref UniScalar size
)
{
switch (placementIndex)
{
case -1:
{
// left or top
var gapWidth = lowBorderWidth - width;
location.Fraction = 0.0f;
location.Offset = gapWidth;
size.Fraction = 0.0f;
size.Offset = width;
break;
}
case +1:
{
// right or bottom
location.Fraction = 1.0f;
location.Offset = -highBorderWidth;
size.Fraction = 0.0f;
size.Offset = width;
break;
}
case 0:
{
// stretch
location.Fraction = 0.0f;
location.Offset = lowBorderWidth;
size.Fraction = 1.0f;
size.Offset = -(highBorderWidth + lowBorderWidth);
break;
}
}
}
/// <summary>Converts a horizontal placement string into a placement index</summary>
/// <param name="placement">String containing the horizontal placement</param>
/// <returns>A placement index that is equivalent to the provided string</returns>
private int GetHorizontalPlacementIndex(string placement)
{
switch (placement)
{
case "left":
{
return -1;
}
case "right":
{
return +1;
}
case "stretch":
default:
{
return 0;
}
}
}
/// <summary>Converts a vertical placement string into a placement index</summary>
/// <param name="placement">String containing the vertical placement</param>
/// <returns>A placement index that is equivalent to the provided string</returns>
private int GetVerticalPlacementIndex(string placement)
{
switch (placement)
{
case "top":
{
return -1;
}
case "bottom":
{
return +1;
}
case "stretch":
default:
{
return 0;
}
}
}
private Frame.Text ParseText(JToken token)
{
var text = new Frame.Text
{
Offset = new Point(token.Value<int>("xoffset"), token.Value<int>("yoffset")),
Color = ColorHelper.FromHex(token.Value<string>("color"))
};
Enum.TryParse(token.Value<string>("hplacement"), true, out text.HorizontalPlacement);
Enum.TryParse(token.Value<string>("vplacement"), true, out text.VerticalPlacement);
text.Source = token.Value<string>("font");
return text;
}
}
}
@@ -1,19 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
/// <summary>Interface for a class that renders a control</summary>
public interface IFlatControlRenderer
{
}
/// <summary>Interface for a class responsible to render a specific control type</summary>
/// <typeparam name="ControlType">Type of control the implementation class will render</typeparam>
public interface IFlatControlRenderer<TControlType> : IFlatControlRenderer where TControlType : GuiControl
{
/// <summary>Renders the specified control using the provided graphics interface</summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">Graphics interface that will be used to draw the control</param>
void Render(TControlType control, IFlatGuiGraphics graphics);
}
}
@@ -1,73 +0,0 @@
using System;
using Microsoft.Xna.Framework;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
/// <summary>Provides drawing methods for GUI controls</summary>
/// <remarks>
/// Analogous to System.Drawing.Graphics, but provides specialized methods for
/// drawing a GUI with a dynamic, switchable theme.
/// </remarks>
public interface IFlatGuiGraphics
{
/// <summary>Sets the clipping region for any future drawing commands</summary>
/// <param name="clipRegion">Clipping region that will be set</param>
/// <returns>
/// An object that will unset the clipping region upon its destruction.
/// </returns>
/// <remarks>
/// Clipping regions can be stacked, though this is not very typical for
/// a game GUI and also not recommended practice due to performance constraints.
/// Unless clipping is implemented in software, setting up a clip region
/// on current hardware requires the drawing queue to be flushed, negatively
/// impacting rendering performance (in technical terms, a clipping region
/// change likely causes 2 more DrawPrimitive() calls from the painter).
/// </remarks>
IDisposable SetClipRegion(RectangleF clipRegion);
/// <summary>Draws a GUI element onto the drawing buffer</summary>
/// <param name="frameName">Class of the element to draw</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <remarks>
/// <para>
/// GUI elements are the basic building blocks of a GUI:
/// </para>
/// </remarks>
void DrawElement(string frameName, RectangleF bounds);
/// <summary>Draws text into the drawing buffer for the specified element</summary>
/// <param name="frameName">Class of the element for which to draw text</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text that will be drawn</param>
void DrawString(string frameName, RectangleF bounds, string text);
void DrawImage(RectangleF bounds, Texture2D texture, Rectangle sourceRectangle);
/// <summary>Draws a caret for text input at the specified index</summary>
/// <param name="frameName">Class of the element for which to draw a caret</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text for which a caret will be drawn</param>
/// <param name="index">Index the caret will be drawn at</param>
void DrawCaret(string frameName, RectangleF bounds, string text, int index);
/// <summary>Measures the extents of a string in the frame's area</summary>
/// <param name="frameName">Class of the element whose text will be measured</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text that will be measured</param>
/// <returns>
/// The size and extents of the specified string within the frame
/// </returns>
RectangleF MeasureString(string frameName, RectangleF bounds, string text);
/// <summary>
/// Locates the closest gap between two letters to the provided position
/// </summary>
/// <param name="frameName">Class of the element in which to find the gap</param>
/// <param name="bounds">Region that will be covered by the drawn element</param>
/// <param name="text">Text in which the closest gap will be found</param>
/// <param name="position">Position of which to determien the closest gap</param>
/// <returns>The index of the gap the position is closest to</returns>
int GetClosestOpening(string frameName, RectangleF bounds, string text, Vector2 position);
}
}
@@ -1,96 +0,0 @@
using System.Text;
using Microsoft.Xna.Framework.Graphics;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat
{
/// <summary>
/// Locates the opening between characters in a string that is nearest
/// to a user-defined location
/// </summary>
/// <remarks>
/// <para>
/// This is a class rather than a static class to prevent garbage production
/// which would then have to be cleaned up again by the garbage collector.
/// If you create an instance of it and keep reusing it, garbage and allocation
/// will amortize.
/// </para>
/// <para>
/// The method used to calculate the openings seems to be not terribly accurate.
/// As of XNA 3.1, SpriteFonts don't do kerning, so the only thing left would be
/// a variable space appended to the end of characters. This could be compensated
/// for by always appending character with a known length for which no kerning is
/// possible, for example, the pipe sign (|).
/// </para>
/// </remarks>
internal class OpeningLocator
{
/// <summary>Used by GetClosestOpening() to avoid garbage production</summary>
private readonly StringBuilder _textBuilder;
/// <summary>Initializes a new text opening locator</summary>
public OpeningLocator()
{
_textBuilder = new StringBuilder(64);
}
/// <summary>
/// Locates the opening between two letters that is closest to
/// the specified position
/// </summary>
/// <param name="font">Font that opening search will use</param>
/// <param name="text">Text that will be searched for the opening</param>
/// <param name="x">X coordinate closest to which an opening will be found</param>
/// <returns>The opening closest to the specified X coordinate</returns>
public int FindClosestOpening(SpriteFont font, string text, float x)
{
// Measure the size of the whole string
_textBuilder.Remove(0, _textBuilder.Length);
_textBuilder.Append(text);
var textSize = font.MeasureString(_textBuilder);
// Run a binary search until to close in on the nearest opening
var left = 0;
var leftX = 0.0f;
var right = text.Length;
var rightX = textSize.X;
for (;;)
{
// Is the provided coordinate outside of our search range?
// -> Opening is to the far left or to the far right.
if (x <= leftX)
return left;
if (x >= rightX)
return right;
// Do we have only one character left to check?
// -> Opening is either to its left or to its right.
if (right - left <= 1)
{
if (x - leftX <= rightX - x)
return left;
else return right;
}
// The position of the opening is still not absolutely clear, cut the string
// in the middle of the search range so we can close in further.
var middle = (right + left)/2;
_textBuilder.Remove(middle, right - middle);
textSize = font.MeasureString(_textBuilder);
// Depending on whether the searched-for position was on the left or right
// of our cut, adjust appropriate side and prepare for another run
if (x < textSize.X)
{
right = middle;
rightX = textSize.X;
}
else
{
_textBuilder.Append(text, middle, right - middle);
left = middle;
leftX = textSize.X;
}
}
}
}
}
@@ -1,58 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders button controls in a traditional flat style</summary>
public class FlatButtonControlRenderer : IFlatControlRenderer<GuiButtonControl>
{
/// <summary>Names of the states the button control can be in</summary>
/// <remarks>
/// Storing this as full strings instead of building them dynamically prevents
/// any garbage from forming during rendering.
/// </remarks>
private static readonly string[] _states =
{
"button.disabled",
"button.normal",
"button.highlighted",
"button.depressed"
};
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiButtonControl control, IFlatGuiGraphics graphics)
{
var controlBounds = control.GetAbsoluteBounds();
// Determine the style to use for the button
var stateIndex = 0;
if (control.Enabled)
{
if (control.Depressed)
stateIndex = 3;
else
{
if (control.MouseHovering || control.HasFocus)
stateIndex = 2;
else stateIndex = 1;
}
}
// Draw the button's frame
graphics.DrawElement(_states[stateIndex], controlBounds);
// If there's image assigned to the button, draw it into the button
if (control.Texture != null)
graphics.DrawImage(controlBounds, control.Texture, control.SourceRectangle);
// If there's text assigned to the button, draw it into the button
if (!string.IsNullOrEmpty(control.Text))
graphics.DrawString(_states[stateIndex], controlBounds, control.Text);
}
}
}
@@ -1,71 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders choice controls in a traditional flat style</summary>
public class FlatChoiceControlRenderer : IFlatControlRenderer<GuiChoiceControl>
{
/// <summary>Names of the states the choice control can be in</summary>
/// <remarks>
/// Storing this as full strings instead of building them dynamically prevents
/// any garbage from forming during rendering.
/// </remarks>
private static readonly string[] _states =
{
"radio.off.disabled",
"radio.off.normal",
"radio.off.highlighted",
"radio.off.depressed",
"radio.on.disabled",
"radio.on.normal",
"radio.on.highlighted",
"radio.on.depressed"
};
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiChoiceControl control, IFlatGuiGraphics graphics)
{
// Determine the index of the state we're going to display
var stateIndex = control.Selected ? 4 : 0;
if (control.Enabled)
{
if (control.Depressed)
stateIndex += 3;
else
{
if (control.MouseHovering)
stateIndex += 2;
else stateIndex += 1;
}
}
// Get the pixel coordinates of the region covered by the control on
// the screen
var controlBounds = control.GetAbsoluteBounds();
var width = controlBounds.Width;
// Now adjust the bounds to a square of height x height pixels so we can
// render the graphical portion of the choice control
controlBounds.Width = controlBounds.Height;
graphics.DrawElement(_states[stateIndex], controlBounds);
// If the choice has text assigned to it, render it too
if (!string.IsNullOrEmpty(control.Text))
{
// Restore the original width, then subtract the region that was covered by
// the graphical portion of the control.
controlBounds.Width = width - controlBounds.Height;
controlBounds.X += controlBounds.Height;
// Draw the text that was assigned to the choice control
graphics.DrawString(_states[stateIndex], controlBounds, control.Text);
}
}
}
}
@@ -1,37 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders horizontal sliders in a traditional flat style</summary>
public class FlatHorizontalSliderControlRenderer : IFlatControlRenderer<GuiHorizontalSliderControl>
{
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiHorizontalSliderControl control, IFlatGuiGraphics graphics
)
{
var controlBounds = control.GetAbsoluteBounds();
var thumbWidth = controlBounds.Width*control.ThumbSize;
var thumbX = (controlBounds.Width - thumbWidth)*control.ThumbPosition;
graphics.DrawElement("rail.horizontal", controlBounds);
var thumbBounds = new RectangleF(controlBounds.X + thumbX, controlBounds.Y, thumbWidth, controlBounds.Height);
if (control.ThumbDepressed)
graphics.DrawElement("slider.horizontal.depressed", thumbBounds);
else
{
if (control.MouseOverThumb)
graphics.DrawElement("slider.horizontal.highlighted", thumbBounds);
else graphics.DrawElement("slider.horizontal.normal", thumbBounds);
}
}
}
}
@@ -1,100 +0,0 @@
using Microsoft.Xna.Framework;
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders text input controls in a traditional flat style</summary>
public class FlatInputControlRenderer : IFlatControlRenderer<GuiInputControl>, IOpeningLocator
{
/// <summary>Style from the skin this renderer uses</summary>
private const string _style = "input.normal";
// Otherwise the renderer could try to renderer when no frame is being drawn.
// Also, the renderer makes the assumption that all drawing happens through
// one graphics interface only.
/// <summary>Graphics interface we used for the last draw call</summary>
private IFlatGuiGraphics _graphics;
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiInputControl control, IFlatGuiGraphics graphics)
{
var controlBounds = control.GetAbsoluteBounds();
// Draw the control's frame and background
graphics.DrawElement(_style, controlBounds);
using (graphics.SetClipRegion(controlBounds))
{
var text = control.Text ?? string.Empty;
// Amount by which the text will be moved within the input box in
// order to keep the caret in view even when the text is wider than
// the input box.
float left = 0;
// Only scroll the text within the input box when it has the input
// focus and the caret is being shown.
if (control.HasFocus)
{
// Find out where the cursor is from the left end of the text
var stringSize = graphics.MeasureString(
_style, controlBounds, text.Substring(0, control.CaretPosition)
);
// Otherwise text will be visible over the frame, which might look bad
// if a skin uses a frame wider than 2 pixels or in a different color
// than the text.
while (stringSize.Width + left > controlBounds.Width)
left -= controlBounds.Width/10.0f;
}
// Draw the text into the input box
controlBounds.X += left;
graphics.DrawString(_style, controlBounds, control.Text);
// If the input box is in focus, also draw the caret so the user knows
// where characters will be inserted into the text.
if (control.HasFocus)
{
if (control.MillisecondsSinceLastCaretMovement%500 < 250)
{
graphics.DrawCaret(
"input.normal", controlBounds, control.Text, control.CaretPosition
);
}
}
}
// Let the control know that we can provide it with additional informations
// about how its text is being rendered
control.OpeningLocator = this;
_graphics = graphics;
}
/// <summary>
/// Calculates which opening between two letters is closest to a position
/// </summary>
/// <param name="bounds">
/// Boundaries of the control, should be in absolute coordinates
/// </param>
/// <param name="text">Text in which the opening will be looked for</param>
/// <param name="position">
/// Position to which the closest opening will be found,
/// should be in absolute coordinates
/// </param>
/// <returns>The index of the opening closest to the provided position</returns>
public int GetClosestOpening(
RectangleF bounds, string text, Vector2 position
)
{
return _graphics.GetClosestOpening("input.normal", bounds, text, position);
}
}
}
@@ -1,24 +0,0 @@
using System;
using MonoGame.Extended.NuclexGui.Controls;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders label controls in a traditional flat style</summary>
public class FlatLabelControlRenderer : IFlatControlRenderer<GuiLabelControl>
{
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiLabelControl control, IFlatGuiGraphics graphics)
{
var frameName = "label";
if (control.Style != null) frameName = control.Style;
graphics.DrawString(frameName, control.GetAbsoluteBounds(), control.Text);
}
}
}
@@ -1,130 +0,0 @@
using System;
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders text input controls in a traditional flat style</summary>
public class FlatListControlRenderer : IFlatControlRenderer<GuiListControl>, IListRowLocator
{
/// <summary>Style used to draw this control</summary>
private const string _style = "list";
// Otherwise the renderer could try to renderer when no frame is being drawn.
// Also, this way the renderer makes the assumption that all drawing happens
// through one graphics interface only.
/// <summary>Graphics interface we used for the last draw call</summary>
private IFlatGuiGraphics _graphics;
/// <summary>Height of a single row in the list</summary>
private float _rowHeight = float.NaN;
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiListControl control, IFlatGuiGraphics graphics)
{
_graphics = graphics;
var controlBounds = control.GetAbsoluteBounds();
graphics.DrawElement(_style, controlBounds);
// Cache the number of items in the list (as a float, this comes in handy later)
// and the height of a single item when rendered
float totalItems = control.Items.Count;
var rowHeight = GetRowHeight(controlBounds);
// Number of items (+fraction) fitting into the list's height
var itemsInView = controlBounds.Height/rowHeight;
// Number of items by which the slider can move up and down
var scrollableArea = Math.Max(totalItems - itemsInView, 0.0f);
// Index (+fraction) of the item at the top of the list given
// the slider's current position
var scrollPosition = control.Slider.ThumbPosition*scrollableArea;
// Determine the first and the last item we need to draw
// (no need to draw the whole of the list when only a small subset
// will end up in the clipping area)
var firstItem = (int) scrollPosition;
var lastItem = (int) Math.Ceiling(scrollPosition + itemsInView);
lastItem = Math.Min(lastItem, control.Items.Count);
// Set up a rectangle we can use to track the bounds of the item
// currently being rendered
var itemBounds = controlBounds;
itemBounds.Y -= (scrollPosition - firstItem)*rowHeight;
itemBounds.Height = rowHeight;
using (graphics.SetClipRegion(controlBounds))
{
for (var item = firstItem; item < lastItem; ++item)
{
if (control.SelectedItems.Contains(item))
graphics.DrawElement("list.selection", itemBounds);
graphics.DrawString(_style, itemBounds, control.Items[item]);
itemBounds.Y += rowHeight;
}
}
control.ListRowLocator = this;
}
/// <summary>Calculates the list row the cursor is in</summary>
/// <param name="bounds">
/// Boundaries of the control, should be in absolute coordinates
/// </param>
/// <param name="thumbPosition">
/// Position of the thumb in the list's slider
/// </param>
/// <param name="itemCount">
/// Number of items contained in the list
/// </param>
/// <param name="y">Vertical position of the cursor</param>
/// <returns>The row the cursor is over</returns>
public int GetRow(RectangleF bounds, float thumbPosition, int itemCount, float y)
{
float totalItems = itemCount;
var rowHeight = GetRowHeight(bounds);
// Number of items (+fraction) fitting into the list's height
var itemsInView = bounds.Height/rowHeight;
// Number of items by which the slider can move up and down
var scrollableArea = totalItems - itemsInView;
// Index (+fraction) of the item at the top of the list given
// the slider's current position
var scrollPosition = thumbPosition*scrollableArea;
// Calculate the item that should be under the requested Y coordinate
return (int) (y/GetRowHeight(bounds) + scrollPosition);
}
/// <summary>Determines the height of a row displayed in the list</summary>
/// <param name="bounds">
/// Boundaries of the control, should be in absolute coordinates
/// </param>
/// <returns>The height of a single row in the list</returns>
public float GetRowHeight(RectangleF bounds)
{
// The code below is not optimal, but the XNA SpriteFont isn't very talkative
// when it comes to providing informations about itself ;)
if (float.IsNaN(_rowHeight))
{
_rowHeight = _graphics.MeasureString("list", bounds, "qyjpMAW!").Height;
_rowHeight += 2.0f;
}
return _rowHeight;
}
}
}
@@ -1,71 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders option controls in a traditional flat style</summary>
public class FlatOptionControlRenderer : IFlatControlRenderer<GuiOptionControl>
{
/// <summary>Names of the states the option control can be in</summary>
/// <remarks>
/// Storing this as full strings instead of building them dynamically prevents
/// any garbage from forming during rendering.
/// </remarks>
private static readonly string[] _states =
{
"check.off.disabled",
"check.off.normal",
"check.off.highlighted",
"check.off.depressed",
"check.on.disabled",
"check.on.normal",
"check.on.highlighted",
"check.on.depressed"
};
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiOptionControl control, IFlatGuiGraphics graphics)
{
// Determine the index of the state we're going to display
var stateIndex = control.Selected ? 4 : 0;
if (control.Enabled)
{
if (control.Depressed)
stateIndex += 3;
else
{
if (control.MouseHovering)
stateIndex += 2;
else stateIndex += 1;
}
}
// Get the pixel coordinates of the region covered by the control on
// the screen
var controlBounds = control.GetAbsoluteBounds();
var width = controlBounds.Width;
// Now adjust the bounds to a square of height x height pixels so we can
// render the graphical portion of the option control
controlBounds.Width = controlBounds.Height;
graphics.DrawElement(_states[stateIndex], controlBounds);
// If the option has text assigned to it, render it too
if (control.Text != null)
{
// Restore the original width, then subtract the region that was covered by
// the graphical portion of the control.
controlBounds.Width = width - controlBounds.Height;
controlBounds.X += controlBounds.Height;
// Draw the text that was assigned to the option control
graphics.DrawString(_states[stateIndex], controlBounds, control.Text);
}
}
}
}
@@ -1,24 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders progress bars in a traditional flat style</summary>
public class FlatProgressControlRenderer : IFlatControlRenderer<GuiProgressControl>
{
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiProgressControl control, IFlatGuiGraphics graphics)
{
var controlBounds = control.GetAbsoluteBounds();
graphics.DrawElement("progress", controlBounds);
controlBounds.Width *= control.Progress;
graphics.DrawElement("progress.bar", controlBounds);
}
}
}
@@ -1,40 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders sliders in a traditional flat style</summary>
public class FlatVerticalSliderControlRenderer : IFlatControlRenderer<GuiVerticalSliderControl>
{
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(
GuiVerticalSliderControl control, IFlatGuiGraphics graphics
)
{
var controlBounds = control.GetAbsoluteBounds();
var thumbHeight = controlBounds.Height*control.ThumbSize;
var thumbY = (controlBounds.Height - thumbHeight)*control.ThumbPosition;
graphics.DrawElement("rail.vertical", controlBounds);
var thumbBounds = new RectangleF(
controlBounds.X, controlBounds.Y + thumbY, controlBounds.Width, thumbHeight
);
if (control.ThumbDepressed)
graphics.DrawElement("slider.vertical.depressed", thumbBounds);
else
{
if (control.MouseOverThumb)
graphics.DrawElement("slider.vertical.highlighted", thumbBounds);
else graphics.DrawElement("slider.vertical.normal", thumbBounds);
}
}
}
}
@@ -1,24 +0,0 @@
using MonoGame.Extended.NuclexGui.Controls.Desktop;
namespace MonoGame.Extended.NuclexGui.Visuals.Flat.Renderers
{
/// <summary>Renders window controls in a traditional flat style</summary>
public class FlatWindowControlRenderer : IFlatControlRenderer<GuiWindowControl>
{
/// <summary>
/// Renders the specified control using the provided graphics interface
/// </summary>
/// <param name="control">Control that will be rendered</param>
/// <param name="graphics">
/// Graphics interface that will be used to draw the control
/// </param>
public void Render(GuiWindowControl control, IFlatGuiGraphics graphics)
{
var controlBounds = control.GetAbsoluteBounds();
graphics.DrawElement("window", controlBounds);
if (control.Title != null)
graphics.DrawString("window", controlBounds, control.Title);
}
}
}
@@ -1,10 +0,0 @@
namespace MonoGame.Extended.NuclexGui.Visuals
{
/// <summary>Interface for an exchangeable GUI painter</summary>
public interface IGuiVisualizer
{
/// <summary>Renders an entire control tree starting at the provided control</summary>
/// <param name="screen">Screen containing the GUI that will be drawn</param>
void Draw(GuiScreen screen);
}
}