initial commit

Contains changes from "gwen-dotnet" removing dependancies on Windows,
which ultimately means certain features (e.g. file load/save dialogs)
do not work. Those classes still exist, but the code has been commented
out.
This commit is contained in:
Gered 2013-03-28 18:47:01 -04:00
commit 10e057953e
121 changed files with 18679 additions and 0 deletions

2
.gitignore vendored Normal file
View file

@ -0,0 +1,2 @@
GwenCS.userprefs
Gwen/bin

118
Gwen/Align.cs Normal file
View file

@ -0,0 +1,118 @@
using System;
using Gwen.Control;
namespace Gwen
{
/// <summary>
/// Utility class for manipulating control's position according to its parent. Rarely needed, use control.Dock.
/// </summary>
public static class Align
{
/// <summary>
/// Centers the control inside its parent.
/// </summary>
/// <param name="control">Control to center.</param>
public static void Center(Base control)
{
Base parent = control.Parent;
if (parent == null)
return;
control.SetPosition(
parent.Padding.Left + (((parent.Width - parent.Padding.Left - parent.Padding.Right) - control.Width)/2),
(parent.Height - control.Height)/2);
}
/// <summary>
/// Moves the control to the left of its parent.
/// </summary>
/// <param name="control"></param>
public static void AlignLeft(Base control)
{
Base parent = control.Parent;
if (null == parent) return;
control.SetPosition(parent.Padding.Left, control.Y);
}
/// <summary>
/// Centers the control horizontally inside its parent.
/// </summary>
/// <param name="control"></param>
public static void CenterHorizontally(Base control)
{
Base parent = control.Parent;
if (null == parent) return;
control.SetPosition(parent.Padding.Left + (((parent.Width - parent.Padding.Left - parent.Padding.Right) - control.Width) / 2), control.Y);
}
/// <summary>
/// Moves the control to the right of its parent.
/// </summary>
/// <param name="control"></param>
public static void AlignRight(Base control)
{
Base parent = control.Parent;
if (null == parent) return;
control.SetPosition(parent.Width - control.Width - parent.Padding.Right, control.Y);
}
/// <summary>
/// Moves the control to the top of its parent.
/// </summary>
/// <param name="control"></param>
public static void AlignTop(Base control)
{
control.SetPosition(control.X, 0);
}
/// <summary>
/// Centers the control vertically inside its parent.
/// </summary>
/// <param name="control"></param>
public static void CenterVertically(Base control)
{
Base parent = control.Parent;
if (null == parent) return;
control.SetPosition(control.X, (parent.Height - control.Height) / 2);
}
/// <summary>
/// Moves the control to the bottom of its parent.
/// </summary>
/// <param name="control"></param>
public static void AlignBottom(Base control)
{
Base parent = control.Parent;
if (null == parent) return;
control.SetPosition(control.X, parent.Height - control.Height);
}
/// <summary>
/// Places the control below other control (left aligned), taking margins into account.
/// </summary>
/// <param name="control">Control to place.</param>
/// <param name="anchor">Anchor control.</param>
/// <param name="spacing">Optional spacing.</param>
public static void PlaceDownLeft(Base control, Base anchor, int spacing = 0)
{
control.SetPosition(anchor.X, anchor.Bottom + spacing);
}
/// <summary>
/// Places the control to the right of other control (bottom aligned), taking margins into account.
/// </summary>
/// <param name="control">Control to place.</param>
/// <param name="anchor">Anchor control.</param>
/// <param name="spacing">Optional spacing.</param>
public static void PlaceRightBottom(Base control, Base anchor, int spacing = 0)
{
control.SetPosition(anchor.Right + spacing, anchor.Y - control.Height + anchor.Height);
}
}
}

57
Gwen/Anim/Animation.cs Normal file
View file

@ -0,0 +1,57 @@
using System;
using System.Collections.Generic;
using Gwen.Control;
namespace Gwen.Anim
{
public class Animation
{
protected Base m_Control;
//private static List<Animation> g_AnimationsListed = new List<Animation>(); // unused
private static readonly Dictionary<Base, List<Animation>> m_Animations = new Dictionary<Base, List<Animation>>();
protected virtual void Think()
{
}
public virtual bool Finished
{
get { throw new InvalidOperationException("Pure virtual function call"); }
}
public static void Add(Base control, Animation animation)
{
animation.m_Control = control;
if (!m_Animations.ContainsKey(control))
m_Animations[control] = new List<Animation>();
m_Animations[control].Add(animation);
}
public static void Cancel(Base control)
{
if (m_Animations.ContainsKey(control))
{
m_Animations[control].Clear();
m_Animations.Remove(control);
}
}
internal static void GlobalThink()
{
foreach (KeyValuePair<Base, List<Animation>> pair in m_Animations)
{
var valCopy = pair.Value.FindAll(x =>true); // list copy so foreach won't break when we remove elements
foreach (Animation animation in valCopy)
{
animation.Think();
if (animation.Finished)
{
pair.Value.Remove(animation);
}
}
}
}
}
}

38
Gwen/Anim/Size/Height.cs Normal file
View file

@ -0,0 +1,38 @@
using System;
namespace Gwen.Anim.Size
{
class Height : TimedAnimation
{
private int m_StartSize;
private int m_Delta;
private bool m_Hide;
public Height(int startSize, int endSize, float length, bool hide = false, float delay = 0.0f, float ease = 1.0f)
: base(length, delay, ease)
{
m_StartSize = startSize;
m_Delta = endSize - m_StartSize;
m_Hide = hide;
}
protected override void OnStart()
{
base.OnStart();
m_Control.Height = m_StartSize;
}
protected override void Run(float delta)
{
base.Run(delta);
m_Control.Height = (int)(m_StartSize + (m_Delta * delta));
}
protected override void OnFinish()
{
base.OnFinish();
m_Control.Height = m_StartSize + m_Delta;
m_Control.IsHidden = m_Hide;
}
}
}

38
Gwen/Anim/Size/Width.cs Normal file
View file

@ -0,0 +1,38 @@
using System;
namespace Gwen.Anim.Size
{
class Width : TimedAnimation
{
private int m_StartSize;
private int m_Delta;
private bool m_Hide;
public Width(int startSize, int endSize, float length, bool hide = false, float delay = 0.0f, float ease = 1.0f)
: base(length, delay, ease)
{
m_StartSize = startSize;
m_Delta = endSize - m_StartSize;
m_Hide = hide;
}
protected override void OnStart()
{
base.OnStart();
m_Control.Width = m_StartSize;
}
protected override void Run(float delta)
{
base.Run(delta);
m_Control.Width = (int)Math.Round(m_StartSize + (m_Delta * delta));
}
protected override void OnFinish()
{
base.OnFinish();
m_Control.Width = m_StartSize + m_Delta;
m_Control.IsHidden = m_Hide;
}
}
}

View file

@ -0,0 +1,69 @@
using System;
namespace Gwen.Anim
{
// Timed animation. Provides a useful base for animations.
public class TimedAnimation : Animation
{
private bool m_Started;
private bool m_Finished;
private float m_Start;
private float m_End;
private float m_Ease;
public override bool Finished { get { return m_Finished; } }
public TimedAnimation(float length, float delay = 0.0f, float ease = 1.0f)
{
m_Start = Platform.Neutral.GetTimeInSeconds() + delay;
m_End = m_Start + length;
m_Ease = ease;
m_Started = false;
m_Finished = false;
}
protected override void Think()
{
//base.Think();
if (m_Finished)
return;
float current = Platform.Neutral.GetTimeInSeconds();
float secondsIn = current - m_Start;
if (secondsIn < 0.0)
return;
if (!m_Started)
{
m_Started = true;
OnStart();
}
float delta = secondsIn / (m_End - m_Start);
if (delta < 0.0f)
delta = 0.0f;
if (delta > 1.0f)
delta = 1.0f;
Run((float)Math.Pow(delta, m_Ease));
if (delta == 1.0f)
{
m_Finished = true;
OnFinish();
}
}
// These are the magic functions you should be overriding
protected virtual void OnStart()
{ }
protected virtual void Run(float delta)
{ }
protected virtual void OnFinish()
{ }
}
}

2126
Gwen/Control/Base.cs Normal file

File diff suppressed because it is too large Load diff

318
Gwen/Control/Button.cs Normal file
View file

@ -0,0 +1,318 @@
using System;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Button control.
/// </summary>
public class Button : Label
{
private bool m_Depressed;
private bool m_Toggle;
private bool m_ToggleStatus;
private bool m_CenterImage;
private ImagePanel m_Image;
/// <summary>
/// Invoked when the button is released.
/// </summary>
public event GwenEventHandler Clicked;
/// <summary>
/// Invoked when the button is pressed.
/// </summary>
public event GwenEventHandler Pressed;
/// <summary>
/// Invoked when the button is released.
/// </summary>
public event GwenEventHandler Released;
/// <summary>
/// Invoked when the button's toggle state has changed.
/// </summary>
public event GwenEventHandler Toggled;
/// <summary>
/// Invoked when the button's toggle state has changed to On.
/// </summary>
public event GwenEventHandler ToggledOn;
/// <summary>
/// Invoked when the button's toggle state has changed to Off.
/// </summary>
public event GwenEventHandler ToggledOff;
/// <summary>
/// Invoked when the button has been double clicked.
/// </summary>
public event GwenEventHandler DoubleClickedLeft;
/// <summary>
/// Indicates whether the button is depressed.
/// </summary>
public bool IsDepressed
{
get { return m_Depressed; }
set
{
if (m_Depressed == value)
return;
m_Depressed = value;
Redraw();
}
}
/// <summary>
/// Indicates whether the button is toggleable.
/// </summary>
public bool IsToggle { get { return m_Toggle; } set { m_Toggle = value; } }
/// <summary>
/// Determines the button's toggle state.
/// </summary>
public bool ToggleState
{
get { return m_ToggleStatus; }
set
{
if (!m_Toggle) return;
if (m_ToggleStatus == value) return;
m_ToggleStatus = value;
if (Toggled != null)
Toggled.Invoke(this);
if (m_ToggleStatus)
{
if (ToggledOn != null)
ToggledOn.Invoke(this);
}
else
{
if (ToggledOff != null)
ToggledOff.Invoke(this);
}
Redraw();
}
}
/// <summary>
/// Control constructor.
/// </summary>
/// <param name="parent">Parent control.</param>
public Button(Base parent)
: base(parent)
{
SetSize(100, 20);
MouseInputEnabled = true;
Alignment = Pos.Center;
TextPadding = new Padding(3, 3, 3, 3);
}
/// <summary>
/// Toggles the button.
/// </summary>
public virtual void Toggle()
{
ToggleState = !ToggleState;
}
/// <summary>
/// "Clicks" the button.
/// </summary>
public virtual void Press(Base control = null)
{
OnClicked();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
base.Render(skin);
if (ShouldDrawBackground)
{
bool drawDepressed = IsDepressed && IsHovered;
if (IsToggle)
drawDepressed = drawDepressed || ToggleState;
bool bDrawHovered = IsHovered && ShouldDrawHover;
skin.DrawButton(this, drawDepressed, bDrawHovered, IsDisabled);
}
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
//base.OnMouseClickedLeft(x, y, down);
if (down)
{
IsDepressed = true;
InputHandler.MouseFocus = this;
if (Pressed != null)
Pressed.Invoke(this);
}
else
{
if (IsHovered && m_Depressed)
{
OnClicked();
}
IsDepressed = false;
InputHandler.MouseFocus = null;
if (Released != null)
Released.Invoke(this);
}
Redraw();
}
/// <summary>
/// Internal OnPressed implementation.
/// </summary>
protected virtual void OnClicked()
{
if (IsToggle)
{
Toggle();
}
if (Clicked != null)
Clicked.Invoke(this);
}
/// <summary>
/// Sets the button's image.
/// </summary>
/// <param name="textureName">Texture name. Null to remove.</param>
/// <param name="center">Determines whether the image should be centered.</param>
public virtual void SetImage(String textureName, bool center = false)
{
if (String.IsNullOrEmpty(textureName))
{
if (m_Image != null)
m_Image.Dispose();
m_Image = null;
return;
}
if (m_Image == null)
{
m_Image = new ImagePanel(this);
}
m_Image.ImageName = textureName;
m_Image.SizeToContents( );
m_Image.SetPosition(Math.Max(Padding.Left, 2), 2);
m_CenterImage = center;
TextPadding = new Padding(m_Image.Right + 2, TextPadding.Top, TextPadding.Right, TextPadding.Bottom);
}
/// <summary>
/// Sizes to contents.
/// </summary>
public override void SizeToContents()
{
base.SizeToContents();
if (m_Image != null)
{
int height = m_Image.Height + 4;
if (Height < height)
{
Height = height;
}
}
}
/// <summary>
/// Handler for Space keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeySpace(bool down)
{
if (down)
OnClicked();
return true;
}
/// <summary>
/// Default accelerator handler.
/// </summary>
protected override void OnAccelerator()
{
OnClicked();
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
if (m_Image != null)
{
Align.CenterVertically(m_Image);
if (m_CenterImage)
Align.CenterHorizontally(m_Image);
}
}
/// <summary>
/// Updates control colors.
/// </summary>
public override void UpdateColors()
{
if (IsDisabled)
{
TextColor = Skin.Colors.Button.Disabled;
return;
}
if (IsDepressed || ToggleState)
{
TextColor = Skin.Colors.Button.Down;
return;
}
if (IsHovered)
{
TextColor = Skin.Colors.Button.Hover;
return;
}
TextColor = Skin.Colors.Button.Normal;
}
/// <summary>
/// Handler invoked on mouse double click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
protected override void OnMouseDoubleClickedLeft(int x, int y)
{
OnMouseClickedLeft(x, y, true);
if (DoubleClickedLeft != null)
DoubleClickedLeft.Invoke(this);
}
}
}

298
Gwen/Control/Canvas.cs Normal file
View file

@ -0,0 +1,298 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using Gwen.Anim;
using Gwen.DragDrop;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Canvas control. It should be the root parent for all other controls.
/// </summary>
public class Canvas : Base
{
private bool m_NeedsRedraw;
private float m_Scale;
private Color m_BackgroundColor;
// [omeg] these are not created by us, so no disposing
internal Base FirstTab;
internal Base NextTab;
private readonly List<IDisposable> m_DisposeQueue; // dictionary for faster access?
/// <summary>
/// Scale for rendering.
/// </summary>
public float Scale
{
get { return m_Scale; }
set
{
if (m_Scale == value)
return;
m_Scale = value;
if (Skin != null && Skin.Renderer != null)
Skin.Renderer.Scale = m_Scale;
OnScaleChanged();
Redraw();
}
}
/// <summary>
/// Background color.
/// </summary>
public Color BackgroundColor { get { return m_BackgroundColor; } set { m_BackgroundColor = value; } }
/// <summary>
/// In most situations you will be rendering the canvas every frame.
/// But in some situations you will only want to render when there have been changes.
/// You can do this by checking NeedsRedraw.
/// </summary>
public bool NeedsRedraw { get { return m_NeedsRedraw; } set { m_NeedsRedraw = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="Canvas"/> class.
/// </summary>
/// <param name="skin">Skin to use.</param>
public Canvas(Skin.Base skin)
{
SetBounds(0, 0, 10000, 10000);
SetSkin(skin);
Scale = 1.0f;
BackgroundColor = Color.White;
ShouldDrawBackground = false;
m_DisposeQueue = new List<IDisposable>();
}
public override void Dispose()
{
ProcessDelayedDeletes();
base.Dispose();
}
/// <summary>
/// Re-renders the control, invalidates cached texture.
/// </summary>
public override void Redraw()
{
NeedsRedraw = true;
base.Redraw();
}
// Children call parent.GetCanvas() until they get to
// this top level function.
public override Canvas GetCanvas()
{
return this;
}
/// <summary>
/// Additional initialization (which is sometimes not appropriate in the constructor)
/// </summary>
protected void Initialize()
{
}
/// <summary>
/// Renders the canvas. Call in your rendering loop.
/// </summary>
public void RenderCanvas()
{
DoThink();
Renderer.Base render = Skin.Renderer;
render.Begin();
RecurseLayout(Skin);
render.ClipRegion = Bounds;
render.RenderOffset = Point.Empty;
render.Scale = Scale;
if (ShouldDrawBackground)
{
render.DrawColor = m_BackgroundColor;
render.DrawFilledRect(RenderBounds);
}
DoRender(Skin);
DragAndDrop.RenderOverlay(this, Skin);
Gwen.ToolTip.RenderToolTip(Skin);
render.EndClip();
render.End();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
//skin.Renderer.rnd = new Random(1);
base.Render(skin);
m_NeedsRedraw = false;
}
/// <summary>
/// Handler invoked when control's bounds change.
/// </summary>
/// <param name="oldBounds">Old bounds.</param>
protected override void OnBoundsChanged(Rectangle oldBounds)
{
base.OnBoundsChanged(oldBounds);
InvalidateChildren(true);
}
/// <summary>
/// Processes input and layout. Also purges delayed delete queue.
/// </summary>
private void DoThink()
{
if (IsHidden)
return;
Animation.GlobalThink();
// Reset tabbing
NextTab = null;
FirstTab = null;
ProcessDelayedDeletes();
// Check has focus etc..
RecurseLayout(Skin);
// If we didn't have a next tab, cycle to the start.
if (NextTab == null)
NextTab = FirstTab;
InputHandler.OnCanvasThink(this);
}
/// <summary>
/// Adds given control to the delete queue and detaches it from canvas. Don't call from Dispose, it modifies child list.
/// </summary>
/// <param name="control">Control to delete.</param>
public void AddDelayedDelete(Base control)
{
if (!m_DisposeQueue.Contains(control))
{
m_DisposeQueue.Add(control);
RemoveChild(control, false);
}
#if DEBUG
else
throw new InvalidOperationException("Control deleted twice");
#endif
}
private void ProcessDelayedDeletes()
{
//if (m_DisposeQueue.Count > 0)
// System.Diagnostics.Debug.Print("Canvas.ProcessDelayedDeletes: {0} items", m_DisposeQueue.Count);
foreach (IDisposable control in m_DisposeQueue)
{
control.Dispose();
}
m_DisposeQueue.Clear();
}
/// <summary>
/// Handles mouse movement events. Called from Input subsystems.
/// </summary>
/// <returns>True if handled.</returns>
public bool Input_MouseMoved(int x, int y, int dx, int dy)
{
if (IsHidden)
return false;
// Todo: Handle scaling here..
//float fScale = 1.0f / Scale();
InputHandler.OnMouseMoved(this, x, y, dx, dy);
if (InputHandler.HoveredControl == null) return false;
if (InputHandler.HoveredControl == this) return false;
if (InputHandler.HoveredControl.GetCanvas() != this) return false;
InputHandler.HoveredControl.InputMouseMoved(x, y, dx, dy);
InputHandler.HoveredControl.UpdateCursor();
DragAndDrop.OnMouseMoved(InputHandler.HoveredControl, x, y);
return true;
}
/// <summary>
/// Handles mouse button events. Called from Input subsystems.
/// </summary>
/// <returns>True if handled.</returns>
public bool Input_MouseButton(int button, bool down)
{
if (IsHidden) return false;
return InputHandler.OnMouseClicked(this, button, down);
}
/// <summary>
/// Handles keyboard events. Called from Input subsystems.
/// </summary>
/// <returns>True if handled.</returns>
public bool Input_Key(Key key, bool down)
{
if (IsHidden) return false;
if (key <= Key.Invalid) return false;
if (key >= Key.Count) return false;
return InputHandler.OnKeyEvent(this, key, down);
}
/// <summary>
/// Handles keyboard events. Called from Input subsystems.
/// </summary>
/// <returns>True if handled.</returns>
public bool Input_Character(char chr)
{
if (IsHidden) return false;
if (char.IsControl(chr)) return false;
//Handle Accelerators
if (InputHandler.HandleAccelerator(this, chr))
return true;
//Handle characters
if (InputHandler.KeyboardFocus == null) return false;
if (InputHandler.KeyboardFocus.GetCanvas() != this) return false;
if (!InputHandler.KeyboardFocus.IsVisible) return false;
if (InputHandler.IsControlDown) return false;
return InputHandler.KeyboardFocus.InputChar(chr);
}
/// <summary>
/// Handles the mouse wheel events. Called from Input subsystems.
/// </summary>
/// <returns>True if handled.</returns>
public bool Input_MouseWheel(int val)
{
if (IsHidden) return false;
if (InputHandler.HoveredControl == null) return false;
if (InputHandler.HoveredControl == this) return false;
if (InputHandler.HoveredControl.GetCanvas() != this) return false;
return InputHandler.HoveredControl.InputMouseWheeled(val);
}
}
}

113
Gwen/Control/CheckBox.cs Normal file
View file

@ -0,0 +1,113 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// CheckBox control.
/// </summary>
public class CheckBox : Button
{
private bool m_Checked;
/// <summary>
/// Indicates whether the checkbox is checked.
/// </summary>
public bool IsChecked
{
get { return m_Checked; }
set
{
if (m_Checked == value) return;
m_Checked = value;
OnCheckChanged();
}
}
/// <summary>
/// Initializes a new instance of the <see cref="CheckBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public CheckBox(Base parent)
: base(parent)
{
SetSize(15, 15);
//m_Checked = true; // [omeg] why?!
//Toggle();
}
/// <summary>
/// Toggles the checkbox.
/// </summary>
public override void Toggle()
{
base.Toggle();
IsChecked = !IsChecked;
}
/// <summary>
/// Invoked when the checkbox has been checked.
/// </summary>
public event GwenEventHandler Checked;
/// <summary>
/// Invoked when the checkbox has been unchecked.
/// </summary>
public event GwenEventHandler UnChecked;
/// <summary>
/// Invoked when the checkbox state has been changed.
/// </summary>
public event GwenEventHandler CheckChanged;
/// <summary>
/// Determines whether unchecking is allowed.
/// </summary>
protected virtual bool AllowUncheck { get { return true; } }
/// <summary>
/// Handler for CheckChanged event.
/// </summary>
protected virtual void OnCheckChanged()
{
if (IsChecked)
{
if (Checked != null)
Checked.Invoke(this);
}
else
{
if (UnChecked != null)
UnChecked.Invoke(this);
}
if (CheckChanged != null)
CheckChanged.Invoke(this);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
base.Render(skin);
skin.DrawCheckBox(this, m_Checked, IsDepressed);
}
/// <summary>
/// Internal OnPressed implementation.
/// </summary>
protected override void OnClicked()
{
if (IsDisabled)
return;
if (IsChecked && !AllowUncheck)
{
return;
}
Toggle();
}
}
}

View file

@ -0,0 +1,176 @@
using System;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// CollapsibleCategory control. Used in CollapsibleList.
/// </summary>
public class CollapsibleCategory : Base
{
private readonly Button m_HeaderButton;
private readonly CollapsibleList m_List;
/// <summary>
/// Header text.
/// </summary>
public String Text { get { return m_HeaderButton.Text; } set { m_HeaderButton.Text = value; } }
/// <summary>
/// Determines whether the category is collapsed (closed).
/// </summary>
public bool IsCollapsed { get { return m_HeaderButton.ToggleState; } set { m_HeaderButton.ToggleState = value; } }
/// <summary>
/// Invoked when an entry has been selected.
/// </summary>
public event GwenEventHandler Selected;
/// <summary>
/// Invoked when the category collapsed state has been changed (header button has been pressed).
/// </summary>
public event GwenEventHandler Collapsed;
/// <summary>
/// Initializes a new instance of the <see cref="CollapsibleCategory"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public CollapsibleCategory(CollapsibleList parent) : base(parent)
{
m_HeaderButton = new CategoryHeaderButton(this);
m_HeaderButton.Text = "Category Title"; // [omeg] todo: i18n
m_HeaderButton.Dock = Pos.Top;
m_HeaderButton.Height = 20;
m_HeaderButton.Toggled += OnHeaderToggle;
m_List = parent;
Padding = new Padding(1, 0, 1, 5);
SetSize(512, 512);
}
/// <summary>
/// Gets the selected entry.
/// </summary>
public Button GetSelectedButton()
{
foreach (Base child in Children)
{
CategoryButton button = child as CategoryButton;
if (button == null)
continue;
if (button.ToggleState)
return button;
}
return null;
}
/// <summary>
/// Handler for header button toggle event.
/// </summary>
/// <param name="control">Source control.</param>
protected virtual void OnHeaderToggle(Base control)
{
if (Collapsed != null)
Collapsed.Invoke(this);
}
/// <summary>
/// Handler for Selected event.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnSelected(Base control)
{
CategoryButton child = control as CategoryButton;
if (child == null) return;
if (m_List != null)
{
m_List.UnselectAll();
}
else
{
UnselectAll();
}
child.ToggleState = true;
if (Selected != null)
Selected.Invoke(this);
}
/// <summary>
/// Adds a new entry.
/// </summary>
/// <param name="name">Entry name (displayed).</param>
/// <returns>Newly created control.</returns>
public Button Add(String name)
{
CategoryButton button = new CategoryButton(this);
button.Text = name;
button.Dock = Pos.Top;
button.SizeToContents();
button.SetSize(button.Width + 4, button.Height + 4);
button.Padding = new Padding(5, 2, 2, 2);
button.Clicked += OnSelected;
return button;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawCategoryInner(this, m_HeaderButton.ToggleState);
base.Render(skin);
}
/// <summary>
/// Unselects all entries.
/// </summary>
public void UnselectAll()
{
foreach (Base child in Children)
{
CategoryButton button = child as CategoryButton;
if (button == null)
continue;
button.ToggleState = false;
}
}
/// <summary>
/// Function invoked after layout.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void PostLayout(Skin.Base skin)
{
if (IsCollapsed)
{
Height = m_HeaderButton.Height;
}
else
{
SizeToChildren(false, true);
}
// alternate row coloring
bool b = true;
foreach (Base child in Children)
{
CategoryButton button = child as CategoryButton;
if (button == null)
continue;
button.m_Alt = b;
button.UpdateColors();
b = !b;
}
}
}
}

View file

@ -0,0 +1,130 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// CollapsibleList control. Groups CollapsibleCategory controls.
/// </summary>
public class CollapsibleList : ScrollControl
{
/// <summary>
/// Invoked when an entry has been selected.
/// </summary>
public event GwenEventHandler ItemSelected;
/// <summary>
/// Invoked when a category collapsed state has been changed (header button has been pressed).
/// </summary>
public event GwenEventHandler CategoryCollapsed;
/// <summary>
/// Initializes a new instance of the <see cref="CollapsibleList"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public CollapsibleList(Base parent) : base(parent)
{
EnableScroll(false, true);
AutoHideBars = true;
}
// todo: iterator, make this as function? check if works
/// <summary>
/// Selected entry.
/// </summary>
public Button GetSelectedButton()
{
foreach (Base child in Children)
{
CollapsibleCategory cat = child as CollapsibleCategory;
if (cat == null)
continue;
Button button = cat.GetSelectedButton();
if (button != null)
return button;
}
return null;
}
/// <summary>
/// Adds a category to the list.
/// </summary>
/// <param name="category">Category control to add.</param>
protected virtual void Add(CollapsibleCategory category)
{
category.Parent = this;
category.Dock = Pos.Top;
category.Margin = new Margin(1, 0, 1, 1);
category.Selected += OnCategorySelected;
category.Collapsed += OnCategoryCollapsed;
// this relies on fact that category.m_List is set to its parent
}
/// <summary>
/// Adds a new category to the list.
/// </summary>
/// <param name="categoryName">Name of the category.</param>
/// <returns>Newly created control.</returns>
public virtual CollapsibleCategory Add(String categoryName)
{
CollapsibleCategory cat = new CollapsibleCategory(this);
cat.Text = categoryName;
Add(cat);
return cat;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawCategoryHolder(this);
base.Render(skin);
}
/// <summary>
/// Unselects all entries.
/// </summary>
public virtual void UnselectAll()
{
foreach (Base child in Children)
{
CollapsibleCategory cat = child as CollapsibleCategory;
if (cat == null)
continue;
cat.UnselectAll();
}
}
/// <summary>
/// Handler for ItemSelected event.
/// </summary>
/// <param name="control">Event source: <see cref="CollapsibleList"/>.</param>
protected virtual void OnCategorySelected(Base control)
{
CollapsibleCategory cat = control as CollapsibleCategory;
if (cat == null) return;
if (ItemSelected != null)
ItemSelected.Invoke(this);
}
/// <summary>
/// Handler for category collapsed event.
/// </summary>
/// <param name="control">Event source: <see cref="CollapsibleCategory"/>.</param>
protected virtual void OnCategoryCollapsed(Base control)
{
CollapsibleCategory cat = control as CollapsibleCategory;
if (cat == null) return;
if (CategoryCollapsed != null)
CategoryCollapsed.Invoke(control);
}
}
}

View file

@ -0,0 +1,207 @@
using System;
using System.Drawing;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Linear-interpolated HSV color box.
/// </summary>
public class ColorLerpBox : Base
{
private Point m_CursorPos;
private bool m_Depressed;
private float m_Hue;
private Texture m_Texture;
/// <summary>
/// Invoked when the selected color has been changed.
/// </summary>
public event GwenEventHandler ColorChanged;
/// <summary>
/// Initializes a new instance of the <see cref="ColorLerpBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ColorLerpBox(Base parent) : base(parent)
{
SetColor(Color.FromArgb(255, 255, 128, 0));
SetSize(128, 128);
MouseInputEnabled = true;
m_Depressed = false;
// texture is initialized in Render() if null
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public override void Dispose()
{
if (m_Texture != null)
m_Texture.Dispose();
base.Dispose();
}
/// <summary>
/// Linear color interpolation.
/// </summary>
public static Color Lerp(Color toColor, Color fromColor, float amount)
{
Color delta = toColor.Subtract(fromColor);
delta = delta.Multiply(amount);
return fromColor.Add(delta);
}
/// <summary>
/// Selected color.
/// </summary>
public Color SelectedColor
{
get { return GetColorAt(m_CursorPos.X, m_CursorPos.Y); }
}
/// <summary>
/// Sets the selected color.
/// </summary>
/// <param name="value">Value to set.</param>
/// <param name="onlyHue">Deetrmines whether to only set H value (not SV).</param>
public void SetColor(Color value, bool onlyHue = true)
{
HSV hsv = value.ToHSV();
m_Hue = hsv.h;
if (!onlyHue)
{
m_CursorPos.X = (int)(hsv.s * Width);
m_CursorPos.Y = (int)((1 - hsv.v) * Height);
}
Invalidate();
if (ColorChanged != null)
ColorChanged.Invoke(this);
}
/// <summary>
/// Handler invoked on mouse moved event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="dx">X change.</param>
/// <param name="dy">Y change.</param>
protected override void OnMouseMoved(int x, int y, int dx, int dy)
{
if (m_Depressed)
{
m_CursorPos = CanvasPosToLocal(new Point(x, y));
//Do we have clamp?
if (m_CursorPos.X < 0)
m_CursorPos.X = 0;
if (m_CursorPos.X > Width)
m_CursorPos.X = Width;
if (m_CursorPos.Y < 0)
m_CursorPos.Y = 0;
if (m_CursorPos.Y > Height)
m_CursorPos.Y = Height;
if (ColorChanged != null)
ColorChanged.Invoke(this);
}
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
m_Depressed = down;
if (down)
InputHandler.MouseFocus = this;
else
InputHandler.MouseFocus = null;
OnMouseMoved(x, y, 0, 0);
}
/// <summary>
/// Gets the color from specified coordinates.
/// </summary>
/// <param name="x">X</param>
/// <param name="y">Y</param>
/// <returns>Color value.</returns>
private Color GetColorAt(int x, int y)
{
float xPercent = (x / (float)Width);
float yPercent = 1 - (y / (float)Height);
Color result = Util.HSVToColor(m_Hue, xPercent, yPercent);
return result;
}
/// <summary>
/// Invalidates the control.
/// </summary>
public override void Invalidate()
{
if (m_Texture != null)
{
m_Texture.Dispose();
m_Texture = null;
}
base.Invalidate();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
if (m_Texture == null)
{
byte[] pixelData = new byte[Width*Height*4];
for (int x = 0; x < Width; x++)
{
for (int y = 0; y < Height; y++)
{
Color c = GetColorAt(x, y);
pixelData[4*(x + y*Width)] = c.R;
pixelData[4*(x + y*Width) + 1] = c.G;
pixelData[4*(x + y*Width) + 2] = c.B;
pixelData[4*(x + y*Width) + 3] = c.A;
}
}
m_Texture = new Texture(skin.Renderer);
m_Texture.Width = Width;
m_Texture.Height = Height;
m_Texture.LoadRaw(Width, Height, pixelData);
}
skin.Renderer.DrawColor = Color.White;
skin.Renderer.DrawTexturedRect(m_Texture, RenderBounds);
skin.Renderer.DrawColor = Color.Black;
skin.Renderer.DrawLinedRect(RenderBounds);
Color selected = SelectedColor;
if ((selected.R + selected.G + selected.B)/3 < 170)
skin.Renderer.DrawColor = Color.White;
else
skin.Renderer.DrawColor = Color.Black;
Rectangle testRect = new Rectangle(m_CursorPos.X - 3, m_CursorPos.Y - 3, 6, 6);
skin.Renderer.DrawShavedCornerRect(testRect);
base.Render(skin);
}
}
}

256
Gwen/Control/ColorPicker.cs Normal file
View file

@ -0,0 +1,256 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// RGBA color picker.
/// </summary>
public class ColorPicker : Base, IColorPicker
{
private Color m_Color;
/// <summary>
/// Selected color.
/// </summary>
public Color SelectedColor { get { return m_Color; } set { m_Color = value; UpdateControls(); } }
/// <summary>
/// Red value of the selected color.
/// </summary>
public int R { get { return m_Color.R; } set { m_Color = Color.FromArgb(m_Color.A, value, m_Color.G, m_Color.B); } }
/// <summary>
/// Green value of the selected color.
/// </summary>
public int G { get { return m_Color.G; } set { m_Color = Color.FromArgb(m_Color.A, m_Color.R, value, m_Color.B); } }
/// <summary>
/// Blue value of the selected color.
/// </summary>
public int B { get { return m_Color.B; } set { m_Color = Color.FromArgb(m_Color.A, m_Color.R, m_Color.G, value); } }
/// <summary>
/// Alpha value of the selected color.
/// </summary>
public int A { get { return m_Color.A; } set { m_Color = Color.FromArgb(value, m_Color.R, m_Color.G, m_Color.B); } }
/// <summary>
/// Invoked when the selected color has been changed.
/// </summary>
public event GwenEventHandler ColorChanged;
/// <summary>
/// Initializes a new instance of the <see cref="ColorPicker"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ColorPicker(Base parent) : base(parent)
{
MouseInputEnabled = true;
SetSize(256, 150);
CreateControls();
SelectedColor = Color.FromArgb(255, 50, 60, 70);
}
private void CreateColorControl(String name, int y)
{
const int colorSize = 12;
GroupBox colorGroup = new GroupBox(this);
colorGroup.SetPosition(10, y);
colorGroup.SetText(name);
colorGroup.SetSize(160, 35);
colorGroup.Name = name + "groupbox";
ColorDisplay disp = new ColorDisplay(colorGroup);
disp.Name = name;
disp.SetBounds(0, 10, colorSize, colorSize);
TextBoxNumeric numeric = new TextBoxNumeric(colorGroup);
numeric.Name = name + "Box";
numeric.SetPosition(105, 7);
numeric.SetSize(26, 16);
numeric.SelectAllOnFocus = true;
numeric.TextChanged += NumericTyped;
HorizontalSlider slider = new HorizontalSlider(colorGroup);
slider.SetPosition(colorSize + 5, 10);
slider.SetRange(0, 255);
slider.SetSize(80, colorSize);
slider.Name = name + "Slider";
slider.ValueChanged += SlidersMoved;
}
private void NumericTyped(Base control)
{
TextBoxNumeric box = control as TextBoxNumeric;
if (null == box)
return;
if (box.Text == string.Empty)
return;
int textValue = (int) box.Value;
if (textValue < 0) textValue = 0;
if (textValue > 255) textValue = 255;
if (box.Name.Contains("Red"))
R = textValue;
if (box.Name.Contains("Green"))
G = textValue;
if (box.Name.Contains("Blue"))
B = textValue;
if (box.Name.Contains("Alpha"))
A = textValue;
UpdateControls();
}
private void CreateControls()
{
const int startY = 5;
const int height = 35;
CreateColorControl("Red", startY);
CreateColorControl("Green", startY + height);
CreateColorControl("Blue", startY + height*2);
CreateColorControl("Alpha", startY + height*3);
GroupBox finalGroup = new GroupBox(this);
finalGroup.SetPosition(180, 40);
finalGroup.SetSize(60, 60);
finalGroup.SetText("Result");
finalGroup.Name = "ResultGroupBox";
ColorDisplay disp = new ColorDisplay(finalGroup);
disp.Name = "Result";
disp.SetBounds(0, 10, 32, 32);
//disp.DrawCheckers = true;
//UpdateControls();
}
private void UpdateColorControls(String name, Color col, int sliderVal)
{
ColorDisplay disp = FindChildByName(name, true) as ColorDisplay;
disp.Color = col;
HorizontalSlider slider = FindChildByName(name + "Slider", true) as HorizontalSlider;
slider.Value = sliderVal;
TextBoxNumeric box = FindChildByName(name + "Box", true) as TextBoxNumeric;
box.Value = sliderVal;
}
private void UpdateControls()
{ //This is a little weird, but whatever for now
UpdateColorControls("Red", Color.FromArgb(255, SelectedColor.R, 0, 0), SelectedColor.R);
UpdateColorControls("Green", Color.FromArgb(255, 0, SelectedColor.G, 0), SelectedColor.G);
UpdateColorControls("Blue", Color.FromArgb(255, 0, 0, SelectedColor.B), SelectedColor.B);
UpdateColorControls("Alpha", Color.FromArgb(SelectedColor.A, 255, 255, 255), SelectedColor.A);
ColorDisplay disp = FindChildByName("Result", true) as ColorDisplay;
disp.Color = SelectedColor;
if (ColorChanged != null)
ColorChanged.Invoke(this);
}
private void SlidersMoved(Base control)
{
/*
HorizontalSlider* redSlider = gwen_cast<HorizontalSlider>( FindChildByName( "RedSlider", true ) );
HorizontalSlider* greenSlider = gwen_cast<HorizontalSlider>( FindChildByName( "GreenSlider", true ) );
HorizontalSlider* blueSlider = gwen_cast<HorizontalSlider>( FindChildByName( "BlueSlider", true ) );
HorizontalSlider* alphaSlider = gwen_cast<HorizontalSlider>( FindChildByName( "AlphaSlider", true ) );
*/
HorizontalSlider slider = control as HorizontalSlider;
if (slider != null)
SetColorByName(GetColorFromName(slider.Name), (int)slider.Value);
UpdateControls();
//SetColor( Gwen::Color( redSlider->GetValue(), greenSlider->GetValue(), blueSlider->GetValue(), alphaSlider->GetValue() ) );
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
SizeToChildren(false, true);
SetSize(Width, Height + 5);
GroupBox groupBox = FindChildByName("ResultGroupBox", true) as GroupBox;
if (groupBox != null)
groupBox.SetPosition(groupBox.X, Height * 0.5f - groupBox.Height * 0.5f);
//UpdateControls(); // this spams events continuously every tick
}
private int GetColorByName(String colorName)
{
if (colorName == "Red")
return SelectedColor.R;
if (colorName == "Green")
return SelectedColor.G;
if (colorName == "Blue")
return SelectedColor.B;
if (colorName == "Alpha")
return SelectedColor.A;
return 0;
}
private static String GetColorFromName(String name)
{
if (name.Contains("Red"))
return "Red";
if (name.Contains("Green"))
return "Green";
if (name.Contains("Blue"))
return "Blue";
if (name.Contains("Alpha"))
return "Alpha";
return String.Empty;
}
private void SetColorByName(String colorName, int colorValue)
{
if (colorName == "Red")
R = colorValue;
else if (colorName == "Green")
G = colorValue;
else if (colorName == "Blue")
B = colorValue;
else if (colorName == "Alpha")
A = colorValue;
}
/// <summary>
/// Determines whether the Alpha control is visible.
/// </summary>
public bool AlphaVisible
{
get
{
GroupBox gb = FindChildByName("Alphagroupbox", true) as GroupBox;
return !gb.IsHidden;
}
set
{
GroupBox gb = FindChildByName("Alphagroupbox", true) as GroupBox;
gb.IsHidden = !value;
Invalidate();
}
}
}
}

152
Gwen/Control/ColorSlider.cs Normal file
View file

@ -0,0 +1,152 @@
using System;
using System.Drawing;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// HSV hue selector.
/// </summary>
public class ColorSlider : Base
{
private int m_SelectedDist;
private bool m_Depressed;
private Texture m_Texture;
/// <summary>
/// Invoked when the selected color has been changed.
/// </summary>
public event GwenEventHandler ColorChanged;
/// <summary>
/// Initializes a new instance of the <see cref="ColorSlider"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ColorSlider(Base parent)
: base(parent)
{
SetSize(32, 128);
MouseInputEnabled = true;
m_Depressed = false;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public override void Dispose()
{
if (m_Texture != null)
m_Texture.Dispose();
base.Dispose();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
//Is there any way to move this into skin? Not for now, no idea how we'll "actually" render these
if (m_Texture == null)
{
byte[] pixelData = new byte[Width * Height * 4];
for (int y = 0; y < Height; y++)
{
Color c = GetColorAtHeight(y);
for (int x = 0; x < Width; x++)
{
pixelData[4 * (x + y * Width)] = c.R;
pixelData[4 * (x + y * Width) + 1] = c.G;
pixelData[4 * (x + y * Width) + 2] = c.B;
pixelData[4 * (x + y * Width) + 3] = c.A;
}
}
m_Texture = new Texture(skin.Renderer);
m_Texture.Width = Width;
m_Texture.Height = Height;
m_Texture.LoadRaw(Width, Height, pixelData);
}
skin.Renderer.DrawColor = Color.White;
skin.Renderer.DrawTexturedRect(m_Texture, new Rectangle(5, 0, Width-10, Height));
int drawHeight = m_SelectedDist - 3;
//Draw our selectors
skin.Renderer.DrawColor = Color.Black;
skin.Renderer.DrawFilledRect(new Rectangle(0, drawHeight + 2, Width, 1));
skin.Renderer.DrawFilledRect(new Rectangle(0, drawHeight, 5, 5));
skin.Renderer.DrawFilledRect(new Rectangle(Width - 5, drawHeight, 5, 5));
skin.Renderer.DrawColor = Color.White;
skin.Renderer.DrawFilledRect(new Rectangle(1, drawHeight + 1, 3, 3));
skin.Renderer.DrawFilledRect(new Rectangle(Width - 4, drawHeight + 1, 3, 3));
base.Render(skin);
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
m_Depressed = down;
if (down)
InputHandler.MouseFocus = this;
else
InputHandler.MouseFocus = null;
OnMouseMoved(x, y, 0, 0);
}
/// <summary>
/// Handler invoked on mouse moved event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="dx">X change.</param>
/// <param name="dy">Y change.</param>
protected override void OnMouseMoved(int x, int y, int dx, int dy)
{
if (m_Depressed)
{
Point cursorPos = CanvasPosToLocal(new Point(x, y));
if (cursorPos.Y < 0)
cursorPos.Y = 0;
if (cursorPos.Y > Height)
cursorPos.Y = Height;
m_SelectedDist = cursorPos.Y;
if (ColorChanged != null)
ColorChanged.Invoke(this);
}
}
private Color GetColorAtHeight(int y)
{
float yPercent = y / (float)Height;
return Util.HSVToColor(yPercent * 360, 1, 1);
}
private void SetColor(Color color)
{
HSV hsv = color.ToHSV();
m_SelectedDist = (int)(hsv.h / 360 * Height);
if (ColorChanged != null)
ColorChanged.Invoke(this);
}
/// <summary>
/// Selected color.
/// </summary>
public Color SelectedColor { get { return GetColorAtHeight(m_SelectedDist); } set { SetColor(value); } }
}
}

238
Gwen/Control/ComboBox.cs Normal file
View file

@ -0,0 +1,238 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// ComboBox control.
/// </summary>
public class ComboBox : Button
{
private readonly Menu m_Menu;
private readonly Base m_Button;
private MenuItem m_SelectedItem;
/// <summary>
/// Invoked when the selected item has changed.
/// </summary>
public event GwenEventHandler ItemSelected;
/// <summary>
/// Indicates whether the combo menu is open.
/// </summary>
public bool IsOpen { get { return m_Menu != null && !m_Menu.IsHidden; } }
/// <summary>
/// Initializes a new instance of the <see cref="ComboBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ComboBox(Base parent)
: base(parent)
{
SetSize(100, 20);
m_Menu = new Menu(this);
m_Menu.IsHidden = true;
m_Menu.IconMarginDisabled = true;
m_Menu.IsTabable = false;
DownArrow arrow = new DownArrow(this);
m_Button = arrow;
Alignment = Pos.Left | Pos.CenterV;
Text = String.Empty;
Margin = new Margin(3, 0, 0, 0);
IsTabable = true;
KeyboardInputEnabled = true;
}
/// <summary>
/// Selected item.
/// </summary>
/// <remarks>Not just String property, because items also have internal names.</remarks>
public Label SelectedItem { get { return m_SelectedItem; } }
internal override bool IsMenuComponent
{
get { return true; }
}
/// <summary>
/// Adds a new item.
/// </summary>
/// <param name="label">Item label (displayed).</param>
/// <param name="name">Item name.</param>
/// <returns>Newly created control.</returns>
public virtual MenuItem AddItem(String label, String name = "")
{
MenuItem item = m_Menu.AddItem(label, String.Empty);
item.Name = name;
item.Selected += OnItemSelected;
if (m_SelectedItem == null)
OnItemSelected(item);
return item;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawComboBox(this, IsDepressed, IsOpen);
}
/// <summary>
/// Internal Pressed implementation.
/// </summary>
protected override void OnClicked()
{
if (IsOpen)
{
GetCanvas().CloseMenus();
return;
}
bool wasMenuHidden = m_Menu.IsHidden;
GetCanvas().CloseMenus();
if (wasMenuHidden)
{
Open();
}
}
/// <summary>
/// Removes all items.
/// </summary>
public virtual void DeleteAll()
{
if (m_Menu != null)
m_Menu.DeleteAll();
}
/// <summary>
/// Internal handler for item selected event.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnItemSelected(Base control)
{
//Convert selected to a menu item
MenuItem item = control as MenuItem;
if (null == item) return;
m_SelectedItem = item;
Text = m_SelectedItem.Text;
m_Menu.IsHidden = true;
if (ItemSelected != null)
ItemSelected.Invoke(this);
Focus();
Invalidate();
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
m_Button.Position(Pos.Right|Pos.CenterV, 4, 0);
base.Layout(skin);
}
/// <summary>
/// Handler for losing keyboard focus.
/// </summary>
protected override void OnLostKeyboardFocus()
{
TextColor = Color.Black;
}
/// <summary>
/// Handler for gaining keyboard focus.
/// </summary>
protected override void OnKeyboardFocus()
{
//Until we add the blue highlighting again
TextColor = Color.Black;
}
/// <summary>
/// Opens the combo.
/// </summary>
public virtual void Open()
{
if (null == m_Menu) return;
m_Menu.Parent = GetCanvas();
m_Menu.IsHidden = false;
m_Menu.BringToFront();
Point p = LocalPosToCanvas(Point.Empty);
m_Menu.SetBounds(new Rectangle(p.X, p.Y + Height, Width, m_Menu.Height));
}
/// <summary>
/// Closes the combo.
/// </summary>
public virtual void Close()
{
if (m_Menu == null)
return;
m_Menu.Hide();
}
/// <summary>
/// Handler for Down Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyDown(bool down)
{
if (down)
{
var it = m_Menu.Children.FindIndex(x => x == m_SelectedItem);
if (it + 1 < m_Menu.Children.Count)
OnItemSelected(m_Menu.Children[it + 1]);
}
return true;
}
/// <summary>
/// Handler for Up Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyUp(bool down)
{
if (down)
{
var it = m_Menu.Children.FindLastIndex(x => x == m_SelectedItem);
if (it - 1 >= 0)
OnItemSelected(m_Menu.Children[it - 1]);
}
return true;
}
/// <summary>
/// Renders the focus overlay.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderFocus(Skin.Base skin)
{
}
}
}

View file

@ -0,0 +1,282 @@
using System;
//using System.Windows.Forms;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Splitter control.
/// </summary>
public class CrossSplitter : Base
{
private readonly SplitterBar m_VSplitter;
private readonly SplitterBar m_HSplitter;
private readonly SplitterBar m_CSplitter;
private readonly Base[] m_Sections;
private float m_HVal; // 0-1
private float m_VVal; // 0-1
private int m_BarSize; // pixels
private int m_ZoomedSection; // 0-3
/// <summary>
/// Invoked when one of the panels has been zoomed (maximized).
/// </summary>
public event GwenEventHandler PanelZoomed;
/// <summary>
/// Invoked when one of the panels has been unzoomed (restored).
/// </summary>
public event GwenEventHandler PanelUnZoomed;
/// <summary>
/// Invoked when the zoomed panel has been changed.
/// </summary>
public event GwenEventHandler ZoomChanged;
/// <summary>
/// Initializes a new instance of the <see cref="CrossSplitter"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public CrossSplitter(Base parent)
: base(parent)
{
m_Sections = new Base[4];
m_VSplitter = new SplitterBar(this);
m_VSplitter.SetPosition(0, 128);
m_VSplitter.Dragged += OnVerticalMoved;
//m_VSplitter.Cursor = Cursors.SizeNS;
m_HSplitter = new SplitterBar(this);
m_HSplitter.SetPosition(128, 0);
m_HSplitter.Dragged += OnHorizontalMoved;
//m_HSplitter.Cursor = Cursors.SizeWE;
m_CSplitter = new SplitterBar(this);
m_CSplitter.SetPosition(128, 128);
m_CSplitter.Dragged += OnCenterMoved;
//m_CSplitter.Cursor = Cursors.SizeAll;
m_HVal = 0.5f;
m_VVal = 0.5f;
SetPanel(0, null);
SetPanel(1, null);
SetPanel(2, null);
SetPanel(3, null);
SplitterSize = 5;
SplittersVisible = false;
m_ZoomedSection = -1;
}
/// <summary>
/// Centers the panels so that they take even amount of space.
/// </summary>
public void CenterPanels()
{
m_HVal = 0.5f;
m_VVal = 0.5f;
Invalidate();
}
/// <summary>
/// Indicates whether any of the panels is zoomed.
/// </summary>
public bool IsZoomed { get { return m_ZoomedSection != -1; } }
/// <summary>
/// Gets or sets a value indicating whether splitters should be visible.
/// </summary>
public bool SplittersVisible
{
get { return m_CSplitter.ShouldDrawBackground; }
set
{
m_CSplitter.ShouldDrawBackground = value;
m_VSplitter.ShouldDrawBackground = value;
m_HSplitter.ShouldDrawBackground = value;
}
}
/// <summary>
/// Gets or sets the size of the splitter.
/// </summary>
public int SplitterSize { get { return m_BarSize; } set { m_BarSize = value; } }
private void UpdateVSplitter()
{
m_VSplitter.MoveTo(m_VSplitter.X, (Height - m_VSplitter.Height) * (m_VVal));
}
private void UpdateHSplitter()
{
m_HSplitter.MoveTo( ( Width - m_HSplitter.Width ) * ( m_HVal ), m_HSplitter.Y );
}
private void UpdateCSplitter()
{
m_CSplitter.MoveTo((Width - m_CSplitter.Width) * (m_HVal), (Height - m_CSplitter.Height) * (m_VVal));
}
protected void OnCenterMoved(Base control)
{
CalculateValueCenter();
Invalidate();
}
protected void OnVerticalMoved(Base control)
{
m_VVal = CalculateValueVertical();
Invalidate();
}
protected void OnHorizontalMoved(Base control)
{
m_HVal = CalculateValueHorizontal();
Invalidate();
}
private void CalculateValueCenter()
{
m_HVal = m_CSplitter.X / (float)(Width - m_CSplitter.Width);
m_VVal = m_CSplitter.Y / (float)(Height - m_CSplitter.Height);
}
private float CalculateValueVertical()
{
return m_VSplitter.Y / (float)(Height - m_VSplitter.Height);
}
private float CalculateValueHorizontal()
{
return m_HSplitter.X / (float)(Width - m_HSplitter.Width);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
m_VSplitter.SetSize(Width, m_BarSize);
m_HSplitter.SetSize(m_BarSize, Height);
m_CSplitter.SetSize(m_BarSize, m_BarSize);
UpdateVSplitter();
UpdateHSplitter();
UpdateCSplitter();
if (m_ZoomedSection == -1)
{
if (m_Sections[0] != null)
m_Sections[0].SetBounds(0, 0, m_HSplitter.X, m_VSplitter.Y);
if (m_Sections[1] != null)
m_Sections[1].SetBounds(m_HSplitter.X + m_BarSize, 0, Width - (m_HSplitter.X + m_BarSize), m_VSplitter.Y);
if (m_Sections[2] != null)
m_Sections[2].SetBounds(0, m_VSplitter.Y + m_BarSize, m_HSplitter.X, Height - (m_VSplitter.Y + m_BarSize));
if (m_Sections[3] != null)
m_Sections[3].SetBounds(m_HSplitter.X + m_BarSize, m_VSplitter.Y + m_BarSize, Width - (m_HSplitter.X + m_BarSize), Height - (m_VSplitter.Y + m_BarSize));
}
else
{
//This should probably use Fill docking instead
m_Sections[m_ZoomedSection].SetBounds(0, 0, Width, Height);
}
}
/// <summary>
/// Assigns a control to the specific inner section.
/// </summary>
/// <param name="index">Section index (0-3).</param>
/// <param name="panel">Control to assign.</param>
public void SetPanel(int index, Base panel)
{
m_Sections[index] = panel;
if (panel != null)
{
panel.Dock = Pos.None;
panel.Parent = this;
}
Invalidate();
}
/// <summary>
/// Gets the specific inner section.
/// </summary>
/// <param name="index">Section index (0-3).</param>
/// <returns>Specified section.</returns>
public Base GetPanel(int index)
{
return m_Sections[index];
}
/// <summary>
/// Internal handler for the zoom changed event.
/// </summary>
protected void OnZoomChanged()
{
if (ZoomChanged != null)
ZoomChanged.Invoke(this);
if (m_ZoomedSection == -1)
{
if (PanelUnZoomed != null)
PanelUnZoomed.Invoke(this);
}
else
{
if (PanelZoomed != null)
PanelZoomed.Invoke(this);
}
}
/// <summary>
/// Maximizes the specified panel so it fills the entire control.
/// </summary>
/// <param name="section">Panel index (0-3).</param>
public void Zoom(int section)
{
UnZoom();
if (m_Sections[section] != null)
{
for (int i = 0; i < 4; i++)
{
if (i != section && m_Sections[i] != null)
m_Sections[i].IsHidden = true;
}
m_ZoomedSection = section;
Invalidate();
}
OnZoomChanged();
}
/// <summary>
/// Restores the control so all panels are visible.
/// </summary>
public void UnZoom()
{
m_ZoomedSection = -1;
for (int i = 0; i < 4; i++)
{
if (m_Sections[i] != null)
m_Sections[i].IsHidden = false;
}
Invalidate();
OnZoomChanged();
}
}
}

440
Gwen/Control/DockBase.cs Normal file
View file

@ -0,0 +1,440 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
using Gwen.DragDrop;
namespace Gwen.Control
{
/// <summary>
/// Base for dockable containers.
/// </summary>
public class DockBase : Base
{
private DockBase m_Left;
private DockBase m_Right;
private DockBase m_Top;
private DockBase m_Bottom;
private Resizer m_Sizer;
// Only CHILD dockpanels have a tabcontrol.
private DockedTabControl m_DockedTabControl;
private bool m_DrawHover;
private bool m_DropFar;
private Rectangle m_HoverRect;
// todo: dock events?
/// <summary>
/// Control docked on the left side.
/// </summary>
public DockBase LeftDock { get { return GetChildDock(Pos.Left); } }
/// <summary>
/// Control docked on the right side.
/// </summary>
public DockBase RightDock { get { return GetChildDock(Pos.Right); } }
/// <summary>
/// Control docked on the top side.
/// </summary>
public DockBase TopDock { get { return GetChildDock(Pos.Top); } }
/// <summary>
/// Control docked on the bottom side.
/// </summary>
public DockBase BottomDock { get { return GetChildDock(Pos.Bottom); } }
public TabControl TabControl { get { return m_DockedTabControl; } }
/// <summary>
/// Initializes a new instance of the <see cref="DockBase"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public DockBase(Base parent)
: base(parent)
{
Padding = Padding.One;
SetSize(200, 200);
}
/// <summary>
/// Handler for Space keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeySpace(bool down)
{
// No action on space (default button action is to press)
return false;
}
/// <summary>
/// Initializes an inner docked control for the specified position.
/// </summary>
/// <param name="pos">Dock position.</param>
protected virtual void SetupChildDock(Pos pos)
{
if (m_DockedTabControl == null)
{
m_DockedTabControl = new DockedTabControl(this);
m_DockedTabControl.TabRemoved += OnTabRemoved;
m_DockedTabControl.TabStripPosition = Pos.Bottom;
m_DockedTabControl.TitleBarVisible = true;
}
Dock = pos;
Pos sizeDir;
if (pos == Pos.Right) sizeDir = Pos.Left;
else if (pos == Pos.Left) sizeDir = Pos.Right;
else if (pos == Pos.Top) sizeDir = Pos.Bottom;
else if (pos == Pos.Bottom) sizeDir = Pos.Top;
else throw new ArgumentException("Invalid dock", "pos");
if (m_Sizer != null)
m_Sizer.Dispose();
m_Sizer = new Resizer(this);
m_Sizer.Dock = sizeDir;
m_Sizer.ResizeDir = sizeDir;
m_Sizer.SetSize(2, 2);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
}
/// <summary>
/// Gets an inner docked control for the specified position.
/// </summary>
/// <param name="pos"></param>
/// <returns></returns>
protected virtual DockBase GetChildDock(Pos pos)
{
// todo: verify
DockBase dock = null;
switch (pos)
{
case Pos.Left:
if (m_Left == null)
{
m_Left = new DockBase(this);
m_Left.SetupChildDock(pos);
}
dock = m_Left;
break;
case Pos.Right:
if (m_Right == null)
{
m_Right = new DockBase(this);
m_Right.SetupChildDock(pos);
}
dock = m_Right;
break;
case Pos.Top:
if (m_Top == null)
{
m_Top = new DockBase(this);
m_Top.SetupChildDock(pos);
}
dock = m_Top;
break;
case Pos.Bottom:
if (m_Bottom == null)
{
m_Bottom = new DockBase(this);
m_Bottom.SetupChildDock(pos);
}
dock = m_Bottom;
break;
}
if (dock != null)
dock.IsHidden = false;
return dock;
}
/// <summary>
/// Calculates dock direction from dragdrop coordinates.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <returns>Dock direction.</returns>
protected virtual Pos GetDroppedTabDirection(int x, int y)
{
int w = Width;
int h = Height;
float top = y / (float)h;
float left = x / (float)w;
float right = (w - x) / (float)w;
float bottom = (h - y) / (float)h;
float minimum = Math.Min(Math.Min(Math.Min(top, left), right), bottom);
m_DropFar = (minimum < 0.2f);
if (minimum > 0.3f)
return Pos.Fill;
if (top == minimum && (null == m_Top || m_Top.IsHidden))
return Pos.Top;
if (left == minimum && (null == m_Left || m_Left.IsHidden))
return Pos.Left;
if (right == minimum && (null == m_Right || m_Right.IsHidden))
return Pos.Right;
if (bottom == minimum && (null == m_Bottom || m_Bottom.IsHidden))
return Pos.Bottom;
return Pos.Fill;
}
public override bool DragAndDrop_CanAcceptPackage(Package p)
{
// A TAB button dropped
if (p.Name == "TabButtonMove")
return true;
// a TAB window dropped
if (p.Name == "TabWindowMove")
return true;
return false;
}
public override bool DragAndDrop_HandleDrop(Package p, int x, int y)
{
Point pos = CanvasPosToLocal(new Point(x, y));
Pos dir = GetDroppedTabDirection(pos.X, pos.Y);
DockedTabControl addTo = m_DockedTabControl;
if (dir == Pos.Fill && addTo == null)
return false;
if (dir != Pos.Fill)
{
DockBase dock = GetChildDock(dir);
addTo = dock.m_DockedTabControl;
if (!m_DropFar)
dock.BringToFront();
else
dock.SendToBack();
}
if (p.Name == "TabButtonMove")
{
TabButton tabButton = DragAndDrop.SourceControl as TabButton;
if (null == tabButton)
return false;
addTo.AddPage(tabButton);
}
if (p.Name == "TabWindowMove")
{
DockedTabControl tabControl = DragAndDrop.SourceControl as DockedTabControl;
if (null == tabControl)
return false;
if (tabControl == addTo)
return false;
tabControl.MoveTabsTo(addTo);
}
Invalidate();
return true;
}
/// <summary>
/// Indicates whether the control contains any docked children.
/// </summary>
public virtual bool IsEmpty
{
get
{
if (m_DockedTabControl != null && m_DockedTabControl.TabCount > 0) return false;
if (m_Left != null && !m_Left.IsEmpty) return false;
if (m_Right != null && !m_Right.IsEmpty) return false;
if (m_Top != null && !m_Top.IsEmpty) return false;
if (m_Bottom != null && !m_Bottom.IsEmpty) return false;
return true;
}
}
protected virtual void OnTabRemoved(Base control)
{
DoRedundancyCheck();
DoConsolidateCheck();
}
protected virtual void DoRedundancyCheck()
{
if (!IsEmpty) return;
DockBase pDockParent = Parent as DockBase;
if (null == pDockParent) return;
pDockParent.OnRedundantChildDock(this);
}
protected virtual void DoConsolidateCheck()
{
if (IsEmpty) return;
if (null == m_DockedTabControl) return;
if (m_DockedTabControl.TabCount > 0) return;
if (m_Bottom != null && !m_Bottom.IsEmpty)
{
m_Bottom.m_DockedTabControl.MoveTabsTo(m_DockedTabControl);
return;
}
if (m_Top != null && !m_Top.IsEmpty)
{
m_Top.m_DockedTabControl.MoveTabsTo(m_DockedTabControl);
return;
}
if (m_Left != null && !m_Left.IsEmpty)
{
m_Left.m_DockedTabControl.MoveTabsTo(m_DockedTabControl);
return;
}
if (m_Right != null && !m_Right.IsEmpty)
{
m_Right.m_DockedTabControl.MoveTabsTo(m_DockedTabControl);
return;
}
}
protected virtual void OnRedundantChildDock(DockBase dock)
{
dock.IsHidden = true;
DoRedundancyCheck();
DoConsolidateCheck();
}
public override void DragAndDrop_HoverEnter(Package p, int x, int y)
{
m_DrawHover = true;
}
public override void DragAndDrop_HoverLeave(Package p)
{
m_DrawHover = false;
}
public override void DragAndDrop_Hover(Package p, int x, int y)
{
Point pos = CanvasPosToLocal(new Point(x, y));
Pos dir = GetDroppedTabDirection(pos.X, pos.Y);
if (dir == Pos.Fill)
{
if (null == m_DockedTabControl)
{
m_HoverRect = Rectangle.Empty;
return;
}
m_HoverRect = InnerBounds;
return;
}
m_HoverRect = RenderBounds;
int HelpBarWidth = 0;
if (dir == Pos.Left)
{
HelpBarWidth = (int)(m_HoverRect.Width * 0.25f);
m_HoverRect.Width = HelpBarWidth;
}
if (dir == Pos.Right)
{
HelpBarWidth = (int)(m_HoverRect.Width * 0.25f);
m_HoverRect.X = m_HoverRect.Width - HelpBarWidth;
m_HoverRect.Width = HelpBarWidth;
}
if (dir == Pos.Top)
{
HelpBarWidth = (int)(m_HoverRect.Height * 0.25f);
m_HoverRect.Height = HelpBarWidth;
}
if (dir == Pos.Bottom)
{
HelpBarWidth = (int)(m_HoverRect.Height * 0.25f);
m_HoverRect.Y = m_HoverRect.Height - HelpBarWidth;
m_HoverRect.Height = HelpBarWidth;
}
if ((dir == Pos.Top || dir == Pos.Bottom) && !m_DropFar)
{
if (m_Left != null && m_Left.IsVisible)
{
m_HoverRect.X += m_Left.Width;
m_HoverRect.Width -= m_Left.Width;
}
if (m_Right != null && m_Right.IsVisible)
{
m_HoverRect.Width -= m_Right.Width;
}
}
if ((dir == Pos.Left || dir == Pos.Right) && !m_DropFar)
{
if (m_Top != null && m_Top.IsVisible)
{
m_HoverRect.Y += m_Top.Height;
m_HoverRect.Height -= m_Top.Height;
}
if (m_Bottom != null && m_Bottom.IsVisible)
{
m_HoverRect.Height -= m_Bottom.Height;
}
}
}
/// <summary>
/// Renders over the actual control (overlays).
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderOver(Skin.Base skin)
{
if (!m_DrawHover)
return;
Renderer.Base render = skin.Renderer;
render.DrawColor = Color.FromArgb(20, 255, 200, 255);
render.DrawFilledRect(RenderBounds);
if (m_HoverRect.Width == 0)
return;
render.DrawColor = Color.FromArgb(100, 255, 200, 255);
render.DrawFilledRect(m_HoverRect);
render.DrawColor = Color.FromArgb(200, 255, 200, 255);
render.DrawLinedRect(m_HoverRect);
}
}
}

View file

@ -0,0 +1,81 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Docked tab control.
/// </summary>
public class DockedTabControl : TabControl
{
private readonly TabTitleBar m_TitleBar;
/// <summary>
/// Determines whether the title bar is visible.
/// </summary>
public bool TitleBarVisible { get { return !m_TitleBar.IsHidden; } set { m_TitleBar.IsHidden = !value; } }
/// <summary>
/// Initializes a new instance of the <see cref="DockedTabControl"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public DockedTabControl(Base parent)
: base(parent)
{
Dock = Pos.Fill;
m_TitleBar = new TabTitleBar(this);
m_TitleBar.Dock = Pos.Top;
m_TitleBar.IsHidden = true;
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
TabStrip.IsHidden = (TabCount <= 1);
UpdateTitleBar();
base.Layout(skin);
}
private void UpdateTitleBar()
{
if (CurrentButton == null)
return;
m_TitleBar.UpdateFromTab(CurrentButton);
}
public override void DragAndDrop_StartDragging(DragDrop.Package package, int x, int y)
{
base.DragAndDrop_StartDragging(package, x, y);
IsHidden = true;
// This hiding our parent thing is kind of lousy.
Parent.IsHidden = true;
}
public override void DragAndDrop_EndDragging(bool success, int x, int y)
{
IsHidden = false;
if (!success)
{
Parent.IsHidden = false;
}
}
public void MoveTabsTo(DockedTabControl target)
{
var children = TabStrip.Children.ToArray(); // copy because collection will be modified
foreach (Base child in children)
{
TabButton button = child as TabButton;
if (button == null)
continue;
target.AddPage(button);
}
Invalidate();
}
}
}

74
Gwen/Control/GroupBox.cs Normal file
View file

@ -0,0 +1,74 @@
using System;
using System.Drawing;
namespace Gwen.Control
{
/// <summary>
/// Group box (container).
/// </summary>
/// <remarks>Don't use autosize with docking.</remarks>
public class GroupBox : Label
{
/// <summary>
/// Initializes a new instance of the <see cref="GroupBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public GroupBox(Base parent)
: base(parent)
{
// Set to true, because it's likely that our
// children will want mouse input, and they
// can't get it without us..
MouseInputEnabled = true;
KeyboardInputEnabled = true;
TextPadding = new Padding(10, 0, 10, 0);
Alignment = Pos.Top | Pos.Left;
Invalidate();
m_InnerPanel = new Base(this);
m_InnerPanel.Dock = Pos.Fill;
m_InnerPanel.Margin = new Margin(5, TextHeight+5, 5, 5);
//Margin = new Margin(5, 5, 5, 5);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
if (AutoSizeToContents)
{
DoSizeToContents();
}
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawGroupBox(this, TextX, TextHeight, TextWidth);
}
/// <summary>
/// Sizes to contents.
/// </summary>
public override void SizeToContents()
{
// we inherit from Label and shouldn't use its method.
DoSizeToContents();
}
protected virtual void DoSizeToContents()
{
m_InnerPanel.SizeToChildren();
SizeToChildren();
if (Width < TextWidth + TextPadding.Right + TextPadding.Left)
Width = TextWidth + TextPadding.Right + TextPadding.Left;
}
}
}

View file

@ -0,0 +1,198 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// HSV color picker with "before" and "after" color boxes.
/// </summary>
public class HSVColorPicker : Base, IColorPicker
{
private readonly ColorLerpBox m_LerpBox;
private readonly ColorSlider m_ColorSlider;
private readonly ColorDisplay m_Before;
private readonly ColorDisplay m_After;
/// <summary>
/// Invoked when the selected color has changed.
/// </summary>
public event GwenEventHandler ColorChanged;
/// <summary>
/// The "before" color.
/// </summary>
public Color DefaultColor { get { return m_Before.Color; } set { m_Before.Color = value; } }
/// <summary>
/// Selected color.
/// </summary>
public Color SelectedColor { get { return m_LerpBox.SelectedColor; } }
/// <summary>
/// Initializes a new instance of the <see cref="HSVColorPicker"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public HSVColorPicker(Base parent)
: base(parent)
{
MouseInputEnabled = true;
SetSize(256, 128);
//ShouldCacheToTexture = true;
m_LerpBox = new ColorLerpBox(this);
m_LerpBox.ColorChanged += ColorBoxChanged;
m_LerpBox.Dock = Pos.Left;
m_ColorSlider = new ColorSlider(this);
m_ColorSlider.SetPosition(m_LerpBox.Width + 15, 5);
m_ColorSlider.ColorChanged += ColorSliderChanged;
m_ColorSlider.Dock = Pos.Left;
m_After = new ColorDisplay(this);
m_After.SetSize(48, 24);
m_After.SetPosition(m_ColorSlider.X + m_ColorSlider.Width + 15, 5);
m_Before = new ColorDisplay(this);
m_Before.SetSize(48, 24);
m_Before.SetPosition(m_After.X, 28);
int x = m_Before.X;
int y = m_Before.Y + 30;
{
Label label = new Label(this);
label.SetText("R:");
label.SizeToContents();
label.SetPosition(x, y);
TextBoxNumeric numeric = new TextBoxNumeric(this);
numeric.Name = "RedBox";
numeric.SetPosition(x + 15, y - 1);
numeric.SetSize(26, 16);
numeric.SelectAllOnFocus = true;
numeric.TextChanged += NumericTyped;
}
y += 20;
{
Label label = new Label(this);
label.SetText("G:");
label.SizeToContents();
label.SetPosition(x, y);
TextBoxNumeric numeric = new TextBoxNumeric(this);
numeric.Name = "GreenBox";
numeric.SetPosition(x + 15, y - 1);
numeric.SetSize(26, 16);
numeric.SelectAllOnFocus = true;
numeric.TextChanged += NumericTyped;
}
y += 20;
{
Label label = new Label(this);
label.SetText("B:");
label.SizeToContents();
label.SetPosition(x, y);
TextBoxNumeric numeric = new TextBoxNumeric(this);
numeric.Name = "BlueBox";
numeric.SetPosition(x + 15, y - 1);
numeric.SetSize(26, 16);
numeric.SelectAllOnFocus = true;
numeric.TextChanged += NumericTyped;
}
SetColor(DefaultColor);
}
private void NumericTyped(Base control)
{
TextBoxNumeric box = control as TextBoxNumeric;
if (null == box) return;
if (box.Text == String.Empty) return;
int textValue = (int)box.Value;
if (textValue < 0) textValue = 0;
if (textValue > 255) textValue = 255;
Color newColor = SelectedColor;
if (box.Name.Contains("Red"))
{
newColor = Color.FromArgb(SelectedColor.A, textValue, SelectedColor.G, SelectedColor.B);
}
else if (box.Name.Contains("Green"))
{
newColor = Color.FromArgb(SelectedColor.A, SelectedColor.R, textValue, SelectedColor.B);
}
else if (box.Name.Contains("Blue"))
{
newColor = Color.FromArgb(SelectedColor.A, SelectedColor.R, SelectedColor.G, textValue);
}
else if (box.Name.Contains("Alpha"))
{
newColor = Color.FromArgb(textValue, SelectedColor.R, SelectedColor.G, SelectedColor.B);
}
SetColor(newColor);
}
private void UpdateControls(Color color)
{
// What in the FUCK
TextBoxNumeric redBox = FindChildByName("RedBox", false) as TextBoxNumeric;
if (redBox != null)
redBox.SetText(color.R.ToString(), false);
TextBoxNumeric greenBox = FindChildByName("GreenBox", false) as TextBoxNumeric;
if (greenBox != null)
greenBox.SetText(color.G.ToString(), false);
TextBoxNumeric blueBox = FindChildByName("BlueBox", false) as TextBoxNumeric;
if (blueBox != null)
blueBox.SetText(color.B.ToString(), false);
m_After.Color = color;
if (ColorChanged != null)
ColorChanged.Invoke(this);
}
/// <summary>
/// Sets the selected color.
/// </summary>
/// <param name="color">Color to set.</param>
/// <param name="onlyHue">Determines whether only the hue should be set.</param>
/// <param name="reset">Determines whether the "before" color should be set as well.</param>
public void SetColor(Color color, bool onlyHue = false, bool reset = false)
{
UpdateControls(color);
if (reset)
m_Before.Color = color;
m_ColorSlider.SelectedColor = color;
m_LerpBox.SetColor(color, onlyHue);
m_After.Color = color;
}
private void ColorBoxChanged(Base control)
{
UpdateControls(SelectedColor);
Invalidate();
}
private void ColorSliderChanged(Base control)
{
if (m_LerpBox != null)
m_LerpBox.SetColor(m_ColorSlider.SelectedColor, true);
Invalidate();
}
}
}

View file

@ -0,0 +1,203 @@
using System;
using System.Drawing;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Horizontal scrollbar.
/// </summary>
public class HorizontalScrollBar : ScrollBar
{
/// <summary>
/// Bar size (in pixels).
/// </summary>
public override int BarSize
{
get { return m_Bar.Width; }
set { m_Bar.Width = value; }
}
/// <summary>
/// Bar position (in pixels).
/// </summary>
public override int BarPos
{
get { return m_Bar.X - Height; }
}
/// <summary>
/// Indicates whether the bar is horizontal.
/// </summary>
public override bool IsHorizontal
{
get { return true; }
}
/// <summary>
/// Button size (in pixels).
/// </summary>
public override int ButtonSize
{
get { return Height; }
}
/// <summary>
/// Initializes a new instance of the <see cref="HorizontalScrollBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public HorizontalScrollBar(Base parent)
: base(parent)
{
m_Bar.IsHorizontal = true;
m_ScrollButton[0].SetDirectionLeft();
m_ScrollButton[0].Clicked += NudgeLeft;
m_ScrollButton[1].SetDirectionRight();
m_ScrollButton[1].Clicked += NudgeRight;
m_Bar.Dragged += OnBarMoved;
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
m_ScrollButton[0].Width = Height;
m_ScrollButton[0].Dock = Pos.Left;
m_ScrollButton[1].Width = Height;
m_ScrollButton[1].Dock = Pos.Right;
m_Bar.Height = ButtonSize;
m_Bar.Padding = new Padding(ButtonSize, 0, ButtonSize, 0);
float barWidth = (m_ViewableContentSize / m_ContentSize) * (Width - (ButtonSize * 2));
if (barWidth < ButtonSize * 0.5f)
barWidth = (int)(ButtonSize * 0.5f);
m_Bar.Width = (int)(barWidth);
m_Bar.IsHidden = Width - (ButtonSize * 2) <= barWidth;
//Based on our last scroll amount, produce a position for the bar
if (!m_Bar.IsHeld)
{
SetScrollAmount(ScrollAmount, true);
}
}
public void NudgeLeft(Base control)
{
if (!IsDisabled)
SetScrollAmount(ScrollAmount - NudgeAmount, true);
}
public void NudgeRight(Base control)
{
if (!IsDisabled)
SetScrollAmount(ScrollAmount + NudgeAmount, true);
}
public override void ScrollToLeft()
{
SetScrollAmount(0, true);
}
public override void ScrollToRight()
{
SetScrollAmount(1, true);
}
public override float NudgeAmount
{
get
{
if (m_Depressed)
return m_ViewableContentSize / m_ContentSize;
else
return base.NudgeAmount;
}
set
{
base.NudgeAmount = value;
}
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
if (down)
{
m_Depressed = true;
InputHandler.MouseFocus = this;
}
else
{
Point clickPos = CanvasPosToLocal(new Point(x, y));
if (clickPos.X < m_Bar.X)
NudgeLeft(this);
else
if (clickPos.X > m_Bar.X + m_Bar.Width)
NudgeRight(this);
m_Depressed = false;
InputHandler.MouseFocus = null;
}
}
protected override float CalculateScrolledAmount()
{
return (float)(m_Bar.X - ButtonSize) / (Width - m_Bar.Width - (ButtonSize * 2));
}
/// <summary>
/// Sets the scroll amount (0-1).
/// </summary>
/// <param name="value">Scroll amount.</param>
/// <param name="forceUpdate">Determines whether the control should be updated.</param>
/// <returns>
/// True if control state changed.
/// </returns>
public override bool SetScrollAmount(float value, bool forceUpdate = false)
{
value = Util.Clamp(value, 0, 1);
if (!base.SetScrollAmount(value, forceUpdate))
return false;
if (forceUpdate)
{
int newX = (int)(ButtonSize + (value * ((Width - m_Bar.Width) - (ButtonSize * 2))));
m_Bar.MoveTo(newX, m_Bar.Y);
}
return true;
}
/// <summary>
/// Handler for the BarMoved event.
/// </summary>
/// <param name="control">Event source.</param>
protected override void OnBarMoved(Base control)
{
if (m_Bar.IsHeld)
{
SetScrollAmount(CalculateScrolledAmount(), false);
base.OnBarMoved(control);
}
else
InvalidateParent();
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Drawing;
namespace Gwen.Control
{
/// <summary>
/// Horizontal slider.
/// </summary>
public class HorizontalSlider : Slider
{
/// <summary>
/// Initializes a new instance of the <see cref="HorizontalSlider"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public HorizontalSlider(Base parent)
: base(parent)
{
m_SliderBar.IsHorizontal = true;
}
protected override float CalculateValue()
{
return (float)m_SliderBar.X / (Width - m_SliderBar.Width);
}
protected override void UpdateBarFromValue()
{
m_SliderBar.MoveTo((int)((Width - m_SliderBar.Width) * (m_Value)), m_SliderBar.Y);
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
m_SliderBar.MoveTo((int)(CanvasPosToLocal(new Point(x, y)).X - m_SliderBar.Width*0.5), m_SliderBar.Y);
m_SliderBar.InputMouseClickedLeft(x, y, down);
OnMoved(m_SliderBar);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
m_SliderBar.SetSize(15, Height);
UpdateBarFromValue();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawSlider(this, true, m_SnapToNotches ? m_NotchCount : 0, m_SliderBar.Width);
}
}
}

View file

@ -0,0 +1,215 @@
using System;
//using System.Windows.Forms;
using Gwen.ControlInternal;
namespace Gwen.Control
{
public class HorizontalSplitter : Base
{
private readonly SplitterBar m_VSplitter;
private readonly Base[] m_Sections;
private float m_VVal; // 0-1
private int m_BarSize; // pixels
private int m_ZoomedSection; // 0-1
/// <summary>
/// Invoked when one of the panels has been zoomed (maximized).
/// </summary>
public event GwenEventHandler PanelZoomed;
/// <summary>
/// Invoked when one of the panels has been unzoomed (restored).
/// </summary>
public event GwenEventHandler PanelUnZoomed;
/// <summary>
/// Invoked when the zoomed panel has been changed.
/// </summary>
public event GwenEventHandler ZoomChanged;
/// <summary>
/// Initializes a new instance of the <see cref="CrossSplitter"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public HorizontalSplitter(Base parent)
: base(parent)
{
m_Sections = new Base[2];
m_VSplitter = new SplitterBar(this);
m_VSplitter.SetPosition(0, 128);
m_VSplitter.Dragged += OnVerticalMoved;
//m_VSplitter.Cursor = Cursors.SizeNS;
m_VVal = 0.5f;
SetPanel(0, null);
SetPanel(1, null);
SplitterSize = 5;
SplittersVisible = false;
m_ZoomedSection = -1;
}
/// <summary>
/// Centers the panels so that they take even amount of space.
/// </summary>
public void CenterPanels()
{
m_VVal = 0.5f;
Invalidate();
}
/// <summary>
/// Indicates whether any of the panels is zoomed.
/// </summary>
public bool IsZoomed { get { return m_ZoomedSection != -1; } }
/// <summary>
/// Gets or sets a value indicating whether splitters should be visible.
/// </summary>
public bool SplittersVisible
{
get { return m_VSplitter.ShouldDrawBackground; }
set
{
m_VSplitter.ShouldDrawBackground = value;
}
}
/// <summary>
/// Gets or sets the size of the splitter.
/// </summary>
public int SplitterSize { get { return m_BarSize; } set { m_BarSize = value; } }
private void UpdateVSplitter()
{
m_VSplitter.MoveTo(m_VSplitter.X, (Height - m_VSplitter.Height) * (m_VVal));
}
protected void OnVerticalMoved(Base control)
{
m_VVal = CalculateValueVertical();
Invalidate();
}
private float CalculateValueVertical()
{
return m_VSplitter.Y / (float)(Height - m_VSplitter.Height);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
m_VSplitter.SetSize(Width, m_BarSize);
UpdateVSplitter();
if (m_ZoomedSection == -1)
{
if (m_Sections[0] != null)
m_Sections[0].SetBounds(0, 0, Width, m_VSplitter.Y);
if (m_Sections[1] != null)
m_Sections[1].SetBounds(0, m_VSplitter.Y + m_BarSize, Width, Height - (m_VSplitter.Y + m_BarSize));
}
else
{
//This should probably use Fill docking instead
m_Sections[m_ZoomedSection].SetBounds(0, 0, Width, Height);
}
}
/// <summary>
/// Assigns a control to the specific inner section.
/// </summary>
/// <param name="index">Section index (0-3).</param>
/// <param name="panel">Control to assign.</param>
public void SetPanel(int index, Base panel)
{
m_Sections[index] = panel;
if (panel != null)
{
panel.Dock = Pos.None;
panel.Parent = this;
}
Invalidate();
}
/// <summary>
/// Gets the specific inner section.
/// </summary>
/// <param name="index">Section index (0-3).</param>
/// <returns>Specified section.</returns>
public Base GetPanel(int index)
{
return m_Sections[index];
}
/// <summary>
/// Internal handler for the zoom changed event.
/// </summary>
protected void OnZoomChanged()
{
if (ZoomChanged != null)
ZoomChanged.Invoke(this);
if (m_ZoomedSection == -1)
{
if (PanelUnZoomed != null)
PanelUnZoomed.Invoke(this);
}
else
{
if (PanelZoomed != null)
PanelZoomed.Invoke(this);
}
}
/// <summary>
/// Maximizes the specified panel so it fills the entire control.
/// </summary>
/// <param name="section">Panel index (0-3).</param>
public void Zoom(int section)
{
UnZoom();
if (m_Sections[section] != null)
{
for (int i = 0; i < 2; i++)
{
if (i != section && m_Sections[i] != null)
m_Sections[i].IsHidden = true;
}
m_ZoomedSection = section;
Invalidate();
}
OnZoomChanged();
}
/// <summary>
/// Restores the control so all panels are visible.
/// </summary>
public void UnZoom()
{
m_ZoomedSection = -1;
for (int i = 0; i < 2; i++)
{
if (m_Sections[i] != null)
m_Sections[i].IsHidden = false;
}
Invalidate();
OnZoomChanged();
}
}
}

View file

@ -0,0 +1,10 @@
using System;
using System.Drawing;
namespace Gwen.Control
{
public interface IColorPicker
{
Color SelectedColor { get; }
}
}

View file

@ -0,0 +1,77 @@
using System;
using System.Drawing;
namespace Gwen.Control
{
/// <summary>
/// Image container.
/// </summary>
public class ImagePanel : Base
{
private readonly Texture m_Texture;
private readonly float[] m_uv;
private Color m_DrawColor;
/// <summary>
/// Initializes a new instance of the <see cref="ImagePanel"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ImagePanel(Base parent)
: base(parent)
{
m_uv = new float[4];
m_Texture = new Texture(Skin.Renderer);
SetUV(0, 0, 1, 1);
MouseInputEnabled = false;
m_DrawColor = Color.White;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public override void Dispose()
{
m_Texture.Dispose();
base.Dispose();
}
/// <summary>
/// Sets the texture coordinates of the image.
/// </summary>
public virtual void SetUV(float u1, float v1, float u2, float v2)
{
m_uv[0] = u1;
m_uv[1] = v1;
m_uv[2] = u2;
m_uv[3] = v2;
}
/// <summary>
/// Texture name.
/// </summary>
public String ImageName
{
get { return m_Texture.Name; }
set { m_Texture.Load(value); }
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
base.Render(skin);
skin.Renderer.DrawColor = m_DrawColor;
skin.Renderer.DrawTexturedRect(m_Texture, RenderBounds, m_uv[0], m_uv[1], m_uv[2], m_uv[3]);
}
/// <summary>
/// Sizes the control to its contents.
/// </summary>
public virtual void SizeToContents()
{
SetSize(m_Texture.Width, m_Texture.Height);
}
}
}

211
Gwen/Control/Label.cs Normal file
View file

@ -0,0 +1,211 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Static text label.
/// </summary>
public class Label : Base
{
private readonly Text m_Text;
private Pos m_Align;
private Padding m_TextPadding;
private bool m_AutoSizeToContents;
/// <summary>
/// Text alignment.
/// </summary>
public Pos Alignment { get { return m_Align; } set { m_Align = value; Invalidate(); } }
/// <summary>
/// Text.
/// </summary>
public String Text { get { return m_Text.String; } set { SetText(value); } }
/// <summary>
/// Font.
/// </summary>
public Font Font
{
get { return m_Text.Font; }
set
{
m_Text.Font = value;
if (m_AutoSizeToContents)
SizeToContents();
Invalidate();
}
}
/// <summary>
/// Text color.
/// </summary>
public Color TextColor { get { return m_Text.TextColor; } set { m_Text.TextColor = value; } }
/// <summary>
/// Override text color (used by tooltips).
/// </summary>
public Color TextColorOverride { get { return m_Text.TextColorOverride; } set { m_Text.TextColorOverride = value; } }
/// <summary>
/// Text override - used to display different string.
/// </summary>
public String TextOverride { get { return m_Text.TextOverride; } set { m_Text.TextOverride = value; } }
/// <summary>
/// Width of the text (in pixels).
/// </summary>
public int TextWidth { get { return m_Text.Width; } }
/// <summary>
/// Height of the text (in pixels).
/// </summary>
public int TextHeight { get { return m_Text.Height; } }
public int TextX { get { return m_Text.X; } }
public int TextY { get { return m_Text.Y; } }
/// <summary>
/// Text length (in characters).
/// </summary>
public int TextLength { get { return m_Text.Length; } }
public int TextRight { get { return m_Text.Right; } }
public virtual void MakeColorNormal() { TextColor = Skin.Colors.Label.Default; }
public virtual void MakeColorBright() { TextColor = Skin.Colors.Label.Bright; }
public virtual void MakeColorDark() { TextColor = Skin.Colors.Label.Dark; }
public virtual void MakeColorHighlight() { TextColor = Skin.Colors.Label.Highlight; }
/// <summary>
/// Determines if the control should autosize to its text.
/// </summary>
public bool AutoSizeToContents { get { return m_AutoSizeToContents; } set { m_AutoSizeToContents = value; Invalidate(); InvalidateParent(); } }
/// <summary>
/// Text padding.
/// </summary>
public Padding TextPadding { get { return m_TextPadding; } set { m_TextPadding = value; Invalidate(); InvalidateParent(); } }
/// <summary>
/// Initializes a new instance of the <see cref="Label"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Label(Base parent) : base(parent)
{
m_Text = new Text(this);
//m_Text.Font = Skin.DefaultFont;
MouseInputEnabled = false;
SetSize(100, 10);
Alignment = Pos.Left | Pos.Top;
m_AutoSizeToContents = false;
}
/// <summary>
/// Returns index of the character closest to specified point (in canvas coordinates).
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
/// <returns></returns>
protected int GetClosestCharacter(int x, int y)
{
return m_Text.GetClosestCharacter(m_Text.CanvasPosToLocal(new Point(x, y)));
}
/// <summary>
/// Sets the position of the internal text control.
/// </summary>
/// <param name="x"></param>
/// <param name="y"></param>
protected void SetTextPosition(int x, int y)
{
m_Text.SetPosition(x, y);
}
/// <summary>
/// Handler for text changed event.
/// </summary>
protected virtual void OnTextChanged()
{}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
Pos align = m_Align;
if (m_AutoSizeToContents)
SizeToContents();
int x = m_TextPadding.Left + Padding.Left;
int y = m_TextPadding.Top + Padding.Top;
if (0 != (align & Pos.Right))
x = Width - m_Text.Width - m_TextPadding.Right - Padding.Right;
if (0 != (align & Pos.CenterH))
x = (int)((m_TextPadding.Left + Padding.Left) + ((Width - m_Text.Width - m_TextPadding.Left - Padding.Left - m_TextPadding.Right - Padding.Right) * 0.5f));
if (0 != (align & Pos.CenterV))
y = (int)((m_TextPadding.Top + Padding.Top) + ((Height - m_Text.Height) * 0.5f) - m_TextPadding.Bottom - Padding.Bottom);
if (0 != (align & Pos.Bottom))
y = Height - m_Text.Height - m_TextPadding.Bottom - Padding.Bottom;
m_Text.SetPosition(x, y);
}
/// <summary>
/// Sets the label text.
/// </summary>
/// <param name="str">Text to set.</param>
/// <param name="doEvents">Determines whether to invoke "text changed" event.</param>
public virtual void SetText(String str, bool doEvents = true)
{
if (Text == str)
return;
m_Text.String = str;
if (m_AutoSizeToContents)
SizeToContents();
Invalidate();
InvalidateParent();
if (doEvents)
OnTextChanged();
}
public virtual void SizeToContents()
{
m_Text.SetPosition(m_TextPadding.Left + Padding.Left, m_TextPadding.Top + Padding.Top);
m_Text.SizeToContents();
SetSize(m_Text.Width + Padding.Left + Padding.Right + m_TextPadding.Left + m_TextPadding.Right,
m_Text.Height + Padding.Top + Padding.Bottom + m_TextPadding.Top + m_TextPadding.Bottom);
InvalidateParent();
}
/// <summary>
/// Gets the coordinates of specified character.
/// </summary>
/// <param name="index">Character index.</param>
/// <returns>Character coordinates (local).</returns>
public virtual Point GetCharacterPosition(int index)
{
Point p = m_Text.GetCharacterPosition(index);
return new Point(p.X + m_Text.X, p.Y + m_Text.Y);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
}
}
}

View file

@ -0,0 +1,30 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Clickable label (for checkboxes etc).
/// </summary>
public class LabelClickable : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="LabelClickable"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public LabelClickable(Base parent)
: base(parent)
{
IsToggle = false;
Alignment = Pos.Left | Pos.CenterV;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
// no button look
}
}
}

View file

@ -0,0 +1,95 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// CheckBox with label.
/// </summary>
public class LabeledCheckBox : Base
{
private readonly CheckBox m_CheckBox;
private readonly LabelClickable m_Label;
/// <summary>
/// Invoked when the control has been checked.
/// </summary>
public event GwenEventHandler Checked;
/// <summary>
/// Invoked when the control has been unchecked.
/// </summary>
public event GwenEventHandler UnChecked;
/// <summary>
/// Invoked when the control's check has been changed.
/// </summary>
public event GwenEventHandler CheckChanged;
/// <summary>
/// Indicates whether the control is checked.
/// </summary>
public bool IsChecked { get { return m_CheckBox.IsChecked; } set { m_CheckBox.IsChecked = value; } }
/// <summary>
/// Label text.
/// </summary>
public String Text { get { return m_Label.Text; } set { m_Label.Text = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="LabeledCheckBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public LabeledCheckBox(Base parent)
: base(parent)
{
SetSize(200, 19);
m_CheckBox = new CheckBox(this);
m_CheckBox.Dock = Pos.Left;
m_CheckBox.Margin = new Margin(0, 2, 2, 2);
m_CheckBox.IsTabable = false;
m_CheckBox.CheckChanged += OnCheckChanged;
m_Label = new LabelClickable(this);
m_Label.Dock = Pos.Fill;
m_Label.Clicked += m_CheckBox.Press;
m_Label.IsTabable = false;
IsTabable = false;
}
/// <summary>
/// Handler for CheckChanged event.
/// </summary>
protected virtual void OnCheckChanged(Base control)
{
if (m_CheckBox.IsChecked)
{
if (Checked != null)
Checked.Invoke(this);
}
else
{
if (UnChecked != null)
UnChecked.Invoke(this);
}
if (CheckChanged != null)
CheckChanged.Invoke(this);
}
/// <summary>
/// Handler for Space keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeySpace(bool down)
{
base.OnKeySpace(down);
if (!down)
m_CheckBox.IsChecked = !m_CheckBox.IsChecked;
return true;
}
}
}

View file

@ -0,0 +1,93 @@
using System;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// RadioButton with label.
/// </summary>
public class LabeledRadioButton : Base
{
private readonly RadioButton m_RadioButton;
private readonly LabelClickable m_Label;
/// <summary>
/// Label text.
/// </summary>
public String Text { get { return m_Label.Text; } set { m_Label.Text = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="LabeledRadioButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public LabeledRadioButton(Base parent)
: base(parent)
{
SetSize(100, 20);
m_RadioButton = new RadioButton(this);
//m_RadioButton.Dock = Pos.Left; // no docking, it causes resizing
//m_RadioButton.Margin = new Margin(0, 2, 2, 2);
m_RadioButton.IsTabable = false;
m_RadioButton.KeyboardInputEnabled = false;
m_Label = new LabelClickable(this);
m_Label.Alignment = Pos.Bottom | Pos.Left;
m_Label.Text = "Radio Button";
//m_Label.Dock = Pos.Fill;
m_Label.Clicked += m_RadioButton.Press;
m_Label.IsTabable = false;
m_Label.KeyboardInputEnabled = false;
m_Label.AutoSizeToContents = true;
}
protected override void Layout(Skin.Base skin)
{
// ugly stuff because we don't have anchoring without docking (docking resizes children)
if (m_Label.Height > m_RadioButton.Height) // usually radio is smaller than label so it gets repositioned to avoid clipping with negative Y
{
m_RadioButton.Y = (m_Label.Height - m_RadioButton.Height)/2;
}
Align.PlaceRightBottom(m_Label, m_RadioButton);
SizeToChildren();
base.Layout(skin);
}
/// <summary>
/// Renders the focus overlay.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderFocus(Skin.Base skin)
{
if (InputHandler.KeyboardFocus != this) return;
if (!IsTabable) return;
skin.DrawKeyboardHighlight(this, RenderBounds, 0);
}
// todo: would be nice to remove that
internal RadioButton RadioButton { get { return m_RadioButton; } }
/// <summary>
/// Handler for Space keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeySpace(bool down)
{
if (down)
m_RadioButton.IsChecked = !m_RadioButton.IsChecked;
return true;
}
/// <summary>
/// Selects the radio button.
/// </summary>
public virtual void Select()
{
m_RadioButton.IsChecked = true;
}
}
}

View file

@ -0,0 +1,53 @@
using System;
namespace Gwen.Control.Layout
{
/// <summary>
/// Helper control that positions its children in a specific way.
/// </summary>
public class Positioner : Base
{
private Pos m_Pos;
/// <summary>
/// Children position.
/// </summary>
public Pos Pos { get { return m_Pos; } set { m_Pos = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="Positioner"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Positioner(Base parent) : base(parent)
{
Pos = Pos.Left | Pos.Top;
}
/// <summary>
/// Function invoked after layout.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void PostLayout(Skin.Base skin)
{
foreach (Base child in Children) // ok?
{
child.Position(m_Pos);
}
}
}
/// <summary>
/// Helper class that centers all its children.
/// </summary>
public class Center : Positioner
{
/// <summary>
/// Initializes a new instance of the <see cref="Center"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Center(Base parent) : base(parent)
{
Pos = Pos.Center;
}
}
}

View file

@ -0,0 +1,95 @@
using System;
namespace Gwen.Control.Layout
{
/// <summary>
/// Base splitter class.
/// </summary>
public class Splitter : Base
{
private readonly Base[] m_Panel;
private readonly bool[] m_Scale;
/// <summary>
/// Initializes a new instance of the <see cref="Splitter"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Splitter(Base parent) : base(parent)
{
m_Panel = new Base[2];
m_Scale = new bool[2];
m_Scale[0] = true;
m_Scale[1] = true;
}
/// <summary>
/// Sets the contents of a splitter panel.
/// </summary>
/// <param name="panelIndex">Panel index (0-1).</param>
/// <param name="panel">Panel contents.</param>
/// <param name="noScale">Determines whether the content is to be scaled.</param>
public void SetPanel(int panelIndex, Base panel, bool noScale = false)
{
if (panelIndex < 0 || panelIndex > 1)
throw new ArgumentException("Invalid panel index", "panelIndex");
m_Panel[panelIndex] = panel;
m_Scale[panelIndex] = !noScale;
if (null != m_Panel[panelIndex])
{
m_Panel[panelIndex].Parent = this;
}
}
/// <summary>
/// Gets the contents of a secific panel.
/// </summary>
/// <param name="panelIndex">Panel index (0-1).</param>
/// <returns></returns>
Base GetPanel(int panelIndex)
{
if (panelIndex < 0 || panelIndex > 1)
throw new ArgumentException("Invalid panel index", "panelIndex");
return m_Panel[panelIndex];
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
LayoutVertical(skin);
}
protected virtual void LayoutVertical(Skin.Base skin)
{
int w = Width;
int h = Height;
if (m_Panel[0] != null)
{
Margin m = m_Panel[0].Margin;
if (m_Scale[0])
m_Panel[0].SetBounds(m.Left, m.Top, w - m.Left - m.Right, (h*0.5f) - m.Top - m.Bottom);
else
m_Panel[0].Position(Pos.Center, 0, (int) (h*-0.25f));
}
if (m_Panel[1] != null)
{
Margin m = m_Panel[1].Margin;
if (m_Scale[1])
m_Panel[1].SetBounds(m.Left, m.Top + (h*0.5f), w - m.Left - m.Right, (h*0.5f) - m.Top - m.Bottom);
else
m_Panel[1].Position(Pos.Center, 0, (int) (h*0.25f));
}
}
protected virtual void LayoutHorizontal(Skin.Base skin)
{
throw new NotImplementedException();
}
}
}

View file

@ -0,0 +1,247 @@
using System;
using System.Linq;
namespace Gwen.Control.Layout
{
/// <summary>
/// Base class for multi-column tables.
/// </summary>
public class Table : Base
{
// only children of this control should be TableRow.
private bool m_SizeToContents;
private int m_ColumnCount;
private int m_DefaultRowHeight;
private int m_MaxWidth; // for autosizing, if nonzero - fills last cell up to this size
private readonly int[] m_ColumnWidth;
/// <summary>
/// Column count (default 1).
/// </summary>
public int ColumnCount { get { return m_ColumnCount; } set { SetColumnCount(value); Invalidate(); } }
/// <summary>
/// Row count.
/// </summary>
public int RowCount { get { return Children.Count; } }
/// <summary>
/// Gets or sets default height for new table rows.
/// </summary>
public int DefaultRowHeight { get { return m_DefaultRowHeight; } set { m_DefaultRowHeight = value; } }
/// <summary>
/// Returns specific row of the table.
/// </summary>
/// <param name="index">Row index.</param>
/// <returns>Row at the specified index.</returns>
public TableRow this[int index] { get { return Children[index] as TableRow; } }
/// <summary>
/// Initializes a new instance of the <see cref="Table"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Table(Base parent) : base(parent)
{
m_ColumnCount = 1;
m_DefaultRowHeight = 22;
m_ColumnWidth = new int[TableRow.MaxColumns];
for (int i = 0; i < TableRow.MaxColumns; i++)
{
m_ColumnWidth[i] = 20;
}
m_SizeToContents = false;
}
/// <summary>
/// Sets the number of columns.
/// </summary>
/// <param name="count">Number of columns.</param>
public void SetColumnCount(int count)
{
if (m_ColumnCount == count) return;
foreach (TableRow row in Children.OfType<TableRow>())
{
row.ColumnCount = count;
}
m_ColumnCount = count;
}
/// <summary>
/// Sets the column width (in pixels).
/// </summary>
/// <param name="column">Column index.</param>
/// <param name="width">Column width.</param>
public void SetColumnWidth(int column, int width)
{
if (m_ColumnWidth[column] == width)
return;
m_ColumnWidth[column] = width;
Invalidate();
}
/// <summary>
/// Gets the column width (in pixels).
/// </summary>
/// <param name="column">Column index.</param>
/// <returns>Column width.</returns>
public int GetColumnWidth(int column)
{
return m_ColumnWidth[column];
}
/// <summary>
/// Adds a new empty row.
/// </summary>
/// <returns>Newly created row.</returns>
public TableRow AddRow()
{
TableRow row = new TableRow(this);
row.ColumnCount = m_ColumnCount;
row.Height = m_DefaultRowHeight;
row.Dock = Pos.Top;
return row;
}
/// <summary>
/// Adds a new row.
/// </summary>
/// <param name="row">Row to add.</param>
public void AddRow(TableRow row)
{
row.Parent = this;
row.ColumnCount = m_ColumnCount;
row.Height = m_DefaultRowHeight;
row.Dock = Pos.Top;
}
/// <summary>
/// Adds a new row with specified text in first column.
/// </summary>
/// <param name="text">Text to add.</param>
/// <returns>New row.</returns>
public TableRow AddRow(String text)
{
var row = AddRow();
row.SetCellText(0, text);
return row;
}
/// <summary>
/// Removes a row by reference.
/// </summary>
/// <param name="row">Row to remove.</param>
public void RemoveRow(TableRow row)
{
RemoveChild(row, true);
}
/// <summary>
/// Removes a row by index.
/// </summary>
/// <param name="idx">Row index.</param>
public void RemoveRow(int idx)
{
var row = Children[idx];
RemoveRow(row as TableRow);
}
/// <summary>
/// Removes all rows.
/// </summary>
public void RemoveAll()
{
while (RowCount > 0)
RemoveRow(0);
}
/// <summary>
/// Gets the index of a specified row.
/// </summary>
/// <param name="row">Row to search for.</param>
/// <returns>Row index if found, -1 otherwise.</returns>
public int GetRowIndex(TableRow row)
{
return Children.IndexOf(row);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
bool even = false;
foreach (TableRow row in Children)
{
row.EvenRow = even;
even = !even;
for (int i = 0; i < m_ColumnCount; i++)
{
row.SetColumnWidth(i, m_ColumnWidth[i]);
}
}
}
protected override void PostLayout(Skin.Base skin)
{
base.PostLayout(skin);
if (m_SizeToContents)
{
DoSizeToContents();
m_SizeToContents = false;
}
}
/// <summary>
/// Sizes to fit contents.
/// </summary>
public void SizeToContents(int maxWidth)
{
m_MaxWidth = maxWidth;
m_SizeToContents = true;
Invalidate();
}
protected void DoSizeToContents()
{
int height = 0;
int width = 0;
foreach (TableRow row in Children)
{
row.SizeToContents(); // now all columns fit but only in this particular row
for (int i = 0; i < ColumnCount; i++)
{
Base cell = row.GetColumn(i);
if (null != cell)
{
if (i < ColumnCount - 1 || m_MaxWidth == 0)
m_ColumnWidth[i] = Math.Max(m_ColumnWidth[i], cell.Width + cell.Margin.Left + cell.Margin.Right);
else
m_ColumnWidth[i] = m_MaxWidth - width; // last cell - fill
}
}
height += row.Height;
}
// sum all column widths
for (int i = 0; i < ColumnCount; i++)
{
width += m_ColumnWidth[i];
}
SetSize(width, height);
//InvalidateParent();
}
}
}

View file

@ -0,0 +1,221 @@
using System;
using System.Drawing;
namespace Gwen.Control.Layout
{
/// <summary>
/// Single table row.
/// </summary>
public class TableRow : Base
{
// [omeg] todo: get rid of this
public const int MaxColumns = 5;
private int m_ColumnCount;
private bool m_EvenRow;
private readonly Label[] m_Columns;
internal Label GetColumn(int index)
{
return m_Columns[index];
}
/// <summary>
/// Invoked when the row has been selected.
/// </summary>
public event GwenEventHandler Selected;
/// <summary>
/// Column count.
/// </summary>
public int ColumnCount { get { return m_ColumnCount; } set { SetColumnCount(value); } }
/// <summary>
/// Indicates whether the row is even or odd (used for alternate coloring).
/// </summary>
public bool EvenRow { get { return m_EvenRow; } set { m_EvenRow = value; } }
/// <summary>
/// Text of the first column.
/// </summary>
public String Text { get { return GetText(0); } set { SetCellText(0, value); } }
/// <summary>
/// Initializes a new instance of the <see cref="TableRow"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TableRow(Base parent)
: base(parent)
{
m_Columns = new Label[MaxColumns];
m_ColumnCount = 0;
KeyboardInputEnabled = true;
}
/// <summary>
/// Sets the number of columns.
/// </summary>
/// <param name="columnCount">Number of columns.</param>
protected void SetColumnCount(int columnCount)
{
if (columnCount == m_ColumnCount) return;
if (columnCount >= MaxColumns)
throw new ArgumentException("Invalid column count", "columnCount");
for (int i = 0; i < MaxColumns; i++)
{
if (i < columnCount)
{
if (null == m_Columns[i])
{
m_Columns[i] = new Label(this);
m_Columns[i].Padding = Padding.Three;
m_Columns[i].Margin = new Margin(0, 0, 2, 0); // to separate them slightly
if (i == columnCount - 1)
{
// last column fills remaining space
m_Columns[i].Dock = Pos.Fill;
}
else
{
m_Columns[i].Dock = Pos.Left;
}
}
}
else if (null != m_Columns[i])
{
RemoveChild(m_Columns[i], true);
m_Columns[i] = null;
}
m_ColumnCount = columnCount;
}
}
/// <summary>
/// Sets the column width (in pixels).
/// </summary>
/// <param name="column">Column index.</param>
/// <param name="width">Column width.</param>
public void SetColumnWidth(int column, int width)
{
if (null == m_Columns[column])
return;
if (m_Columns[column].Width == width)
return;
m_Columns[column].Width = width;
}
/// <summary>
/// Sets the text of a specified cell.
/// </summary>
/// <param name="column">Column number.</param>
/// <param name="text">Text to set.</param>
public void SetCellText(int column, String text)
{
if (null == m_Columns[column])
return;
m_Columns[column].Text = text;
}
/// <summary>
/// Sets the contents of a specified cell.
/// </summary>
/// <param name="column">Column number.</param>
/// <param name="control">Cell contents.</param>
/// <param name="enableMouseInput">Determines whether mouse input should be enabled for the cell.</param>
public void SetCellContents(int column, Base control, bool enableMouseInput = false)
{
if (null == m_Columns[column])
return;
control.Parent = m_Columns[column];
m_Columns[column].MouseInputEnabled = enableMouseInput;
}
/// <summary>
/// Gets the contents of a specified cell.
/// </summary>
/// <param name="column">Column number.</param>
/// <returns>Control embedded in the cell.</returns>
public Base GetCellContents(int column)
{
return m_Columns[column];
}
protected virtual void OnRowSelected()
{
if (Selected != null)
Selected.Invoke(this);
}
/// <summary>
/// Sizes all cells to fit contents.
/// </summary>
public void SizeToContents()
{
int width = 0;
int height = 0;
for (int i = 0; i < m_ColumnCount; i++)
{
if (null == m_Columns[i])
continue;
// Note, more than 1 child here, because the
// label has a child built in ( The Text )
if (m_Columns[i].Children.Count > 1)
{
m_Columns[i].SizeToChildren();
}
else
{
m_Columns[i].SizeToContents();
}
//if (i == m_ColumnCount - 1) // last column
// m_Columns[i].Width = Parent.Width - width; // fill if not autosized
width += m_Columns[i].Width + m_Columns[i].Margin.Left + m_Columns[i].Margin.Right;
height = Math.Max(height, m_Columns[i].Height + m_Columns[i].Margin.Top + m_Columns[i].Margin.Bottom);
}
SetSize(width, height);
}
/// <summary>
/// Sets the text color for all cells.
/// </summary>
/// <param name="color">Text color.</param>
public void SetTextColor(Color color)
{
for (int i = 0; i < m_ColumnCount; i++)
{
if (null == m_Columns[i]) continue;
m_Columns[i].TextColor = color;
}
}
/// <summary>
/// Returns text of a specified row cell (default first).
/// </summary>
/// <param name="column">Column index.</param>
/// <returns>Column cell text.</returns>
public String GetText(int column = 0)
{
return m_Columns[column].Text;
}
/// <summary>
/// Handler for Copy event.
/// </summary>
/// <param name="from">Source control.</param>
protected override void OnCopy(Base from)
{
Platform.Neutral.SetClipboardText(Text);
}
}
}

325
Gwen/Control/ListBox.cs Normal file
View file

@ -0,0 +1,325 @@
using System;
using System.Collections.Generic;
using System.Drawing;
using System.Linq;
using System.Text.RegularExpressions;
using Gwen.Control.Layout;
namespace Gwen.Control
{
/// <summary>
/// ListBox control.
/// </summary>
public class ListBox : ScrollControl
{
private readonly Table m_Table;
private readonly List<TableRow> m_SelectedRows;
private bool m_MultiSelect;
private bool m_IsToggle;
private bool m_SizeToContents;
private Pos m_OldDock; // used while autosizing
/// <summary>
/// Determines whether multiple rows can be selected at once.
/// </summary>
public bool AllowMultiSelect
{
get { return m_MultiSelect; }
set
{
m_MultiSelect = value;
if (value)
IsToggle = true;
}
}
/// <summary>
/// Determines whether rows can be unselected by clicking on them again.
/// </summary>
public bool IsToggle { get { return m_IsToggle; } set { m_IsToggle = value; } }
/// <summary>
/// Number of rows in the list box.
/// </summary>
public int RowCount { get { return m_Table.RowCount; } }
/// <summary>
/// Returns specific row of the ListBox.
/// </summary>
/// <param name="index">Row index.</param>
/// <returns>Row at the specified index.</returns>
public ListBoxRow this[int index] { get { return m_Table[index] as ListBoxRow; } }
/// <summary>
/// List of selected rows.
/// </summary>
public IEnumerable<TableRow> SelectedRows { get { return m_SelectedRows; } }
/// <summary>
/// First selected row (and only if list is not multiselectable).
/// </summary>
public TableRow SelectedRow
{
get
{
if (m_SelectedRows.Count == 0)
return null;
return m_SelectedRows[0];
}
}
/// <summary>
/// Gets the selected row number.
/// </summary>
public int SelectedRowIndex
{
get
{
var selected = SelectedRow;
if (selected == null)
return -1;
return m_Table.GetRowIndex(selected);
}
}
/// <summary>
/// Column count of table rows.
/// </summary>
public int ColumnCount { get { return m_Table.ColumnCount; } set { m_Table.ColumnCount = value; Invalidate(); } }
/// <summary>
/// Invoked when a row has been selected.
/// </summary>
public event GwenEventHandler RowSelected;
/// <summary>
/// Invoked whan a row has beed unselected.
/// </summary>
public event GwenEventHandler RowUnselected;
/// <summary>
/// Initializes a new instance of the <see cref="ListBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ListBox(Base parent)
: base(parent)
{
m_SelectedRows = new List<TableRow>();
EnableScroll(false, true);
AutoHideBars = true;
Margin = Margin.One;
m_Table = new Table(this);
m_Table.Dock = Pos.Fill;
m_Table.ColumnCount = 1;
m_Table.BoundsChanged += TableResized;
m_MultiSelect = false;
m_IsToggle = false;
}
/// <summary>
/// Selects the specified row by index.
/// </summary>
/// <param name="index">Row to select.</param>
/// <param name="clearOthers">Determines whether to deselect previously selected rows.</param>
public void SelectRow(int index, bool clearOthers = false)
{
if (index < 0 || index >= m_Table.RowCount)
return;
SelectRow(m_Table.Children[index], clearOthers);
}
/// <summary>
/// Selects the specified row(s) by text.
/// </summary>
/// <param name="rowText">Text to search for (exact match).</param>
/// <param name="clearOthers">Determines whether to deselect previously selected rows.</param>
public void SelectRows(String rowText, bool clearOthers = false)
{
var rows = m_Table.Children.OfType<ListBoxRow>().Where(x => x.Text == rowText);
foreach (ListBoxRow row in rows)
{
SelectRow(row, clearOthers);
}
}
/// <summary>
/// Selects the specified row(s) by regex text search.
/// </summary>
/// <param name="pattern">Regex pattern to search for.</param>
/// <param name="regexOptions">Regex options.</param>
/// <param name="clearOthers">Determines whether to deselect previously selected rows.</param>
public void SelectRowsByRegex(String pattern, RegexOptions regexOptions = RegexOptions.None, bool clearOthers = false)
{
var rows = m_Table.Children.OfType<ListBoxRow>().Where(x => Regex.IsMatch(x.Text, pattern) );
foreach (ListBoxRow row in rows)
{
SelectRow(row, clearOthers);
}
}
/// <summary>
/// Slelects the specified row.
/// </summary>
/// <param name="control">Row to select.</param>
/// <param name="clearOthers">Determines whether to deselect previously selected rows.</param>
public void SelectRow(Base control, bool clearOthers = false)
{
if (!AllowMultiSelect || clearOthers)
UnselectAll();
ListBoxRow row = control as ListBoxRow;
if (row == null)
return;
// TODO: make sure this is one of our rows!
row.IsSelected = true;
m_SelectedRows.Add(row);
if (RowSelected != null)
RowSelected.Invoke(this);
}
/// <summary>
/// Removes the specified row by index.
/// </summary>
/// <param name="idx">Row index.</param>
public void RemoveRow(int idx)
{
m_Table.RemoveRow(idx); // this calls Dispose()
}
/// <summary>
/// Adds a new row.
/// </summary>
/// <param name="label">Row text.</param>
/// <returns>Newly created control.</returns>
public TableRow AddRow(String label)
{
return AddRow(label, String.Empty);
}
/// <summary>
/// Adds a new row.
/// </summary>
/// <param name="label">Row text.</param>
/// <param name="name">Internal control name.</param>
/// <returns>Newly created control.</returns>
public TableRow AddRow(String label, String name)
{
ListBoxRow row = new ListBoxRow(this);
m_Table.AddRow(row);
row.SetCellText(0, label);
row.Name = name;
row.Selected += OnRowSelected;
m_Table.SizeToContents(Width);
return row;
}
/// <summary>
/// Sets the column width (in pixels).
/// </summary>
/// <param name="column">Column index.</param>
/// <param name="width">Column width.</param>
public void SetColumnWidth(int column, int width)
{
m_Table.SetColumnWidth(column, width);
Invalidate();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawListBox(this);
}
/// <summary>
/// Deselects all rows.
/// </summary>
public virtual void UnselectAll()
{
foreach (ListBoxRow row in m_SelectedRows)
{
row.IsSelected = false;
if (RowUnselected != null)
RowUnselected.Invoke(this);
}
m_SelectedRows.Clear();
}
/// <summary>
/// Unselects the specified row.
/// </summary>
/// <param name="row">Row to unselect.</param>
public void UnselectRow(ListBoxRow row)
{
row.IsSelected = false;
m_SelectedRows.Remove(row);
if (RowUnselected != null)
RowUnselected.Invoke(this);
}
/// <summary>
/// Handler for the row selection event.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnRowSelected(Base control)
{
// [omeg] changed default behavior
bool clear = false;// !InputHandler.InputHandler.IsShiftDown;
ListBoxRow row = control as ListBoxRow;
if (row == null)
return;
if (row.IsSelected)
{
if (IsToggle)
UnselectRow(row);
}
else
{
SelectRow(control, clear);
}
}
/// <summary>
/// Removes all rows.
/// </summary>
public virtual void Clear()
{
UnselectAll();
m_Table.RemoveAll();
}
public void SizeToContents()
{
m_SizeToContents = true;
// docking interferes with autosizing so we disable it until sizing is done
m_OldDock = m_Table.Dock;
m_Table.Dock = Pos.None;
m_Table.SizeToContents(0); // autosize without constraints
}
private void TableResized(Base control)
{
if (m_SizeToContents)
{
SetSize(m_Table.Width, m_Table.Height);
m_SizeToContents = false;
m_Table.Dock = m_OldDock;
Invalidate();
}
}
}
}

View file

@ -0,0 +1,66 @@
using System;
using System.Drawing;
using Gwen.Control.Layout;
namespace Gwen.Control
{
/// <summary>
/// List box row (selectable).
/// </summary>
public class ListBoxRow : TableRow
{
private bool m_Selected;
/// <summary>
/// Initializes a new instance of the <see cref="ListBoxRow"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ListBoxRow(Base parent)
: base(parent)
{
MouseInputEnabled = true;
IsSelected = false;
}
/// <summary>
/// Indicates whether the control is selected.
/// </summary>
public bool IsSelected
{
get { return m_Selected; }
set
{
m_Selected = value;
// TODO: Get these values from the skin.
if (value)
SetTextColor(Color.White);
else
SetTextColor(Color.Black);
}
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawListBoxLine(this, IsSelected, EvenRow);
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
if (down)
{
//IsSelected = true; // [omeg] ListBox manages that
OnRowSelected();
}
}
}
}

209
Gwen/Control/Menu.cs Normal file
View file

@ -0,0 +1,209 @@
using System;
using System.Drawing;
using System.Linq;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Popup menu.
/// </summary>
public class Menu : ScrollControl
{
private bool m_DisableIconMargin;
private bool m_DeleteOnClose;
internal override bool IsMenuComponent { get { return true; } }
public bool IconMarginDisabled { get { return m_DisableIconMargin; } set { m_DisableIconMargin = value; } }
/// <summary>
/// Determines whether the menu should be disposed on close.
/// </summary>
public bool DeleteOnClose { get { return m_DeleteOnClose; } set { m_DeleteOnClose = value; } }
/// <summary>
/// Determines whether the menu should open on mouse hover.
/// </summary>
protected virtual bool ShouldHoverOpenMenu { get { return true; } }
/// <summary>
/// Initializes a new instance of the <see cref="Menu"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Menu(Base parent)
: base(parent)
{
SetBounds(0, 0, 10, 10);
Padding = Padding.Two;
IconMarginDisabled = false;
AutoHideBars = true;
EnableScroll(false, true);
DeleteOnClose = false;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawMenu(this, IconMarginDisabled);
}
/// <summary>
/// Renders under the actual control (shadows etc).
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderUnder(Skin.Base skin)
{
base.RenderUnder(skin);
skin.DrawShadow(this);
}
/// <summary>
/// Opens the menu.
/// </summary>
/// <param name="pos">Unused.</param>
public void Open(Pos pos)
{
IsHidden = false;
BringToFront();
Point mouse = Input.InputHandler.MousePosition;
SetPosition(mouse.X, mouse.Y);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
int childrenHeight = Children.Sum(child => child != null ? child.Height : 0);
if (Y + childrenHeight > GetCanvas().Height)
childrenHeight = GetCanvas().Height - Y;
SetSize(Width, childrenHeight);
base.Layout(skin);
}
/// <summary>
/// Adds a new menu item.
/// </summary>
/// <param name="text">Item text.</param>
/// <returns>Newly created control.</returns>
public virtual MenuItem AddItem(String text)
{
return AddItem(text, String.Empty);
}
/// <summary>
/// Adds a new menu item.
/// </summary>
/// <param name="text">Item text.</param>
/// <param name="iconName">Icon texture name.</param>
/// <param name="accelerator">Accelerator for this item.</param>
/// <returns>Newly created control.</returns>
public virtual MenuItem AddItem(String text, String iconName, String accelerator = "")
{
MenuItem item = new MenuItem(this);
item.Padding = Padding.Four;
item.SetText(text);
item.SetImage(iconName);
item.SetAccelerator(accelerator);
OnAddItem(item);
return item;
}
/// <summary>
/// Add item handler.
/// </summary>
/// <param name="item">Item added.</param>
protected virtual void OnAddItem(MenuItem item)
{
item.TextPadding = new Padding(IconMarginDisabled ? 0 : 24, 0, 16, 0);
item.Dock = Pos.Top;
item.SizeToContents();
item.Alignment = Pos.CenterV | Pos.Left;
item.HoverEnter += OnHoverItem;
// Do this here - after Top Docking these values mean nothing in layout
int w = item.Width + 10 + 32;
if (w < Width) w = Width;
SetSize(w, Height);
}
/// <summary>
/// Closes all submenus.
/// </summary>
public virtual void CloseAll()
{
//System.Diagnostics.Debug.Print("Menu.CloseAll: {0}", this);
Children.ForEach(child => { if (child is MenuItem) (child as MenuItem).CloseMenu(); });
}
/// <summary>
/// Indicates whether any (sub)menu is open.
/// </summary>
/// <returns></returns>
public virtual bool IsMenuOpen()
{
return Children.Any(child => { if (child is MenuItem) return (child as MenuItem).IsMenuOpen; return false; });
}
/// <summary>
/// Mouse hover handler.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnHoverItem(Base control)
{
if (!ShouldHoverOpenMenu) return;
MenuItem item = control as MenuItem;
if (null == item) return;
if (item.IsMenuOpen) return;
CloseAll();
item.OpenMenu();
}
/// <summary>
/// Closes the current menu.
/// </summary>
public virtual void Close()
{
//System.Diagnostics.Debug.Print("Menu.Close: {0}", this);
IsHidden = true;
if (DeleteOnClose)
{
DelayedDelete();
}
}
/// <summary>
/// Closes all submenus and the current menu.
/// </summary>
public override void CloseMenus()
{
//System.Diagnostics.Debug.Print("Menu.CloseMenus: {0}", this);
base.CloseMenus();
CloseAll();
Close();
}
/// <summary>
/// Adds a divider menu item.
/// </summary>
public virtual void AddDivider()
{
MenuDivider divider = new MenuDivider(this);
divider.Dock = Pos.Top;
divider.Margin = new Margin(IconMarginDisabled ? 0 : 24, 0, 4, 0);
}
}
}

255
Gwen/Control/MenuItem.cs Normal file
View file

@ -0,0 +1,255 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Menu item.
/// </summary>
public class MenuItem : Button
{
private bool m_OnStrip;
private bool m_Checkable;
private bool m_Checked;
private Menu m_Menu;
private Base m_SubmenuArrow;
private Label m_Accelerator;
/// <summary>
/// Indicates whether the item is on a menu strip.
/// </summary>
public bool IsOnStrip { get { return m_OnStrip; } set { m_OnStrip = value; } }
/// <summary>
/// Determines if the menu item is checkable.
/// </summary>
public bool IsCheckable { get { return m_Checkable; } set { m_Checkable = value; } }
/// <summary>
/// Indicates if the parent menu is open.
/// </summary>
public bool IsMenuOpen { get { if (m_Menu == null) return false; return !m_Menu.IsHidden; } }
/// <summary>
/// Gets or sets the check value.
/// </summary>
public bool IsChecked
{
get { return m_Checked; }
set
{
if (value == m_Checked)
return;
m_Checked = value;
if (CheckChanged != null)
CheckChanged.Invoke(this);
if (value)
{
if (Checked != null)
Checked.Invoke(this);
}
else
{
if (UnChecked != null)
UnChecked.Invoke(this);
}
}
}
/// <summary>
/// Gets the parent menu.
/// </summary>
public Menu Menu
{
get
{
if (null == m_Menu)
{
m_Menu = new Menu(GetCanvas());
m_Menu.IsHidden = true;
if (!m_OnStrip)
{
if (m_SubmenuArrow != null)
m_SubmenuArrow.Dispose();
m_SubmenuArrow = new RightArrow(this);
m_SubmenuArrow.SetSize(15, 15);
}
Invalidate();
}
return m_Menu;
}
}
/// <summary>
/// Invoked when the item is selected.
/// </summary>
public event GwenEventHandler Selected;
/// <summary>
/// Invoked when the item is checked.
/// </summary>
public event GwenEventHandler Checked;
/// <summary>
/// Invoked when the item is unchecked.
/// </summary>
public event GwenEventHandler UnChecked;
/// <summary>
/// Invoked when the item's check value is changed.
/// </summary>
public event GwenEventHandler CheckChanged;
/// <summary>
/// Initializes a new instance of the <see cref="MenuItem"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public MenuItem(Base parent)
: base(parent)
{
m_OnStrip = false;
IsTabable = false;
IsCheckable = false;
IsChecked = false;
m_Accelerator = new Label(this);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawMenuItem(this, IsMenuOpen, m_Checkable ? m_Checked : false);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
if (m_SubmenuArrow != null)
{
m_SubmenuArrow.Position(Pos.Right | Pos.CenterV, 4, 0);
}
base.Layout(skin);
}
/// <summary>
/// Internal OnPressed implementation.
/// </summary>
protected override void OnClicked()
{
if (m_Menu != null)
{
ToggleMenu();
}
else if (!m_OnStrip)
{
IsChecked = !IsChecked;
if (Selected != null)
Selected.Invoke(this);
GetCanvas().CloseMenus();
}
base.OnClicked();
}
/// <summary>
/// Toggles the menu open state.
/// </summary>
public void ToggleMenu()
{
if (IsMenuOpen)
CloseMenu();
else
OpenMenu();
}
/// <summary>
/// Opens the menu.
/// </summary>
public void OpenMenu()
{
if (null == m_Menu) return;
m_Menu.IsHidden = false;
m_Menu.BringToFront();
Point p = LocalPosToCanvas(Point.Empty);
// Strip menus open downwards
if (m_OnStrip)
{
m_Menu.SetPosition(p.X, p.Y + Height + 1);
}
// Submenus open sidewards
else
{
m_Menu.SetPosition(p.X + Width, p.Y);
}
// TODO: Option this.
// TODO: Make sure on screen, open the other side of the
// parent if it's better...
}
/// <summary>
/// Closes the menu.
/// </summary>
public void CloseMenu()
{
if (null == m_Menu) return;
m_Menu.Close();
m_Menu.CloseAll();
}
public override void SizeToContents()
{
base.SizeToContents();
if (m_Accelerator != null)
{
m_Accelerator.SizeToContents();
Width = Width + m_Accelerator.Width;
}
}
public MenuItem SetAction(GwenEventHandler handler)
{
if (m_Accelerator != null)
{
AddAccelerator(m_Accelerator.Text, handler);
}
Selected += handler;
return this;
}
public void SetAccelerator(String acc)
{
if (m_Accelerator != null)
{
//m_Accelerator.DelayedDelete(); // to prevent double disposing
m_Accelerator = null;
}
if (acc == string.Empty)
return;
m_Accelerator = new Label(this);
m_Accelerator.Dock = Pos.Right;
m_Accelerator.Alignment = Pos.Right | Pos.CenterV;
m_Accelerator.Text = acc;
m_Accelerator.Margin = new Margin(0, 0, 16, 0);
// todo
}
}
}

78
Gwen/Control/MenuStrip.cs Normal file
View file

@ -0,0 +1,78 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Menu strip.
/// </summary>
public class MenuStrip : Menu
{
/// <summary>
/// Initializes a new instance of the <see cref="MenuStrip"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public MenuStrip(Base parent)
: base(parent)
{
SetBounds(0, 0, 200, 22);
Dock = Pos.Top;
m_InnerPanel.Padding = new Padding(5, 0, 0, 0);
}
/// <summary>
/// Closes the current menu.
/// </summary>
public override void Close()
{
}
/// <summary>
/// Renders under the actual control (shadows etc).
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderUnder(Skin.Base skin)
{
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawMenuStrip(this);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
//TODO: We don't want to do vertical sizing the same as Menu, do nothing for now
}
/// <summary>
/// Determines whether the menu should open on mouse hover.
/// </summary>
protected override bool ShouldHoverOpenMenu
{
get { return IsMenuOpen(); }
}
/// <summary>
/// Add item handler.
/// </summary>
/// <param name="item">Item added.</param>
protected override void OnAddItem(MenuItem item)
{
item.Dock = Pos.Left;
item.TextPadding = new Padding(5, 0, 5, 0);
item.Padding = new Padding(10, 0, 10, 0);
item.SizeToContents();
item.IsOnStrip = true;
item.HoverEnter += OnHoverItem;
}
}
}

View file

@ -0,0 +1,67 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Simple message box.
/// </summary>
public class MessageBox : WindowControl
{
private readonly Button m_Button;
private readonly Label m_Label; // should be rich label with maxwidth = parent
/// <summary>
/// Invoked when the message box has been dismissed.
/// </summary>
public GwenEventHandler Dismissed;
/// <summary>
/// Initializes a new instance of the <see cref="MessageBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
/// <param name="text">Message to display.</param>
/// <param name="caption">Window caption.</param>
public MessageBox(Base parent, String text, String caption = "")
: base(parent, caption, true)
{
DeleteOnClose = true;
m_Label = new Label(m_InnerPanel);
m_Label.Text = text;
m_Label.Margin = Margin.Five;
m_Label.Dock = Pos.Top;
m_Label.Alignment = Pos.Center;
m_Label.AutoSizeToContents = true;
m_Button = new Button(m_InnerPanel);
m_Button.Text = "OK"; // todo: parametrize buttons
m_Button.Clicked += CloseButtonPressed;
m_Button.Clicked += DismissedHandler;
m_Button.Margin = Margin.Five;
m_Button.SetSize(50, 20);
Align.Center(this);
}
private void DismissedHandler(Base control)
{
if (Dismissed != null)
Dismissed.Invoke(this);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
Align.PlaceDownLeft(m_Button, m_Label, 10);
Align.CenterHorizontally(m_Button);
m_InnerPanel.SizeToChildren();
m_InnerPanel.Height += 10;
SizeToChildren();
}
}
}

View file

@ -0,0 +1,152 @@
using System;
using Gwen.Control.Layout;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Numeric up/down.
/// </summary>
public class NumericUpDown : TextBoxNumeric
{
private int m_Max;
private int m_Min;
private readonly Splitter m_Splitter;
private readonly UpDownButton_Up m_Up;
private readonly UpDownButton_Down m_Down;
/// <summary>
/// Minimum value.
/// </summary>
public int Min { get { return m_Min; } set { m_Min = value; } }
/// <summary>
/// Maximum value.
/// </summary>
public int Max { get { return m_Max; } set { m_Max = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="NumericUpDown"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public NumericUpDown(Base parent)
: base(parent)
{
SetSize(100, 20);
m_Splitter = new Splitter(this);
m_Splitter.Dock = Pos.Right;
m_Splitter.SetSize(13, 13);
m_Up = new UpDownButton_Up(m_Splitter);
m_Up.Clicked += OnButtonUp;
m_Up.IsTabable = false;
m_Splitter.SetPanel(0, m_Up, false);
m_Down = new UpDownButton_Down(m_Splitter);
m_Down.Clicked += OnButtonDown;
m_Down.IsTabable = false;
m_Down.Padding = new Padding(0, 1, 1, 0);
m_Splitter.SetPanel(1, m_Down, false);
m_Max = 100;
m_Min = 0;
m_Value = 0f;
Text = "0";
}
/// <summary>
/// Invoked when the value has been changed.
/// </summary>
public event GwenEventHandler ValueChanged;
/// <summary>
/// Handler for Up Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyUp(bool down)
{
if (down) OnButtonUp(null);
return true;
}
/// <summary>
/// Handler for Down Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyDown(bool down)
{
if (down) OnButtonDown(null);
return true;
}
/// <summary>
/// Handler for the button up event.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnButtonUp(Base control)
{
Value = m_Value + 1;
}
/// <summary>
/// Handler for the button down event.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnButtonDown(Base control)
{
Value = m_Value - 1;
}
/// <summary>
/// Determines whether the text can be assighed to the control.
/// </summary>
/// <param name="str">Text to evaluate.</param>
/// <returns>True if the text is allowed.</returns>
protected override bool IsTextAllowed(string str)
{
float d;
if (!float.TryParse(str, out d))
return false;
if (d < m_Min) return false;
if (d > m_Max) return false;
return true;
}
/// <summary>
/// Numeric value of the control.
/// </summary>
public override float Value
{
get
{
return base.Value;
}
set
{
if (value < m_Min) value = m_Min;
if (value > m_Max) value = m_Max;
if (value == m_Value) return;
base.Value = value;
}
}
/// <summary>
/// Handler for the text changed event.
/// </summary>
protected override void OnTextChanged()
{
base.OnTextChanged();
if (ValueChanged != null)
ValueChanged.Invoke(this);
}
}
}

View file

@ -0,0 +1,72 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Progress bar.
/// </summary>
public class ProgressBar : Label
{
private bool m_Horizontal;
private bool m_AutoLabel;
private float m_Progress;
/// <summary>
/// Determines whether the control is horizontal.
/// </summary>
public bool IsHorizontal { get { return m_Horizontal; } set { m_Horizontal = value; } }
/// <summary>
/// Progress value (0-1).
/// </summary>
public float Value
{
get { return m_Progress; }
set
{
if (value < 0)
value = 0;
if (value > 1)
value = 1;
m_Progress = value;
if (m_AutoLabel)
{
int displayVal = (int)(m_Progress * 100);
Text = displayVal.ToString() + "%";
}
}
}
/// <summary>
/// Determines whether the label text is autogenerated from value.
/// </summary>
public bool AutoLabel { get { return m_AutoLabel; } set { m_AutoLabel = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="ProgressBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ProgressBar(Base parent)
: base(parent)
{
MouseInputEnabled = false; // [omeg] what? was true
SetSize(128, 32);
TextPadding = Padding.Three;
IsHorizontal = true;
Alignment = Pos.Center;
m_Progress = 0;
m_AutoLabel = true;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawProgressBar(this, m_Horizontal, m_Progress);
}
}
}

108
Gwen/Control/Properties.cs Normal file
View file

@ -0,0 +1,108 @@
using System;
//using System.Windows.Forms;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Properties table.
/// </summary>
public class Properties : Base
{
private readonly SplitterBar m_SplitterBar;
/// <summary>
/// Returns the width of the first column (property names).
/// </summary>
public int SplitWidth { get { return m_SplitterBar.X; } } // todo: rename?
/// <summary>
/// Invoked when a property value has been changed.
/// </summary>
public event GwenEventHandler ValueChanged;
/// <summary>
/// Initializes a new instance of the <see cref="Properties"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Properties(Base parent)
: base(parent)
{
m_SplitterBar = new SplitterBar(this);
m_SplitterBar.SetPosition(80, 0);
//m_SplitterBar.Cursor = Cursors.SizeWE;
m_SplitterBar.Dragged += OnSplitterMoved;
m_SplitterBar.ShouldDrawBackground = false;
}
/// <summary>
/// Function invoked after layout.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void PostLayout(Skin.Base skin)
{
m_SplitterBar.Height = 0;
if (SizeToChildren(false, true))
{
InvalidateParent();
}
m_SplitterBar.SetSize(3, Height);
}
/// <summary>
/// Handles the splitter moved event.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnSplitterMoved(Base control)
{
InvalidateChildren();
}
/// <summary>
/// Adds a new text property row.
/// </summary>
/// <param name="label">Property name.</param>
/// <param name="value">Initial value.</param>
/// <returns>Newly created row.</returns>
public PropertyRow Add(String label, String value="")
{
return Add(label, new Property.Text(this), value);
}
/// <summary>
/// Adds a new property row.
/// </summary>
/// <param name="label">Property name.</param>
/// <param name="prop">Property control.</param>
/// <param name="value">Initial value.</param>
/// <returns>Newly created row.</returns>
public PropertyRow Add(String label, Property.Base prop, String value="")
{
PropertyRow row = new PropertyRow(this, prop);
row.Dock = Pos.Top;
row.Label = label;
row.ValueChanged += OnRowValueChanged;
prop.SetValue(value, true);
m_SplitterBar.BringToFront();
return row;
}
private void OnRowValueChanged(Base control)
{
if (ValueChanged != null)
ValueChanged.Invoke(control);
}
/// <summary>
/// Deletes all rows.
/// </summary>
public void DeleteAll()
{
m_InnerPanel.DeleteAllChildren();
}
}
}

View file

@ -0,0 +1,55 @@
using System;
namespace Gwen.Control.Property
{
/// <summary>
/// Base control for property entry.
/// </summary>
public class Base : Control.Base
{
/// <summary>
/// Initializes a new instance of the <see cref="Base"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Base(Control.Base parent) : base(parent)
{
Height = 17;
}
/// <summary>
/// Invoked when the property value has been changed.
/// </summary>
public event GwenEventHandler ValueChanged;
/// <summary>
/// Property value (todo: always string, which is ugly. do something about it).
/// </summary>
public virtual String Value { get { return null; } set { SetValue(value, false); } }
/// <summary>
/// Indicates whether the property value is being edited.
/// </summary>
public virtual bool IsEditing { get { return false; } }
protected virtual void DoChanged()
{
if (ValueChanged != null)
ValueChanged.Invoke(this);
}
protected virtual void OnValueChanged(Control.Base control)
{
DoChanged();
}
/// <summary>
/// Sets the property value.
/// </summary>
/// <param name="value">Value to set.</param>
/// <param name="fireEvents">Determines whether to fire "value changed" event.</param>
public virtual void SetValue(String value, bool fireEvents = false)
{
}
}
}

View file

@ -0,0 +1,71 @@
using System;
namespace Gwen.Control.Property
{
/// <summary>
/// Checkable property.
/// </summary>
public class Check : Base
{
protected readonly Control.CheckBox m_CheckBox;
/// <summary>
/// Initializes a new instance of the <see cref="Check"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Check(Control.Base parent)
: base(parent)
{
m_CheckBox = new Control.CheckBox(this);
m_CheckBox.ShouldDrawBackground = false;
m_CheckBox.CheckChanged += OnValueChanged;
m_CheckBox.IsTabable = true;
m_CheckBox.KeyboardInputEnabled = true;
m_CheckBox.SetPosition(2, 1);
Height = 18;
}
/// <summary>
/// Property value.
/// </summary>
public override string Value
{
get { return m_CheckBox.IsChecked ? "1" : "0"; }
set { base.Value = value; }
}
/// <summary>
/// Sets the property value.
/// </summary>
/// <param name="value">Value to set.</param>
/// <param name="fireEvents">Determines whether to fire "value changed" event.</param>
public override void SetValue(string value, bool fireEvents = false)
{
if (value == "1" || value.ToLower() == "true" || value.ToLower() == "yes")
{
m_CheckBox.IsChecked = true;
}
else
{
m_CheckBox.IsChecked = false;
}
}
/// <summary>
/// Indicates whether the property value is being edited.
/// </summary>
public override bool IsEditing
{
get { return m_CheckBox.HasFocus; }
}
/// <summary>
/// Indicates whether the control is hovered by mouse pointer.
/// </summary>
public override bool IsHovered
{
get { return base.IsHovered || m_CheckBox.IsHovered; }
}
}
}

View file

@ -0,0 +1,126 @@
using System;
using Gwen.ControlInternal;
using Gwen.Input;
namespace Gwen.Control.Property
{
/// <summary>
/// Color property.
/// </summary>
public class Color : Text
{
protected readonly ColorButton m_Button;
/// <summary>
/// Initializes a new instance of the <see cref="Color"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Color(Control.Base parent) : base(parent)
{
m_Button = new ColorButton(m_TextBox);
m_Button.Dock = Pos.Right;
m_Button.Width = 20;
m_Button.Margin = new Margin(1, 1, 1, 2);
m_Button.Clicked += OnButtonPressed;
}
/// <summary>
/// Color-select button press handler.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnButtonPressed(Control.Base control)
{
Menu menu = new Menu(GetCanvas());
menu.SetSize(256, 180);
menu.DeleteOnClose = true;
menu.IconMarginDisabled = true;
HSVColorPicker picker = new HSVColorPicker(menu);
picker.Dock = Pos.Fill;
picker.SetSize(256, 128);
String[] split = m_TextBox.Text.Split(' ');
picker.SetColor(GetColorFromText(), false, true);
picker.ColorChanged += OnColorChanged;
menu.Open(Pos.Right | Pos.Top);
}
/// <summary>
/// Color changed handler.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnColorChanged(Control.Base control)
{
HSVColorPicker picker = control as HSVColorPicker;
SetTextFromColor(picker.SelectedColor);
DoChanged();
}
/// <summary>
/// Property value.
/// </summary>
public override string Value
{
get { return m_TextBox.Text; }
set { base.Value = value; }
}
/// <summary>
/// Sets the property value.
/// </summary>
/// <param name="value">Value to set.</param>
/// <param name="fireEvents">Determines whether to fire "value changed" event.</param>
public override void SetValue(string value, bool fireEvents = false)
{
m_TextBox.SetText(value, fireEvents);
}
/// <summary>
/// Indicates whether the property value is being edited.
/// </summary>
public override bool IsEditing
{
get { return m_TextBox == InputHandler.KeyboardFocus; }
}
private void SetTextFromColor(System.Drawing.Color color)
{
m_TextBox.Text = String.Format("{0} {1} {2}", color.R, color.G, color.B);
}
private System.Drawing.Color GetColorFromText()
{
String[] split = m_TextBox.Text.Split(' ');
byte red = 0;
byte green = 0;
byte blue = 0;
byte alpha = 255;
if (split.Length > 0 && split[0].Length > 0)
{
Byte.TryParse(split[0], out red);
}
if (split.Length > 1 && split[1].Length > 0)
{
Byte.TryParse(split[1], out green);
}
if (split.Length > 2 && split[2].Length > 0)
{
Byte.TryParse(split[2], out blue);
}
return System.Drawing.Color.FromArgb(alpha, red, green, blue);
}
protected override void DoChanged()
{
base.DoChanged();
m_Button.Color = GetColorFromText();
}
}
}

View file

@ -0,0 +1,59 @@
using System;
namespace Gwen.Control.Property
{
/// <summary>
/// Text property.
/// </summary>
public class Text : Base
{
protected readonly TextBox m_TextBox;
/// <summary>
/// Initializes a new instance of the <see cref="Text"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Text(Control.Base parent) : base(parent)
{
m_TextBox = new TextBox(this);
m_TextBox.Dock = Pos.Fill;
m_TextBox.ShouldDrawBackground = false;
m_TextBox.TextChanged += OnValueChanged;
}
/// <summary>
/// Property value.
/// </summary>
public override string Value
{
get { return m_TextBox.Text; }
set { base.Value = value; }
}
/// <summary>
/// Sets the property value.
/// </summary>
/// <param name="value">Value to set.</param>
/// <param name="fireEvents">Determines whether to fire "value changed" event.</param>
public override void SetValue(string value, bool fireEvents = false)
{
m_TextBox.SetText(value, fireEvents);
}
/// <summary>
/// Indicates whether the property value is being edited.
/// </summary>
public override bool IsEditing
{
get { return m_TextBox.HasFocus; }
}
/// <summary>
/// Indicates whether the control is hovered by mouse pointer.
/// </summary>
public override bool IsHovered
{
get { return base.IsHovered | m_TextBox.IsHovered; }
}
}
}

123
Gwen/Control/PropertyRow.cs Normal file
View file

@ -0,0 +1,123 @@
using System;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Single property row.
/// </summary>
public class PropertyRow : Base
{
private readonly Label m_Label;
private readonly Property.Base m_Property;
private bool m_LastEditing;
private bool m_LastHover;
/// <summary>
/// Invoked when the property value has changed.
/// </summary>
public event GwenEventHandler ValueChanged;
/// <summary>
/// Indicates whether the property value is being edited.
/// </summary>
public bool IsEditing { get { return m_Property != null && m_Property.IsEditing; } }
/// <summary>
/// Property value.
/// </summary>
public String Value { get { return m_Property.Value; } set { m_Property.Value = value; } }
/// <summary>
/// Indicates whether the control is hovered by mouse pointer.
/// </summary>
public override bool IsHovered
{
get
{
return base.IsHovered || (m_Property != null && m_Property.IsHovered);
}
}
/// <summary>
/// Property name.
/// </summary>
public String Label { get { return m_Label.Text; } set { m_Label.Text = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="PropertyRow"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
/// <param name="prop">Property control associated with this row.</param>
public PropertyRow(Base parent, Property.Base prop)
: base(parent)
{
PropertyRowLabel label = new PropertyRowLabel(this);
label.Dock = Pos.Left;
label.Alignment = Pos.Left | Pos.Top;
label.Margin = new Margin(2, 2, 0, 0);
m_Label = label;
m_Property = prop;
m_Property.Parent = this;
m_Property.Dock = Pos.Fill;
m_Property.ValueChanged += OnValueChanged;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
/* SORRY */
if (IsEditing != m_LastEditing)
{
OnEditingChanged();
m_LastEditing = IsEditing;
}
if (IsHovered != m_LastHover)
{
OnHoverChanged();
m_LastHover = IsHovered;
}
/* SORRY */
skin.DrawPropertyRow(this, m_Label.Right, IsEditing, IsHovered | m_Property.IsHovered);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
Properties parent = Parent as Properties;
if (null == parent) return;
m_Label.Width = parent.SplitWidth;
if (m_Property != null)
{
Height = m_Property.Height;
}
}
protected virtual void OnValueChanged(Base control)
{
if (ValueChanged != null)
ValueChanged.Invoke(this);
}
private void OnEditingChanged()
{
m_Label.Redraw();
}
private void OnHoverChanged()
{
m_Label.Redraw();
}
}
}

View file

@ -0,0 +1,38 @@
using System;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Property table/tree.
/// </summary>
public class PropertyTree : TreeControl
{
/// <summary>
/// Initializes a new instance of the <see cref="PropertyTree"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public PropertyTree(Base parent)
: base(parent)
{
}
/// <summary>
/// Adds a new properties node.
/// </summary>
/// <param name="label">Node label.</param>
/// <returns>Newly created control</returns>
public Properties Add(String label)
{
TreeNode node = new PropertyTreeNode(this);
node.Text = label;
node.Dock = Pos.Top;
Properties props = new Properties(node);
props.Dock = Pos.Top;
return props;
}
}
}

View file

@ -0,0 +1,39 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Radio button.
/// </summary>
public class RadioButton : CheckBox
{
/// <summary>
/// Determines whether unchecking is allowed.
/// </summary>
protected override bool AllowUncheck
{
get { return false; }
}
/// <summary>
/// Initializes a new instance of the <see cref="RadioButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public RadioButton(Base parent)
: base(parent)
{
SetSize(15, 15);
MouseInputEnabled = true;
IsTabable = false;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawRadioButton(this, IsChecked, IsDepressed);
}
}
}

View file

@ -0,0 +1,137 @@
using System;
using System.Linq;
namespace Gwen.Control
{
/// <summary>
/// Radio button group.
/// </summary>
public class RadioButtonGroup : GroupBox
{
private LabeledRadioButton m_Selected;
/// <summary>
/// Selected radio button.
/// </summary>
public LabeledRadioButton Selected { get { return m_Selected; } }
/// <summary>
/// Internal name of the selected radio button.
/// </summary>
public String SelectedName { get { return m_Selected.Name; } }
/// <summary>
/// Text of the selected radio button.
/// </summary>
public String SelectedLabel { get { return m_Selected.Text; } }
/// <summary>
/// Index of the selected radio button.
/// </summary>
public int SelectedIndex { get { return Children.IndexOf(m_Selected); } }
/// <summary>
/// Invoked when the selected option has changed.
/// </summary>
public event GwenEventHandler SelectionChanged;
/// <summary>
/// Initializes a new instance of the <see cref="RadioButtonGroup"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
/// <param name="label">Label for the outlining GroupBox.</param>
public RadioButtonGroup(Base parent, String label)
: base(parent)
{
IsTabable = false;
KeyboardInputEnabled = true;
Text = label;
AutoSizeToContents = true;
}
/// <summary>
/// Adds a new option.
/// </summary>
/// <param name="text">Option text.</param>
/// <returns>Newly created control.</returns>
public virtual LabeledRadioButton AddOption(String text)
{
return AddOption(text, String.Empty);
}
/// <summary>
/// Adds a new option.
/// </summary>
/// <param name="text">Option text.</param>
/// <param name="optionName">Internal name.</param>
/// <returns>Newly created control.</returns>
public virtual LabeledRadioButton AddOption(String text, String optionName)
{
LabeledRadioButton lrb = new LabeledRadioButton(this);
lrb.Name = optionName;
lrb.Text = text;
lrb.RadioButton.Checked += OnRadioClicked;
lrb.Dock = Pos.Top;
lrb.Margin = new Margin(0, 0, 0, 1); // 1 bottom
lrb.KeyboardInputEnabled = false; // todo: true?
lrb.IsTabable = true;
Invalidate();
return lrb;
}
/// <summary>
/// Handler for the option change.
/// </summary>
/// <param name="fromPanel">Event source.</param>
protected virtual void OnRadioClicked(Base fromPanel)
{
RadioButton @checked = fromPanel as RadioButton;
foreach (LabeledRadioButton rb in Children.OfType<LabeledRadioButton>()) // todo: optimize
{
if (rb.RadioButton == @checked)
m_Selected = rb;
else
rb.RadioButton.IsChecked = false;
}
OnChanged();
}
/*
/// <summary>
/// Sizes to contents.
/// </summary>
public override void SizeToContents()
{
RecurseLayout(Skin); // options are docked so positions are not updated until layout runs
//base.SizeToContents();
int width = 0;
int height = 0;
foreach (Base child in Children)
{
width = Math.Max(child.Width, width);
height += child.Height;
}
SetSize(width, height);
InvalidateParent();
}
*/
protected virtual void OnChanged()
{
if (SelectionChanged != null)
SelectionChanged.Invoke(this);
}
/// <summary>
/// Selects the specified option.
/// </summary>
/// <param name="index">Option to select.</param>
public void SetSelection(int index)
{
if (index < 0 || index >= Children.Count)
return;
(Children[index] as LabeledRadioButton).RadioButton.Press();
}
}
}

View file

@ -0,0 +1,160 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Base resizable control.
/// </summary>
public class ResizableControl : Base
{
private bool m_ClampMovement;
private readonly Resizer[] m_Resizer;
/// <summary>
/// Determines whether control's position should be restricted to its parent bounds.
/// </summary>
public bool ClampMovement { get { return m_ClampMovement; } set { m_ClampMovement = value; } }
/// <summary>
/// Invoked when the control has been resized.
/// </summary>
public event GwenEventHandler Resized;
/// <summary>
/// Initializes a new instance of the <see cref="ResizableControl"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ResizableControl(Base parent)
: base(parent)
{
m_Resizer = new Resizer[10];
MinimumSize = new Point(5, 5);
m_ClampMovement = false;
m_Resizer[2] = new Resizer(this);
m_Resizer[2].Dock = Pos.Bottom;
m_Resizer[2].ResizeDir = Pos.Bottom;
m_Resizer[2].Resized += OnResized;
m_Resizer[2].Target = this;
m_Resizer[1] = new Resizer(m_Resizer[2]);
m_Resizer[1].Dock = Pos.Left;
m_Resizer[1].ResizeDir = Pos.Bottom | Pos.Left;
m_Resizer[1].Resized += OnResized;
m_Resizer[1].Target = this;
m_Resizer[3] = new Resizer(m_Resizer[2]);
m_Resizer[3].Dock = Pos.Right;
m_Resizer[3].ResizeDir = Pos.Bottom | Pos.Right;
m_Resizer[3].Resized += OnResized;
m_Resizer[3].Target = this;
m_Resizer[8] = new Resizer(this);
m_Resizer[8].Dock = Pos.Top;
m_Resizer[8].ResizeDir = Pos.Top;
m_Resizer[8].Resized += OnResized;
m_Resizer[8].Target = this;
m_Resizer[7] = new Resizer(m_Resizer[8]);
m_Resizer[7].Dock = Pos.Left;
m_Resizer[7].ResizeDir = Pos.Left | Pos.Top;
m_Resizer[7].Resized += OnResized;
m_Resizer[7].Target = this;
m_Resizer[9] = new Resizer(m_Resizer[8]);
m_Resizer[9].Dock = Pos.Right;
m_Resizer[9].ResizeDir = Pos.Right | Pos.Top;
m_Resizer[9].Resized += OnResized;
m_Resizer[9].Target = this;
m_Resizer[4] = new Resizer(this);
m_Resizer[4].Dock = Pos.Left;
m_Resizer[4].ResizeDir = Pos.Left;
m_Resizer[4].Resized += OnResized;
m_Resizer[4].Target = this;
m_Resizer[6] = new Resizer(this);
m_Resizer[6].Dock = Pos.Right;
m_Resizer[6].ResizeDir = Pos.Right;
m_Resizer[6].Resized += OnResized;
m_Resizer[6].Target = this;
}
/// <summary>
/// Handler for the resized event.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnResized(Base control)
{
if (Resized != null)
Resized.Invoke(this);
}
protected Resizer GetResizer(int i)
{
return m_Resizer[i];
}
/// <summary>
/// Disables resizing.
/// </summary>
public void DisableResizing()
{
for (int i = 0; i < 10; i++)
{
if (m_Resizer[i] == null)
continue;
m_Resizer[i].MouseInputEnabled = false;
m_Resizer[i].IsHidden = true;
Padding = new Padding(m_Resizer[i].Width, m_Resizer[i].Width, m_Resizer[i].Width, m_Resizer[i].Width);
}
}
/// <summary>
/// Enables resizing.
/// </summary>
public void EnableResizing()
{
for (int i = 0; i < 10; i++)
{
if (m_Resizer[i] == null)
continue;
m_Resizer[i].MouseInputEnabled = true;
m_Resizer[i].IsHidden = false;
Padding = new Padding(0, 0, 0, 0); // todo: check if ok
}
}
/// <summary>
/// Sets the control bounds.
/// </summary>
/// <param name="x">X position.</param>
/// <param name="y">Y position.</param>
/// <param name="width">Width.</param>
/// <param name="height">Height.</param>
/// <returns>
/// True if bounds changed.
/// </returns>
public override bool SetBounds(int x, int y, int width, int height)
{
Point minSize = MinimumSize;
// Clamp Minimum Size
if (width < minSize.X) width = minSize.X;
if (height < minSize.Y) height = minSize.Y;
// Clamp to parent's window
Base parent = Parent;
if (parent != null && m_ClampMovement)
{
if (x + width > parent.Width) x = parent.Width - width;
if (x < 0) x = 0;
if (y + height > parent.Height) y = parent.Height - height;
if (y < 0) y = 0;
}
return base.SetBounds(x, y, width, height);
}
}
}

254
Gwen/Control/RichLabel.cs Normal file
View file

@ -0,0 +1,254 @@
using System;
using System.Collections.Generic;
using System.Drawing;
namespace Gwen.Control
{
/// <summary>
/// Multiline label with text chunks having different color/font.
/// </summary>
public class RichLabel : Base
{
protected struct TextBlock
{
public BlockType Type;
public String Text;
public Color Color;
public Font Font;
}
protected enum BlockType
{
Text,
NewLine
}
private bool m_NeedsRebuild;
private readonly List<TextBlock> m_TextBlocks;
private readonly String[] newline;
/// <summary>
/// Initializes a new instance of the <see cref="RichLabel"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public RichLabel(Base parent)
: base(parent)
{
newline = new string[] { Environment.NewLine };
m_TextBlocks = new List<TextBlock>();
}
/// <summary>
/// Adds a line break to the control.
/// </summary>
public void AddLineBreak()
{
TextBlock block = new TextBlock { Type = BlockType.NewLine };
m_TextBlocks.Add(block);
}
/// <summary>
/// Adds text to the control.
/// </summary>
/// <param name="text">Text to add.</param>
/// <param name="color">Text color.</param>
/// <param name="font">Font to use.</param>
public void AddText(String text, Color color, Font font = null)
{
if (String.IsNullOrEmpty(text))
return;
var lines = text.Split(newline, StringSplitOptions.None);
for (int i = 0; i < lines.Length; i++)
{
if (i > 0)
AddLineBreak();
TextBlock block = new TextBlock { Type = BlockType.Text, Text = lines[i], Color = color, Font = font };
m_TextBlocks.Add(block);
m_NeedsRebuild = true;
Invalidate();
}
}
/// <summary>
/// Resizes the control to fit its children.
/// </summary>
/// <param name="width">Determines whether to change control's width.</param>
/// <param name="height">Determines whether to change control's height.</param>
/// <returns>
/// True if bounds changed.
/// </returns>
public override bool SizeToChildren(bool width = true, bool height = true)
{
Rebuild();
return base.SizeToChildren(width, height);
}
protected void SplitLabel(String text, Font font, TextBlock block, ref int x, ref int y, ref int lineHeight)
{
var spaced = Util.SplitAndKeep(text, " ");
if (spaced.Length == 0)
return;
int spaceLeft = Width - x;
String leftOver;
// Does the whole word fit in?
Point stringSize = Skin.Renderer.MeasureText(font, text);
if (spaceLeft > stringSize.X)
{
CreateLabel(text, block, ref x, ref y, ref lineHeight, true);
return;
}
// If the first word is bigger than the line, just give up.
Point wordSize = Skin.Renderer.MeasureText(font, spaced[0]);
if (wordSize.X >= spaceLeft)
{
CreateLabel(spaced[0], block, ref x, ref y, ref lineHeight, true);
if (spaced[0].Length >= text.Length)
return;
leftOver = text.Substring(spaced[0].Length + 1);
SplitLabel(leftOver, font, block, ref x, ref y, ref lineHeight);
return;
}
String newString = String.Empty;
for (int i = 0; i < spaced.Length; i++)
{
wordSize = Skin.Renderer.MeasureText(font, newString + spaced[i]);
if (wordSize.X > spaceLeft)
{
CreateLabel(newString, block, ref x, ref y, ref lineHeight, true);
x = 0;
y += lineHeight;
break;
}
newString += spaced[i];
}
int newstr_len = newString.Length;
if (newstr_len < text.Length)
{
leftOver = text.Substring(newstr_len + 1);
SplitLabel(leftOver, font, block, ref x, ref y, ref lineHeight);
}
}
protected void CreateLabel(String text, TextBlock block, ref int x, ref int y, ref int lineHeight, bool noSplit)
{
// Use default font or is one set?
Font font = Skin.DefaultFont;
if (block.Font != null)
font = block.Font;
// This string is too long for us, split it up.
Point p = Skin.Renderer.MeasureText(font, text);
if (lineHeight == -1)
{
lineHeight = p.Y;
}
if (!noSplit)
{
if (x + p.X > Width)
{
SplitLabel(text, font, block, ref x, ref y, ref lineHeight);
return;
}
}
// Wrap
if (x + p.X >= Width)
{
CreateNewline(ref x, ref y, lineHeight);
}
Label label = new Label(this);
label.SetText(x == 0 ? text.TrimStart(' ') : text);
label.TextColor = block.Color;
label.Font = font;
label.SizeToContents();
label.SetPosition(x, y);
//lineheight = (lineheight + pLabel.Height()) / 2;
x += label.Width;
if (x >= Width)
{
CreateNewline(ref x, ref y, lineHeight);
}
}
protected void CreateNewline(ref int x, ref int y, int lineHeight)
{
x = 0;
y += lineHeight;
}
protected void Rebuild()
{
DeleteAllChildren();
int x = 0;
int y = 0;
int lineHeight = -1;
foreach (var block in m_TextBlocks)
{
if (block.Type == BlockType.NewLine)
{
CreateNewline(ref x, ref y, lineHeight);
continue;
}
if (block.Type == BlockType.Text)
{
CreateLabel(block.Text, block, ref x, ref y, ref lineHeight, false);
continue;
}
}
m_NeedsRebuild = false;
}
/// <summary>
/// Handler invoked when control's bounds change.
/// </summary>
/// <param name="oldBounds">Old bounds.</param>
protected override void OnBoundsChanged(Rectangle oldBounds)
{
base.OnBoundsChanged(oldBounds);
Rebuild();
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
if (m_NeedsRebuild)
Rebuild();
// align bottoms. this is still not ideal, need to take font metrics into account.
Base prev = null;
foreach (Base child in Children)
{
if (prev != null && child.Y == prev.Y)
{
Align.PlaceRightBottom(child, prev);
}
prev = child;
}
}
}
}

133
Gwen/Control/ScrollBar.cs Normal file
View file

@ -0,0 +1,133 @@
using System;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Base class for scrollbars.
/// </summary>
public class ScrollBar : Base
{
protected readonly ScrollBarButton[] m_ScrollButton;
protected readonly ScrollBarBar m_Bar;
protected bool m_Depressed;
protected float m_ScrollAmount;
protected float m_ContentSize;
protected float m_ViewableContentSize;
protected float m_NudgeAmount;
/// <summary>
/// Invoked when the bar is moved.
/// </summary>
public event GwenEventHandler BarMoved;
/// <summary>
/// Bar size (in pixels).
/// </summary>
public virtual int BarSize { get; set; }
/// <summary>
/// Bar position (in pixels).
/// </summary>
public virtual int BarPos { get { return 0; } }
/// <summary>
/// Button size (in pixels).
/// </summary>
public virtual int ButtonSize { get { return 0; } }
public virtual float NudgeAmount { get { return m_NudgeAmount / m_ContentSize; } set { m_NudgeAmount = value; } }
public float ScrollAmount { get { return m_ScrollAmount; } }
public float ContentSize { get { return m_ContentSize; } set { if (m_ContentSize != value) Invalidate(); m_ContentSize = value; } }
public float ViewableContentSize { get { return m_ViewableContentSize; } set { if (m_ViewableContentSize != value) Invalidate(); m_ViewableContentSize = value; } }
/// <summary>
/// Indicates whether the bar is horizontal.
/// </summary>
public virtual bool IsHorizontal { get { return false; } }
/// <summary>
/// Initializes a new instance of the <see cref="ScrollBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
protected ScrollBar(Base parent) : base(parent)
{
m_ScrollButton = new ScrollBarButton[2];
m_ScrollButton[0] = new ScrollBarButton(this);
m_ScrollButton[1] = new ScrollBarButton(this);
m_Bar = new ScrollBarBar(this);
SetBounds(0, 0, 15, 15);
m_Depressed = false;
m_ScrollAmount = 0;
m_ContentSize = 0;
m_ViewableContentSize = 0;
NudgeAmount = 20;
}
/// <summary>
/// Sets the scroll amount (0-1).
/// </summary>
/// <param name="value">Scroll amount.</param>
/// <param name="forceUpdate">Determines whether the control should be updated.</param>
/// <returns>True if control state changed.</returns>
public virtual bool SetScrollAmount(float value, bool forceUpdate = false)
{
if (m_ScrollAmount == value && !forceUpdate)
return false;
m_ScrollAmount = value;
Invalidate();
OnBarMoved(this);
return true;
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawScrollBar(this, IsHorizontal, m_Depressed);
}
/// <summary>
/// Handler for the BarMoved event.
/// </summary>
/// <param name="control">The control.</param>
protected virtual void OnBarMoved(Base control)
{
if (BarMoved != null)
BarMoved.Invoke(this);
}
protected virtual float CalculateScrolledAmount()
{
return 0;
}
protected virtual int CalculateBarSize()
{
return 0;
}
public virtual void ScrollToLeft() { }
public virtual void ScrollToRight() { }
public virtual void ScrollToTop() { }
public virtual void ScrollToBottom() { }
}
}

View file

@ -0,0 +1,301 @@
using System;
using System.Linq;
namespace Gwen.Control
{
/// <summary>
/// Base for controls whose interior can be scrolled.
/// </summary>
public class ScrollControl : Base
{
private bool m_CanScrollH;
private bool m_CanScrollV;
private bool m_AutoHideBars;
private readonly ScrollBar m_VerticalScrollBar;
private readonly ScrollBar m_HorizontalScrollBar;
/// <summary>
/// Indicates whether the control can be scrolled horizontally.
/// </summary>
public bool CanScrollH { get { return m_CanScrollH; } }
/// <summary>
/// Indicates whether the control can be scrolled vertically.
/// </summary>
public bool CanScrollV { get { return m_CanScrollV; } }
/// <summary>
/// Determines whether the scroll bars should be hidden if not needed.
/// </summary>
public bool AutoHideBars { get { return m_AutoHideBars; } set { m_AutoHideBars = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="ScrollControl"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ScrollControl(Base parent)
: base(parent)
{
MouseInputEnabled = false;
m_VerticalScrollBar = new VerticalScrollBar(this);
m_VerticalScrollBar.Dock = Pos.Right;
m_VerticalScrollBar.BarMoved += VBarMoved;
m_CanScrollV = true;
m_VerticalScrollBar.NudgeAmount = 30;
m_HorizontalScrollBar = new HorizontalScrollBar(this);
m_HorizontalScrollBar.Dock = Pos.Bottom;
m_HorizontalScrollBar.BarMoved += HBarMoved;
m_CanScrollH = true;
m_HorizontalScrollBar.NudgeAmount = 30;
m_InnerPanel = new Base(this);
m_InnerPanel.SetPosition(0, 0);
m_InnerPanel.Margin = Margin.Five;
m_InnerPanel.SendToBack();
m_InnerPanel.MouseInputEnabled = false;
m_AutoHideBars = false;
}
protected bool HScrollRequired
{
set
{
if (value)
{
m_HorizontalScrollBar.SetScrollAmount(0, true);
m_HorizontalScrollBar.IsDisabled = true;
if (m_AutoHideBars)
m_HorizontalScrollBar.IsHidden = true;
}
else
{
m_HorizontalScrollBar.IsHidden = false;
m_HorizontalScrollBar.IsDisabled = false;
}
}
}
protected bool VScrollRequired
{
set
{
if (value)
{
m_VerticalScrollBar.SetScrollAmount(0, true);
m_VerticalScrollBar.IsDisabled = true;
if (m_AutoHideBars)
m_VerticalScrollBar.IsHidden = true;
}
else
{
m_VerticalScrollBar.IsHidden = false;
m_VerticalScrollBar.IsDisabled = false;
}
}
}
/// <summary>
/// Enables or disables inner scrollbars.
/// </summary>
/// <param name="horizontal">Determines whether the horizontal scrollbar should be enabled.</param>
/// <param name="vertical">Determines whether the vertical scrollbar should be enabled.</param>
public virtual void EnableScroll(bool horizontal, bool vertical)
{
m_CanScrollV = vertical;
m_CanScrollH = horizontal;
m_VerticalScrollBar.IsHidden = !m_CanScrollV;
m_HorizontalScrollBar.IsHidden = !m_CanScrollH;
}
public virtual void SetInnerSize(int width, int height)
{
m_InnerPanel.SetSize(width, height);
}
protected virtual void VBarMoved(Base control)
{
Invalidate();
}
protected virtual void HBarMoved(Base control)
{
Invalidate();
}
/// <summary>
/// Handler invoked when control children's bounds change.
/// </summary>
/// <param name="oldChildBounds"></param>
/// <param name="child"></param>
protected override void OnChildBoundsChanged(System.Drawing.Rectangle oldChildBounds, Base child)
{
UpdateScrollBars();
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
UpdateScrollBars();
base.Layout(skin);
}
/// <summary>
/// Handler invoked on mouse wheel event.
/// </summary>
/// <param name="delta">Scroll delta.</param>
/// <returns></returns>
protected override bool OnMouseWheeled(int delta)
{
if (CanScrollV && m_VerticalScrollBar.IsVisible)
{
if (m_VerticalScrollBar.SetScrollAmount(
m_VerticalScrollBar.ScrollAmount - m_VerticalScrollBar.NudgeAmount * (delta / 60.0f), true))
return true;
}
if (CanScrollH && m_HorizontalScrollBar.IsVisible)
{
if (m_HorizontalScrollBar.SetScrollAmount(
m_HorizontalScrollBar.ScrollAmount - m_HorizontalScrollBar.NudgeAmount * (delta / 60.0f), true))
return true;
}
return false;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
#if false
// Debug render - this shouldn't render ANYTHING REALLY - it should be up to the parent!
Gwen::Rect rect = GetRenderBounds();
Gwen::Renderer::Base* render = skin->GetRender();
render->SetDrawColor( Gwen::Color( 255, 255, 0, 100 ) );
render->DrawFilledRect( rect );
render->SetDrawColor( Gwen::Color( 255, 0, 0, 100 ) );
render->DrawFilledRect( m_InnerPanel->GetBounds() );
render->RenderText( skin->GetDefaultFont(), Gwen::Point( 0, 0 ), Utility::Format( L"Offset: %i %i", m_InnerPanel->X(), m_InnerPanel->Y() ) );
#endif
}
public virtual void UpdateScrollBars()
{
if (null == m_InnerPanel)
return;
//Get the max size of all our children together
int childrenWidth = Children.Count > 0 ? Children.Max(x => x.Right) : 0;
int childrenHeight = Children.Count > 0 ? Children.Max(x => x.Bottom) : 0;
if (m_CanScrollH)
{
m_InnerPanel.SetSize(Math.Max(Width, childrenWidth), Math.Max(Height, childrenHeight));
}
else
{
m_InnerPanel.SetSize(Width - (m_VerticalScrollBar.IsHidden ? 0 : m_VerticalScrollBar.Width),
Math.Max(Height, childrenHeight));
}
float wPercent = Width/
(float) (childrenWidth + (m_VerticalScrollBar.IsHidden ? 0 : m_VerticalScrollBar.Width));
float hPercent = Height/
(float)
(childrenHeight + (m_HorizontalScrollBar.IsHidden ? 0 : m_HorizontalScrollBar.Height));
if (m_CanScrollV)
VScrollRequired = hPercent >= 1;
else
m_VerticalScrollBar.IsHidden = true;
if (m_CanScrollH)
HScrollRequired = wPercent >= 1;
else
m_HorizontalScrollBar.IsHidden = true;
m_VerticalScrollBar.ContentSize = m_InnerPanel.Height;
m_VerticalScrollBar.ViewableContentSize = Height - (m_HorizontalScrollBar.IsHidden ? 0 : m_HorizontalScrollBar.Height);
m_HorizontalScrollBar.ContentSize = m_InnerPanel.Width;
m_HorizontalScrollBar.ViewableContentSize = Width - (m_VerticalScrollBar.IsHidden ? 0 : m_VerticalScrollBar.Width);
int newInnerPanelPosX = 0;
int newInnerPanelPosY = 0;
if (CanScrollV && !m_VerticalScrollBar.IsHidden)
{
newInnerPanelPosY =
(int)(
-((m_InnerPanel.Height) - Height + (m_HorizontalScrollBar.IsHidden ? 0 : m_HorizontalScrollBar.Height))*
m_VerticalScrollBar.ScrollAmount);
}
if (CanScrollH && !m_HorizontalScrollBar.IsHidden)
{
newInnerPanelPosX =
(int)(
-((m_InnerPanel.Width) - Width + (m_VerticalScrollBar.IsHidden ? 0 : m_VerticalScrollBar.Width))*
m_HorizontalScrollBar.ScrollAmount);
}
m_InnerPanel.SetPosition(newInnerPanelPosX, newInnerPanelPosY);
}
public virtual void ScrollToBottom()
{
if (!CanScrollV)
return;
UpdateScrollBars();
m_VerticalScrollBar.ScrollToBottom();
}
public virtual void ScrollToTop()
{
if (CanScrollV)
{
UpdateScrollBars();
m_VerticalScrollBar.ScrollToTop();
}
}
public virtual void ScrollToLeft()
{
if (CanScrollH)
{
UpdateScrollBars();
m_VerticalScrollBar.ScrollToLeft();
}
}
public virtual void ScrollToRight()
{
if (CanScrollH)
{
UpdateScrollBars();
m_VerticalScrollBar.ScrollToRight();
}
}
public virtual void DeleteAll()
{
m_InnerPanel.DeleteAllChildren();
}
}
}

236
Gwen/Control/Slider.cs Normal file
View file

@ -0,0 +1,236 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Base slider.
/// </summary>
public class Slider : Base
{
protected readonly SliderBar m_SliderBar;
protected bool m_SnapToNotches;
protected int m_NotchCount;
protected float m_Value;
protected float m_Min;
protected float m_Max;
/// <summary>
/// Number of notches on the slider axis.
/// </summary>
public int NotchCount { get { return m_NotchCount; } set { m_NotchCount = value; } }
/// <summary>
/// Determines whether the slider should snap to notches.
/// </summary>
public bool SnapToNotches { get { return m_SnapToNotches; } set { m_SnapToNotches = value; } }
/// <summary>
/// Minimum value.
/// </summary>
public float Min { get { return m_Min; } set { SetRange(value, m_Max); } }
/// <summary>
/// Maximum value.
/// </summary>
public float Max { get { return m_Max; } set { SetRange(m_Min, value); } }
/// <summary>
/// Current value.
/// </summary>
public float Value
{
get { return m_Min + (m_Value * (m_Max - m_Min)); }
set
{
if (value < m_Min) value = m_Min;
if (value > m_Max) value = m_Max;
// Normalize Value
value = (value - m_Min) / (m_Max - m_Min);
SetValueInternal(value);
Redraw();
}
}
/// <summary>
/// Invoked when the value has been changed.
/// </summary>
public event GwenEventHandler ValueChanged;
/// <summary>
/// Initializes a new instance of the <see cref="Slider"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
protected Slider(Base parent)
: base(parent)
{
SetBounds(new Rectangle(0, 0, 32, 128));
m_SliderBar = new SliderBar(this);
m_SliderBar.Dragged += OnMoved;
m_Min = 0.0f;
m_Max = 1.0f;
m_SnapToNotches = false;
m_NotchCount = 5;
m_Value = 0.0f;
KeyboardInputEnabled = true;
IsTabable = true;
}
/// <summary>
/// Handler for Right Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyRight(bool down)
{
if (down)
Value = Value + 1;
return true;
}
/// <summary>
/// Handler for Up Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyUp(bool down)
{
if (down)
Value = Value + 1;
return true;
}
/// <summary>
/// Handler for Left Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyLeft(bool down)
{
if (down)
Value = Value - 1;
return true;
}
/// <summary>
/// Handler for Down Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyDown(bool down)
{
if (down)
Value = Value - 1;
return true;
}
/// <summary>
/// Handler for Home keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyHome(bool down)
{
if (down)
Value = m_Min;
return true;
}
/// <summary>
/// Handler for End keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyEnd(bool down)
{
if (down)
Value = m_Max;
return true;
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
}
protected virtual void OnMoved(Base control)
{
SetValueInternal(CalculateValue());
}
protected virtual float CalculateValue()
{
return 0;
}
protected virtual void UpdateBarFromValue()
{
}
protected virtual void SetValueInternal(float val)
{
if (m_SnapToNotches)
{
val = (float)Math.Floor((val * m_NotchCount) + 0.5f);
val /= m_NotchCount;
}
if (m_Value != val)
{
m_Value = val;
if (ValueChanged != null)
ValueChanged.Invoke(this);
}
UpdateBarFromValue();
}
/// <summary>
/// Sets the value range.
/// </summary>
/// <param name="min">Minimum value.</param>
/// <param name="max">Maximum value.</param>
public void SetRange(float min, float max)
{
m_Min = min;
m_Max = max;
}
/// <summary>
/// Renders the focus overlay.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderFocus(Skin.Base skin)
{
if (InputHandler.KeyboardFocus != this) return;
if (!IsTabable) return;
skin.DrawKeyboardHighlight(this, RenderBounds, 0);
}
}
}

44
Gwen/Control/StatusBar.cs Normal file
View file

@ -0,0 +1,44 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Status bar.
/// </summary>
public class StatusBar : Label
{
/// <summary>
/// Initializes a new instance of the <see cref="StatusBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public StatusBar(Base parent)
: base(parent)
{
Height = 22;
Dock = Pos.Bottom;
Padding = Padding.Two;
//Text = "Status Bar"; // [omeg] todo i18n
Alignment = Pos.Left | Pos.CenterV;
}
/// <summary>
/// Adds a control to the bar.
/// </summary>
/// <param name="control">Control to add.</param>
/// <param name="right">Determines whether the control should be added to the right side of the bar.</param>
public void AddControl(Base control, bool right)
{
control.Parent = this;
control.Dock = right ? Pos.Right : Pos.Left;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawStatusBar(this);
}
}
}

203
Gwen/Control/TabButton.cs Normal file
View file

@ -0,0 +1,203 @@
using System;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Tab header.
/// </summary>
public class TabButton : Button
{
private Base m_Page;
private TabControl m_Control;
/// <summary>
/// Indicates whether the tab is active.
/// </summary>
public bool IsActive { get { return m_Page != null && m_Page.IsVisible; } }
// todo: remove public access
public TabControl TabControl
{
get { return m_Control; }
set
{
if (value == m_Control) return;
if (m_Control != null)
m_Control.OnLoseTab(this);
m_Control = value;
}
}
/// <summary>
/// Interior of the tab.
/// </summary>
public Base Page { get { return m_Page; } set { m_Page = value; } }
/// <summary>
/// Determines whether the control should be clipped to its bounds while rendering.
/// </summary>
protected override bool ShouldClip
{
get { return false; }
}
/// <summary>
/// Initializes a new instance of the <see cref="TabButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TabButton(Base parent)
: base(parent)
{
DragAndDrop_SetPackage(true, "TabButtonMove");
Alignment = Pos.Top | Pos.Left;
TextPadding = new Padding(5, 3, 3, 3);
Padding = Padding.Two;
KeyboardInputEnabled = true;
}
public override void DragAndDrop_StartDragging(DragDrop.Package package, int x, int y)
{
IsHidden = true;
}
public override void DragAndDrop_EndDragging(bool success, int x, int y)
{
IsHidden = false;
IsDepressed = false;
}
public override bool DragAndDrop_ShouldStartDrag()
{
return m_Control.AllowReorder;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawTabButton(this, IsActive, m_Control.TabStrip.Dock);
}
/// <summary>
/// Handler for Down Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyDown(bool down)
{
OnKeyRight(down);
return true;
}
/// <summary>
/// Handler for Up Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyUp(bool down)
{
OnKeyLeft(down);
return true;
}
/// <summary>
/// Handler for Right Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyRight(bool down)
{
if (down)
{
var count = Parent.Children.Count;
int me = Parent.Children.IndexOf(this);
if (me + 1 < count)
{
var nextTab = Parent.Children[me + 1];
TabControl.OnTabPressed(nextTab);
InputHandler.KeyboardFocus = nextTab;
}
}
return true;
}
/// <summary>
/// Handler for Left Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyLeft(bool down)
{
if (down)
{
var count = Parent.Children.Count;
int me = Parent.Children.IndexOf(this);
if (me - 1 >= 0)
{
var prevTab = Parent.Children[me - 1];
TabControl.OnTabPressed(prevTab);
InputHandler.KeyboardFocus = prevTab;
}
}
return true;
}
/// <summary>
/// Updates control colors.
/// </summary>
public override void UpdateColors()
{
if (IsActive)
{
if (IsDisabled)
{
TextColor = Skin.Colors.Tab.Active.Disabled;
return;
}
if (IsDepressed)
{
TextColor = Skin.Colors.Tab.Active.Down;
return;
}
if (IsHovered)
{
TextColor = Skin.Colors.Tab.Active.Hover;
return;
}
TextColor = Skin.Colors.Tab.Active.Normal;
}
if (IsDisabled)
{
TextColor = Skin.Colors.Tab.Inactive.Disabled;
return;
}
if (IsDepressed)
{
TextColor = Skin.Colors.Tab.Inactive.Down;
return;
}
if (IsHovered)
{
TextColor = Skin.Colors.Tab.Inactive.Hover;
return;
}
TextColor = Skin.Colors.Tab.Inactive.Normal;
}
}
}

250
Gwen/Control/TabControl.cs Normal file
View file

@ -0,0 +1,250 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Control with multiple tabs that can be reordered and dragged.
/// </summary>
public class TabControl : Base
{
private readonly TabStrip m_TabStrip;
private readonly ScrollBarButton[] m_Scroll;
private TabButton m_CurrentButton;
private int m_ScrollOffset;
/// <summary>
/// Invoked when a tab has been added.
/// </summary>
public event GwenEventHandler TabAdded;
/// <summary>
/// Invoked when a tab has been removed.
/// </summary>
public event GwenEventHandler TabRemoved;
/// <summary>
/// Determines if tabs can be reordered by dragging.
/// </summary>
public bool AllowReorder { get { return m_TabStrip.AllowReorder; } set { m_TabStrip.AllowReorder = value; } }
/// <summary>
/// Currently active tab button.
/// </summary>
public TabButton CurrentButton { get { return m_CurrentButton; } }
/// <summary>
/// Current tab strip position.
/// </summary>
public Pos TabStripPosition { get { return m_TabStrip.StripPosition; }set { m_TabStrip.StripPosition = value; } }
/// <summary>
/// Tab strip.
/// </summary>
public TabStrip TabStrip { get { return m_TabStrip; } }
/// <summary>
/// Initializes a new instance of the <see cref="TabControl"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TabControl(Base parent)
: base(parent)
{
m_Scroll = new ScrollBarButton[2];
m_ScrollOffset = 0;
m_TabStrip = new TabStrip(this);
m_TabStrip.StripPosition = Pos.Top;
// Make this some special control?
m_Scroll[0] = new ScrollBarButton(this);
m_Scroll[0].SetDirectionLeft();
m_Scroll[0].Clicked += ScrollPressedLeft;
m_Scroll[0].SetSize(14, 16);
m_Scroll[1] = new ScrollBarButton(this);
m_Scroll[1].SetDirectionRight();
m_Scroll[1].Clicked += ScrollPressedRight;
m_Scroll[1].SetSize(14, 16);
m_InnerPanel = new TabControlInner(this);
m_InnerPanel.Dock = Pos.Fill;
m_InnerPanel.SendToBack();
IsTabable = false;
}
/// <summary>
/// Adds a new page/tab.
/// </summary>
/// <param name="label">Tab label.</param>
/// <param name="page">Page contents.</param>
/// <returns>Newly created control.</returns>
public TabButton AddPage(String label, Base page = null)
{
if (null == page)
{
page = new Base(this);
}
else
{
page.Parent = this;
}
TabButton button = new TabButton(m_TabStrip);
button.SetText(label);
button.Page = page;
button.IsTabable = false;
AddPage(button);
return button;
}
/// <summary>
/// Adds a page/tab.
/// </summary>
/// <param name="button">Page to add. (well, it's a TabButton which is a parent to the page).</param>
public void AddPage(TabButton button)
{
Base page = button.Page;
page.Parent = this;
page.IsHidden = true;
page.Margin = new Margin(6, 6, 6, 6);
page.Dock = Pos.Fill;
button.Parent = m_TabStrip;
button.Dock = Pos.Left;
button.SizeToContents();
if (button.TabControl != null)
button.TabControl.UnsubscribeTabEvent(button);
button.TabControl = this;
button.Clicked += OnTabPressed;
if (null == m_CurrentButton)
{
button.Press();
}
if (TabAdded != null)
TabAdded.Invoke(this);
Invalidate();
}
private void UnsubscribeTabEvent(TabButton button)
{
button.Clicked -= OnTabPressed;
}
/// <summary>
/// Handler for tab selection.
/// </summary>
/// <param name="control">Event source (TabButton).</param>
internal virtual void OnTabPressed(Base control)
{
TabButton button = control as TabButton;
if (null == button) return;
Base page = button.Page;
if (null == page) return;
if (m_CurrentButton == button)
return;
if (null != m_CurrentButton)
{
Base page2 = m_CurrentButton.Page;
if (page2 != null)
{
page2.IsHidden = true;
}
m_CurrentButton.Redraw();
m_CurrentButton = null;
}
m_CurrentButton = button;
page.IsHidden = false;
m_TabStrip.Invalidate();
Invalidate();
}
/// <summary>
/// Function invoked after layout.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void PostLayout(Skin.Base skin)
{
base.PostLayout(skin);
HandleOverflow();
}
/// <summary>
/// Handler for tab removing.
/// </summary>
/// <param name="button"></param>
internal virtual void OnLoseTab(TabButton button)
{
if (m_CurrentButton == button)
m_CurrentButton = null;
//TODO: Select a tab if any exist.
if (TabRemoved != null)
TabRemoved.Invoke(this);
Invalidate();
}
/// <summary>
/// Number of tabs in the control.
/// </summary>
public int TabCount { get { return m_TabStrip.Children.Count; } }
private void HandleOverflow()
{
Point TabsSize = m_TabStrip.GetChildrenSize();
// Only enable the scrollers if the tabs are at the top.
// This is a limitation we should explore.
// Really TabControl should have derivitives for tabs placed elsewhere where we could specialize
// some functions like this for each direction.
bool needed = TabsSize.X > Width && m_TabStrip.Dock == Pos.Top;
m_Scroll[0].IsHidden = !needed;
m_Scroll[1].IsHidden = !needed;
if (!needed) return;
m_ScrollOffset = Util.Clamp(m_ScrollOffset, 0, TabsSize.X - Width + 32);
#if false
//
// This isn't frame rate independent.
// Could be better. Get rid of m_ScrollOffset and just use m_TabStrip.GetMargin().left ?
// Then get a margin animation type and do it properly!
// TODO!
//
m_TabStrip.SetMargin( Margin( Gwen::Approach( m_TabStrip.GetMargin().left, m_iScrollOffset * -1, 2 ), 0, 0, 0 ) );
InvalidateParent();
#else
m_TabStrip.Margin = new Margin(m_ScrollOffset*-1, 0, 0, 0);
#endif
m_Scroll[0].SetPosition(Width - 30, 5);
m_Scroll[1].SetPosition(m_Scroll[0].Right, 5);
}
protected virtual void ScrollPressedLeft(Base control)
{
m_ScrollOffset -= 120;
}
protected virtual void ScrollPressedRight(Base control)
{
m_ScrollOffset += 120;
}
}
}

204
Gwen/Control/TabStrip.cs Normal file
View file

@ -0,0 +1,204 @@
using System;
using System.Drawing;
using Gwen.ControlInternal;
using Gwen.DragDrop;
namespace Gwen.Control
{
/// <summary>
/// Tab strip - groups TabButtons and allows reordering.
/// </summary>
public class TabStrip : Base
{
private Base m_TabDragControl;
private bool m_AllowReorder;
/// <summary>
/// Determines whether it is possible to reorder tabs by mouse dragging.
/// </summary>
public bool AllowReorder { get { return m_AllowReorder; } set { m_AllowReorder = value; } }
/// <summary>
/// Determines whether the control should be clipped to its bounds while rendering.
/// </summary>
protected override bool ShouldClip
{
get { return false; }
}
/// <summary>
/// Initializes a new instance of the <see cref="TabStrip"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TabStrip(Base parent)
: base(parent)
{
m_AllowReorder = false;
}
/// <summary>
/// Strip position (top/left/right/bottom).
/// </summary>
public Pos StripPosition
{
get { return Dock; }
set
{
Dock = value;
if (Dock == Pos.Top)
Padding = new Padding(5, 0, 0, 0);
if (Dock == Pos.Left)
Padding = new Padding(0, 5, 0, 0);
if (Dock == Pos.Bottom)
Padding = new Padding(5, 0, 0, 0);
if (Dock == Pos.Right)
Padding = new Padding(0, 5, 0, 0);
}
}
public override bool DragAndDrop_HandleDrop(Package p, int x, int y)
{
Point LocalPos = CanvasPosToLocal(new Point(x, y));
TabButton button = DragAndDrop.SourceControl as TabButton;
TabControl tabControl = Parent as TabControl;
if (tabControl != null && button != null)
{
if (button.TabControl != tabControl)
{
// We've moved tab controls!
tabControl.AddPage(button);
}
}
Base droppedOn = GetControlAt(LocalPos.X, LocalPos.Y);
if (droppedOn != null)
{
Point dropPos = droppedOn.CanvasPosToLocal(new Point(x, y));
DragAndDrop.SourceControl.BringNextToControl(droppedOn, dropPos.X > droppedOn.Width/2);
}
else
{
DragAndDrop.SourceControl.BringToFront();
}
return true;
}
public override bool DragAndDrop_CanAcceptPackage(Package p)
{
if (!m_AllowReorder)
return false;
if (p.Name == "TabButtonMove")
return true;
return false;
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
Point largestTab = new Point(5, 5);
int num = 0;
foreach (var child in Children)
{
TabButton button = child as TabButton;
if (null == button) continue;
button.SizeToContents();
Margin m = new Margin();
int notFirst = num > 0 ? -1 : 0;
if (Dock == Pos.Top)
{
m.Left = notFirst;
button.Dock = Pos.Left;
}
if (Dock == Pos.Left)
{
m.Top = notFirst;
button.Dock = Pos.Top;
}
if (Dock == Pos.Right)
{
m.Top = notFirst;
button.Dock = Pos.Top;
}
if (Dock == Pos.Bottom)
{
m.Left = notFirst;
button.Dock = Pos.Left;
}
largestTab.X = Math.Max(largestTab.X, button.Width);
largestTab.Y = Math.Max(largestTab.Y, button.Height);
button.Margin = m;
num++;
}
if (Dock == Pos.Top || Dock == Pos.Bottom)
SetSize(Width, largestTab.Y);
if (Dock == Pos.Left || Dock == Pos.Right)
SetSize(largestTab.X, Height);
base.Layout(skin);
}
public override void DragAndDrop_HoverEnter(Package p, int x, int y)
{
if (m_TabDragControl != null)
{
throw new InvalidOperationException("ERROR! TabStrip::DragAndDrop_HoverEnter");
}
m_TabDragControl = new Highlight(this);
m_TabDragControl.MouseInputEnabled = false;
m_TabDragControl.SetSize(3, Height);
}
public override void DragAndDrop_HoverLeave(Package p)
{
if (m_TabDragControl != null)
{
RemoveChild(m_TabDragControl, false); // [omeg] need to do that explicitely
m_TabDragControl.Dispose();
}
m_TabDragControl = null;
}
public override void DragAndDrop_Hover(Package p, int x, int y)
{
Point localPos = CanvasPosToLocal(new Point(x, y));
Base droppedOn = GetControlAt(localPos.X, localPos.Y);
if (droppedOn != null && droppedOn != this)
{
Point dropPos = droppedOn.CanvasPosToLocal(new Point(x, y));
m_TabDragControl.SetBounds(new Rectangle(0, 0, 3, Height));
m_TabDragControl.BringToFront();
m_TabDragControl.SetPosition(droppedOn.X - 1, 0);
if (dropPos.X > droppedOn.Width/2)
{
m_TabDragControl.MoveBy(droppedOn.Width - 1, 0);
}
m_TabDragControl.Dock = Pos.None;
}
else
{
m_TabDragControl.Dock = Pos.Left;
m_TabDragControl.BringToFront();
}
}
}
}

View file

@ -0,0 +1,41 @@
using System;
using Gwen.DragDrop;
namespace Gwen.Control
{
/// <summary>
/// Titlebar for DockedTabControl.
/// </summary>
public class TabTitleBar : Label
{
public TabTitleBar(Base parent) : base(parent)
{
MouseInputEnabled = true;
TextPadding = new Padding(5, 2, 5, 2);
Padding = new Padding(1, 2, 1, 2);
DragAndDrop_SetPackage(true, "TabWindowMove");
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawTabTitleBar(this);
}
public override void DragAndDrop_StartDragging(Package package, int x, int y)
{
DragAndDrop.SourceControl = Parent;
DragAndDrop.SourceControl.DragAndDrop_StartDragging(package, x, y);
}
public void UpdateFromTab(TabButton button)
{
Text = button.Text;
SizeToContents();
}
}
}

607
Gwen/Control/TextBox.cs Normal file
View file

@ -0,0 +1,607 @@
using System;
using System.Drawing;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Text box (editable).
/// </summary>
public class TextBox : Label
{
private bool m_SelectAll;
private int m_CursorPos;
private int m_CursorEnd;
private Rectangle m_SelectionBounds;
private Rectangle m_CaretBounds;
private float m_LastInputTime;
protected override bool AccelOnlyFocus { get { return true; } }
protected override bool NeedsInputChars { get { return true; } }
/// <summary>
/// Determines whether text should be selected when the control is focused.
/// </summary>
public bool SelectAllOnFocus { get { return m_SelectAll; } set { m_SelectAll = value; if (value) OnSelectAll(this); } }
/// <summary>
/// Indicates whether the text has active selection.
/// </summary>
public bool HasSelection { get { return m_CursorPos != m_CursorEnd; } }
/// <summary>
/// Invoked when the text has changed.
/// </summary>
public event GwenEventHandler TextChanged;
/// <summary>
/// Invoked when the submit key has been pressed.
/// </summary>
public event GwenEventHandler SubmitPressed;
/// <summary>
/// Current cursor position (character index).
/// </summary>
public int CursorPos
{
get { return m_CursorPos; }
set
{
if (m_CursorPos == value) return;
m_CursorPos = value;
RefreshCursorBounds();
}
}
public int CursorEnd
{
get { return m_CursorEnd; }
set
{
if (m_CursorEnd == value) return;
m_CursorEnd = value;
RefreshCursorBounds();
}
}
/// <summary>
/// Determines whether the control can insert text at a given cursor position.
/// </summary>
/// <param name="text">Text to check.</param>
/// <param name="position">Cursor position.</param>
/// <returns>True if allowed.</returns>
protected virtual bool IsTextAllowed(String text, int position)
{
return true;
}
/// <summary>
/// Initializes a new instance of the <see cref="TextBox"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TextBox(Base parent)
: base(parent)
{
SetSize(200, 20);
MouseInputEnabled = true;
KeyboardInputEnabled = true;
Alignment = Pos.Left | Pos.CenterV;
TextPadding = new Padding(4, 2, 4, 2);
m_CursorPos = 0;
m_CursorEnd = 0;
m_SelectAll = false;
TextColor = Color.FromArgb(255, 50, 50, 50); // TODO: From Skin
IsTabable = true;
AddAccelerator("Ctrl + C", OnCopy);
AddAccelerator("Ctrl + X", OnCut);
AddAccelerator("Ctrl + V", OnPaste);
AddAccelerator("Ctrl + A", OnSelectAll);
}
/// <summary>
/// Renders the focus overlay.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderFocus(Skin.Base skin)
{
// nothing
}
/// <summary>
/// Handler for text changed event.
/// </summary>
protected override void OnTextChanged()
{
base.OnTextChanged();
if (m_CursorPos > TextLength) m_CursorPos = TextLength;
if (m_CursorEnd > TextLength) m_CursorEnd = TextLength;
if (TextChanged != null)
TextChanged.Invoke(this);
}
/// <summary>
/// Handler for character input event.
/// </summary>
/// <param name="chr">Character typed.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnChar(char chr)
{
base.OnChar(chr);
if (chr == '\t') return false;
InsertText(chr.ToString());
return true;
}
/// <summary>
/// Inserts text at current cursor position, erasing selection if any.
/// </summary>
/// <param name="text">Text to insert.</param>
void InsertText(String text)
{
// TODO: Make sure fits (implement maxlength)
if (HasSelection)
{
EraseSelection();
}
if (m_CursorPos > TextLength)
m_CursorPos = TextLength;
if (!IsTextAllowed(text, m_CursorPos))
return;
String str = Text;
str = str.Insert(m_CursorPos, text);
SetText(str);
m_CursorPos += text.Length;
m_CursorEnd = m_CursorPos;
RefreshCursorBounds();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
base.Render(skin);
if (ShouldDrawBackground)
skin.DrawTextBox(this);
if (!HasFocus) return;
// Draw selection.. if selected..
if (m_CursorPos != m_CursorEnd)
{
skin.Renderer.DrawColor = Color.FromArgb(200, 50, 170, 255);
skin.Renderer.DrawFilledRect(m_SelectionBounds);
}
// Draw caret
float time = Platform.Neutral.GetTimeInSeconds() - m_LastInputTime;
if ((time % 1.0f) <= 0.5f)
{
skin.Renderer.DrawColor = Color.Black;
skin.Renderer.DrawFilledRect(m_CaretBounds);
}
}
protected virtual void RefreshCursorBounds()
{
m_LastInputTime = Platform.Neutral.GetTimeInSeconds();
MakeCaretVisible();
Point pA = GetCharacterPosition(m_CursorPos);
Point pB = GetCharacterPosition(m_CursorEnd);
m_SelectionBounds.X = Math.Min(pA.X, pB.X);
m_SelectionBounds.Y = TextY - 1;
m_SelectionBounds.Width = Math.Max(pA.X, pB.X) - m_SelectionBounds.X;
m_SelectionBounds.Height = TextHeight + 2;
m_CaretBounds.X = pA.X;
m_CaretBounds.Y = TextY - 1;
m_CaretBounds.Width = 1;
m_CaretBounds.Height = TextHeight + 2;
Redraw();
}
/// <summary>
/// Handler for Paste event.
/// </summary>
/// <param name="from">Source control.</param>
protected override void OnPaste(Base from)
{
base.OnPaste(from);
InsertText(Platform.Neutral.GetClipboardText());
}
/// <summary>
/// Handler for Copy event.
/// </summary>
/// <param name="from">Source control.</param>
protected override void OnCopy(Base from)
{
if (!HasSelection) return;
base.OnCopy(from);
Platform.Neutral.SetClipboardText(GetSelection());
}
/// <summary>
/// Handler for Cut event.
/// </summary>
/// <param name="from">Source control.</param>
protected override void OnCut(Base from)
{
if (!HasSelection) return;
base.OnCut(from);
Platform.Neutral.SetClipboardText(GetSelection());
EraseSelection();
}
/// <summary>
/// Handler for Select All event.
/// </summary>
/// <param name="from">Source control.</param>
protected override void OnSelectAll(Base from)
{
//base.OnSelectAll(from);
m_CursorEnd = 0;
m_CursorPos = TextLength;
RefreshCursorBounds();
}
/// <summary>
/// Handler invoked on mouse double click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
protected override void OnMouseDoubleClickedLeft(int x, int y)
{
//base.OnMouseDoubleClickedLeft(x, y);
OnSelectAll(this);
}
/// <summary>
/// Handler for Return keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyReturn(bool down)
{
base.OnKeyReturn(down);
if (down) return true;
OnReturn();
// Try to move to the next control, as if tab had been pressed
OnKeyTab(true);
// If we still have focus, blur it.
if (HasFocus)
{
Blur();
}
return true;
}
/// <summary>
/// Handler for Backspace keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyBackspace(bool down)
{
base.OnKeyBackspace(down);
if (!down) return true;
if (HasSelection)
{
EraseSelection();
return true;
}
if (m_CursorPos == 0) return true;
DeleteText(m_CursorPos - 1, 1);
return true;
}
/// <summary>
/// Handler for Delete keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyDelete(bool down)
{
base.OnKeyDelete(down);
if (!down) return true;
if (HasSelection)
{
EraseSelection();
return true;
}
if (m_CursorPos >= TextLength) return true;
DeleteText(m_CursorPos, 1);
return true;
}
/// <summary>
/// Handler for Left Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyLeft(bool down)
{
base.OnKeyLeft(down);
if (!down) return true;
if (m_CursorPos > 0)
m_CursorPos--;
if (!Input.InputHandler.IsShiftDown)
{
m_CursorEnd = m_CursorPos;
}
RefreshCursorBounds();
return true;
}
/// <summary>
/// Handler for Right Arrow keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyRight(bool down)
{
base.OnKeyRight(down);
if (!down) return true;
if (m_CursorPos < TextLength)
m_CursorPos++;
if (!Input.InputHandler.IsShiftDown)
{
m_CursorEnd = m_CursorPos;
}
RefreshCursorBounds();
return true;
}
/// <summary>
/// Handler for Home keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyHome(bool down)
{
base.OnKeyHome(down);
if (!down) return true;
m_CursorPos = 0;
if (!Input.InputHandler.IsShiftDown)
{
m_CursorEnd = m_CursorPos;
}
RefreshCursorBounds();
return true;
}
/// <summary>
/// Handler for End keyboard event.
/// </summary>
/// <param name="down">Indicates whether the key was pressed or released.</param>
/// <returns>
/// True if handled.
/// </returns>
protected override bool OnKeyEnd(bool down)
{
base.OnKeyEnd(down);
m_CursorPos = TextLength;
if (!Input.InputHandler.IsShiftDown)
{
m_CursorEnd = m_CursorPos;
}
RefreshCursorBounds();
return true;
}
/// <summary>
/// Returns currently selected text.
/// </summary>
/// <returns>Current selection.</returns>
public String GetSelection()
{
if (!HasSelection) return String.Empty;
int start = Math.Min(m_CursorPos, m_CursorEnd);
int end = Math.Max(m_CursorPos, m_CursorEnd);
String str = Text;
return str.Substring(start, end - start);
}
/// <summary>
/// Deletes text.
/// </summary>
/// <param name="startPos">Starting cursor position.</param>
/// <param name="length">Length in characters.</param>
public virtual void DeleteText(int startPos, int length)
{
String str = Text;
str = str.Remove(startPos, length);
SetText(str);
if (m_CursorPos > startPos)
{
CursorPos = m_CursorPos - length;
}
CursorEnd = m_CursorPos;
}
/// <summary>
/// Deletes selected text.
/// </summary>
public virtual void EraseSelection()
{
int start = Math.Min(m_CursorPos, m_CursorEnd);
int end = Math.Max(m_CursorPos, m_CursorEnd);
DeleteText(start, end - start);
// Move the cursor to the start of the selection,
// since the end is probably outside of the string now.
m_CursorPos = start;
m_CursorEnd = start;
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
base.OnMouseClickedLeft(x, y, down);
if (m_SelectAll)
{
OnSelectAll(this);
//m_SelectAll = false;
return;
}
int c = GetClosestCharacter(x, y);
if (down)
{
CursorPos = c;
if (!Input.InputHandler.IsShiftDown)
CursorEnd = c;
InputHandler.MouseFocus = this;
}
else
{
if (InputHandler.MouseFocus == this)
{
CursorPos = c;
InputHandler.MouseFocus = null;
}
}
}
/// <summary>
/// Handler invoked on mouse moved event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="dx">X change.</param>
/// <param name="dy">Y change.</param>
protected override void OnMouseMoved(int x, int y, int dx, int dy)
{
base.OnMouseMoved(x, y, dx, dy);
if (InputHandler.MouseFocus != this) return;
int c = GetClosestCharacter(x, y);
CursorPos = c;
}
protected virtual void MakeCaretVisible()
{
int caretPos = GetCharacterPosition(m_CursorPos).X - TextX;
// If the caret is already in a semi-good position, leave it.
{
int realCaretPos = caretPos + TextX;
if (realCaretPos > Width*0.1f && realCaretPos < Width*0.9f)
return;
}
// The ideal position is for the caret to be right in the middle
int idealx = (int)(-caretPos + Width * 0.5f);
// Don't show too much whitespace to the right
if (idealx + TextWidth < Width - TextPadding.Right)
idealx = -TextWidth + (Width - TextPadding.Right);
// Or the left
if (idealx > TextPadding.Left)
idealx = TextPadding.Left;
SetTextPosition(idealx, TextY);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
RefreshCursorBounds();
}
/// <summary>
/// Handler for the return key.
/// </summary>
protected virtual void OnReturn()
{
if (SubmitPressed != null)
SubmitPressed.Invoke(this);
}
}
}

View file

@ -0,0 +1,85 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Numeric text box - accepts only float numbers.
/// </summary>
public class TextBoxNumeric : TextBox
{
/// <summary>
/// Current numeric value.
/// </summary>
protected float m_Value;
/// <summary>
/// Initializes a new instance of the <see cref="TextBoxNumeric"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TextBoxNumeric(Base parent)
: base(parent)
{
SetText("0", false);
}
protected virtual bool IsTextAllowed(String str)
{
if (str == "" || str == "-")
return true; // annoying if single - is not allowed
float d;
return float.TryParse(str, out d);
}
/// <summary>
/// Determines whether the control can insert text at a given cursor position.
/// </summary>
/// <param name="text">Text to check.</param>
/// <param name="position">Cursor position.</param>
/// <returns>True if allowed.</returns>
protected override bool IsTextAllowed(String text, int position)
{
String newText = Text.Insert(position, text);
return IsTextAllowed(newText);
}
/// <summary>
/// Current numerical value.
/// </summary>
public virtual float Value
{
get { return m_Value; }
set
{
m_Value = value;
Text = value.ToString();
}
}
// text -> value
/// <summary>
/// Handler for text changed event.
/// </summary>
protected override void OnTextChanged()
{
if (String.IsNullOrEmpty(Text) || Text == "-")
{
m_Value = 0;
//SetText("0");
}
else
m_Value = float.Parse(Text);
base.OnTextChanged();
}
/// <summary>
/// Sets the control text.
/// </summary>
/// <param name="str">Text to set.</param>
/// <param name="doEvents">Determines whether to invoke "text changed" event.</param>
public override void SetText(string str, bool doEvents = true)
{
if (IsTextAllowed(str))
base.SetText(str, doEvents);
}
}
}

View file

@ -0,0 +1,41 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Text box with masked text.
/// </summary>
/// <remarks>
/// This class doesn't prevent programatic access to the text in any way.
/// </remarks>
public class TextBoxPassword : TextBox
{
private String m_Mask;
private char m_MaskCharacter;
/// <summary>
/// Character used in place of actual characters for display.
/// </summary>
public char MaskCharacter { get { return m_MaskCharacter; } set { m_MaskCharacter = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="TextBoxPassword"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TextBoxPassword(Base parent)
: base(parent)
{
m_MaskCharacter = '*';
}
/// <summary>
/// Handler for text changed event.
/// </summary>
protected override void OnTextChanged()
{
m_Mask = new string(MaskCharacter, Text.Length);
TextOverride = m_Mask;
base.OnTextChanged();
}
}
}

View file

@ -0,0 +1,95 @@
using System;
namespace Gwen.Control
{
/// <summary>
/// Tree control.
/// </summary>
public class TreeControl : TreeNode
{
private readonly ScrollControl m_ScrollControl;
private bool m_MultiSelect;
/// <summary>
/// Determines if multiple nodes can be selected at the same time.
/// </summary>
public bool AllowMultiSelect { get { return m_MultiSelect; } set { m_MultiSelect = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="TreeControl"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TreeControl(Base parent)
: base(parent)
{
m_TreeControl = this;
RemoveChild(m_ToggleButton, true);
m_ToggleButton = null;
RemoveChild(m_Title, true);
m_Title = null;
RemoveChild(m_InnerPanel, true);
m_InnerPanel = null;
m_MultiSelect = false;
m_ScrollControl = new ScrollControl(this);
m_ScrollControl.Dock = Pos.Fill;
m_ScrollControl.EnableScroll(false, true);
m_ScrollControl.AutoHideBars = true;
m_ScrollControl.Margin = Margin.One;
m_InnerPanel = m_ScrollControl;
m_ScrollControl.SetInnerSize(1000, 1000); // todo: why such arbitrary numbers?
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
if (ShouldDrawBackground)
skin.DrawTreeControl(this);
}
/// <summary>
/// Handler invoked when control children's bounds change.
/// </summary>
/// <param name="oldChildBounds"></param>
/// <param name="child"></param>
protected override void OnChildBoundsChanged(System.Drawing.Rectangle oldChildBounds, Base child)
{
if (m_ScrollControl != null)
m_ScrollControl.UpdateScrollBars();
}
/// <summary>
/// Removes all child nodes.
/// </summary>
public virtual void RemoveAll()
{
m_ScrollControl.DeleteAll();
}
/// <summary>
/// Handler for node added event.
/// </summary>
/// <param name="node">Node added.</param>
public virtual void OnNodeAdded(TreeNode node)
{
node.LabelPressed += OnNodeSelected;
}
/// <summary>
/// Handler for node selected event.
/// </summary>
/// <param name="Control">Node selected.</param>
protected virtual void OnNodeSelected(Base Control)
{
if (!m_MultiSelect /*|| InputHandler.InputHandler.IsKeyDown(Key.Control)*/)
UnselectAll();
}
}
}

326
Gwen/Control/TreeNode.cs Normal file
View file

@ -0,0 +1,326 @@
using System;
using System.Linq;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Tree control node.
/// </summary>
public class TreeNode : Base
{
public const int TreeIndentation = 14;
protected TreeControl m_TreeControl;
protected Button m_ToggleButton;
protected Button m_Title;
private bool m_Root;
private bool m_Selected;
private bool m_Selectable;
/// <summary>
/// Indicates whether this is a root node.
/// </summary>
public bool IsRoot { get { return m_Root; } set { m_Root = value; } }
/// <summary>
/// Parent tree control.
/// </summary>
public TreeControl TreeControl { get { return m_TreeControl; } set { m_TreeControl = value; } }
/// <summary>
/// Determines whether the node is selectable.
/// </summary>
public bool IsSelectable { get { return m_Selectable; } set { m_Selectable = value; } }
/// <summary>
/// Indicates whether the node is selected.
/// </summary>
public bool IsSelected
{
get { return m_Selected; }
set
{
if (!IsSelectable)
return;
if (IsSelected == value)
return;
m_Selected = value;
if (m_Title != null)
m_Title.ToggleState = value;
if (SelectionChanged != null)
SelectionChanged.Invoke(this);
// propagate to root parent (tree)
if (m_TreeControl != null && m_TreeControl.SelectionChanged != null)
m_TreeControl.SelectionChanged.Invoke(this);
if (value)
{
if (Selected != null)
Selected.Invoke(this);
if (m_TreeControl != null && m_TreeControl.Selected != null)
m_TreeControl.Selected.Invoke(this);
}
else
{
if (Unselected != null)
Unselected.Invoke(this);
if (m_TreeControl != null && m_TreeControl.Unselected != null)
m_TreeControl.Unselected.Invoke(this);
}
}
}
/// <summary>
/// Node's label.
/// </summary>
public String Text { get { return m_Title.Text; } set { m_Title.Text = value; } }
/// <summary>
/// Invoked when the node label has been pressed.
/// </summary>
public event GwenEventHandler LabelPressed;
/// <summary>
/// Invoked when the node's selected state has changed.
/// </summary>
public event GwenEventHandler SelectionChanged;
/// <summary>
/// Invoked when the node has been selected.
/// </summary>
public event GwenEventHandler Selected;
/// <summary>
/// Invoked when the node has been unselected.
/// </summary>
public event GwenEventHandler Unselected;
/// <summary>
/// Invoked when the node has been expanded.
/// </summary>
public event GwenEventHandler Expanded;
/// <summary>
/// Invoked when the node has been collapsed.
/// </summary>
public event GwenEventHandler Collapsed;
/// <summary>
/// Initializes a new instance of the <see cref="TreeNode"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TreeNode(Base parent)
: base(parent)
{
m_ToggleButton = new TreeToggleButton(this);
m_ToggleButton.SetBounds(0, 0, 15, 15);
m_ToggleButton.Toggled += OnToggleButtonPress;
m_Title = new TreeNodeLabel(this);
m_Title.Dock = Pos.Top;
m_Title.Margin = new Margin(16, 0, 0, 0);
m_Title.DoubleClickedLeft += OnDoubleClickName;
m_Title.Clicked += OnClickName;
m_InnerPanel = new Base(this);
m_InnerPanel.Dock = Pos.Top;
m_InnerPanel.Height = 100;
m_InnerPanel.Margin = new Margin(TreeIndentation, 1, 0, 0);
m_InnerPanel.Hide();
m_Root = false;
m_Selected = false;
m_Selectable = true;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
int bottom = 0;
if (m_InnerPanel.Children.Count > 0)
{
bottom = m_InnerPanel.Children.Last().Y + m_InnerPanel.Y;
}
skin.DrawTreeNode(this, m_InnerPanel.IsVisible, IsSelected, m_Title.Height, m_Title.TextRight,
(int)(m_ToggleButton.Y + m_ToggleButton.Height * 0.5f), bottom, m_TreeControl == Parent); // IsRoot
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
if (m_ToggleButton != null)
{
if (m_Title != null)
{
m_ToggleButton.SetPosition(0, (m_Title.Height - m_ToggleButton.Height)*0.5f);
}
if (m_InnerPanel.Children.Count == 0)
{
m_ToggleButton.Hide();
m_ToggleButton.ToggleState = false;
m_InnerPanel.Hide();
}
else
{
m_ToggleButton.Show();
m_InnerPanel.SizeToChildren(false, true);
}
}
base.Layout(skin);
}
/// <summary>
/// Function invoked after layout.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void PostLayout(Skin.Base skin)
{
if (SizeToChildren(false, true))
{
InvalidateParent();
}
}
/// <summary>
/// Adds a new child node.
/// </summary>
/// <param name="label">Node's label.</param>
/// <returns>Newly created control.</returns>
public TreeNode AddNode(string label)
{
TreeNode node = new TreeNode(this);
node.Text = label;
node.Dock = Pos.Top;
node.IsRoot = this is TreeControl;
node.TreeControl = m_TreeControl;
if (m_TreeControl != null)
{
m_TreeControl.OnNodeAdded(node);
}
return node;
}
/// <summary>
/// Opens the node.
/// </summary>
public void Open()
{
m_InnerPanel.Show();
if (m_ToggleButton != null)
m_ToggleButton.ToggleState = true;
if (Expanded != null)
Expanded.Invoke(this);
if (m_TreeControl != null && m_TreeControl.Expanded != null)
m_TreeControl.Expanded.Invoke(this);
Invalidate();
}
/// <summary>
/// Closes the node.
/// </summary>
public void Close()
{
m_InnerPanel.Hide();
if (m_ToggleButton != null)
m_ToggleButton.ToggleState = false;
if (Collapsed != null)
Collapsed.Invoke(this);
if (m_TreeControl != null && m_TreeControl.Collapsed != null)
m_TreeControl.Collapsed.Invoke(this);
Invalidate();
}
/// <summary>
/// Opens the node and all child nodes.
/// </summary>
public void ExpandAll()
{
Open();
foreach (Base child in Children)
{
TreeNode node = child as TreeNode;
if (node == null)
continue;
node.ExpandAll();
}
}
/// <summary>
/// Clears the selection on the node and all child nodes.
/// </summary>
public void UnselectAll()
{
IsSelected = false;
if (m_Title != null)
m_Title.ToggleState = false;
foreach (Base child in Children)
{
TreeNode node = child as TreeNode;
if (node == null)
continue;
node.UnselectAll();
}
}
/// <summary>
/// Handler for the toggle button.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnToggleButtonPress(Base control)
{
if (m_ToggleButton.ToggleState)
{
Open();
}
else
{
Close();
}
}
/// <summary>
/// Handler for label double click.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnDoubleClickName(Base control)
{
if (!m_ToggleButton.IsVisible)
return;
m_ToggleButton.Toggle();
}
/// <summary>
/// Handler for label click.
/// </summary>
/// <param name="control">Event source.</param>
protected virtual void OnClickName(Base control)
{
if (LabelPressed != null)
LabelPressed.Invoke(this);
IsSelected = !IsSelected;
}
}
}

View file

@ -0,0 +1,193 @@
using System;
using System.Drawing;
using Gwen.Input;
namespace Gwen.Control
{
/// <summary>
/// Vertical scrollbar.
/// </summary>
public class VerticalScrollBar : ScrollBar
{
/// <summary>
/// Bar size (in pixels).
/// </summary>
public override int BarSize
{
get { return m_Bar.Height; }
set { m_Bar.Height = value; }
}
/// <summary>
/// Bar position (in pixels).
/// </summary>
public override int BarPos
{
get { return m_Bar.Y - Width; }
}
/// <summary>
/// Button size (in pixels).
/// </summary>
public override int ButtonSize
{
get { return Width; }
}
/// <summary>
/// Initializes a new instance of the <see cref="VerticalScrollBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public VerticalScrollBar(Base parent)
: base(parent)
{
m_Bar.IsVertical = true;
m_ScrollButton[0].SetDirectionUp();
m_ScrollButton[0].Clicked += NudgeUp;
m_ScrollButton[1].SetDirectionDown();
m_ScrollButton[1].Clicked += NudgeDown;
m_Bar.Dragged += OnBarMoved;
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
base.Layout(skin);
m_ScrollButton[0].Height = Width;
m_ScrollButton[0].Dock = Pos.Top;
m_ScrollButton[1].Height = Width;
m_ScrollButton[1].Dock = Pos.Bottom;
m_Bar.Width = ButtonSize;
m_Bar.Padding = new Padding(0, ButtonSize, 0, ButtonSize);
float barHeight = 0.0f;
if (m_ContentSize > 0.0f) barHeight = (m_ViewableContentSize/m_ContentSize)*(Height - (ButtonSize*2));
if (barHeight < ButtonSize*0.5f)
barHeight = (int) (ButtonSize*0.5f);
m_Bar.Height = (int) (barHeight);
m_Bar.IsHidden = Height - (ButtonSize*2) <= barHeight;
//Based on our last scroll amount, produce a position for the bar
if (!m_Bar.IsHeld)
{
SetScrollAmount(ScrollAmount, true);
}
}
public virtual void NudgeUp(Base control)
{
if (!IsDisabled)
SetScrollAmount(ScrollAmount - NudgeAmount, true);
}
public virtual void NudgeDown(Base control)
{
if (!IsDisabled)
SetScrollAmount(ScrollAmount + NudgeAmount, true);
}
public override void ScrollToTop()
{
SetScrollAmount(0, true);
}
public override void ScrollToBottom()
{
SetScrollAmount(1, true);
}
public override float NudgeAmount
{
get
{
if (m_Depressed)
return m_ViewableContentSize / m_ContentSize;
else
return base.NudgeAmount;
}
set
{
base.NudgeAmount = value;
}
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
if (down)
{
m_Depressed = true;
InputHandler.MouseFocus = this;
}
else
{
Point clickPos = CanvasPosToLocal(new Point(x, y));
if (clickPos.Y < m_Bar.Y)
NudgeUp(this);
else if (clickPos.Y > m_Bar.Y + m_Bar.Height)
NudgeDown(this);
m_Depressed = false;
InputHandler.MouseFocus = null;
}
}
protected override float CalculateScrolledAmount()
{
return (float)(m_Bar.Y - ButtonSize) / (Height - m_Bar.Height - (ButtonSize * 2));
}
/// <summary>
/// Sets the scroll amount (0-1).
/// </summary>
/// <param name="value">Scroll amount.</param>
/// <param name="forceUpdate">Determines whether the control should be updated.</param>
/// <returns>True if control state changed.</returns>
public override bool SetScrollAmount(float value, bool forceUpdate = false)
{
value = Util.Clamp(value, 0, 1);
if (!base.SetScrollAmount(value, forceUpdate))
return false;
if (forceUpdate)
{
int newY = (int)(ButtonSize + (value * ((Height - m_Bar.Height) - (ButtonSize * 2))));
m_Bar.MoveTo(m_Bar.X, newY);
}
return true;
}
/// <summary>
/// Handler for the BarMoved event.
/// </summary>
/// <param name="control">The control.</param>
protected override void OnBarMoved(Base control)
{
if (m_Bar.IsHeld)
{
SetScrollAmount(CalculateScrolledAmount(), false);
base.OnBarMoved(control);
}
else
InvalidateParent();
}
}
}

View file

@ -0,0 +1,63 @@
using System;
using System.Drawing;
namespace Gwen.Control
{
/// <summary>
/// Vertical slider.
/// </summary>
public class VerticalSlider : Slider
{
/// <summary>
/// Initializes a new instance of the <see cref="VerticalSlider"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public VerticalSlider(Base parent)
: base(parent)
{
m_SliderBar.IsHorizontal = false;
}
protected override float CalculateValue()
{
return 1 - m_SliderBar.Y / (float)(Height - m_SliderBar.Height);
}
protected override void UpdateBarFromValue()
{
m_SliderBar.MoveTo(m_SliderBar.X, (int)((Height - m_SliderBar.Height) * (1 - m_Value)));
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
m_SliderBar.MoveTo(m_SliderBar.X, (int) (CanvasPosToLocal(new Point(x, y)).Y - m_SliderBar.Height*0.5));
m_SliderBar.InputMouseClickedLeft(x, y, down);
OnMoved(m_SliderBar);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
m_SliderBar.SetSize(Width, 15);
UpdateBarFromValue();
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawSlider(this, false, m_SnapToNotches ? m_NotchCount : 0, m_SliderBar.Height);
}
}
}

View file

@ -0,0 +1,221 @@
using System;
//using System.Windows.Forms;
using Gwen.ControlInternal;
namespace Gwen.Control
{
public class VerticalSplitter : Base
{
private readonly SplitterBar m_HSplitter;
private readonly Base[] m_Sections;
private float m_HVal; // 0-1
private int m_BarSize; // pixels
private int m_ZoomedSection; // 0-3
/// <summary>
/// Invoked when one of the panels has been zoomed (maximized).
/// </summary>
public event GwenEventHandler PanelZoomed;
/// <summary>
/// Invoked when one of the panels has been unzoomed (restored).
/// </summary>
public event GwenEventHandler PanelUnZoomed;
/// <summary>
/// Invoked when the zoomed panel has been changed.
/// </summary>
public event GwenEventHandler ZoomChanged;
/// <summary>
/// Initializes a new instance of the <see cref="CrossSplitter"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public VerticalSplitter(Base parent)
: base(parent)
{
m_Sections = new Base[2];
m_HSplitter = new SplitterBar(this);
m_HSplitter.SetPosition(128, 0);
m_HSplitter.Dragged += OnHorizontalMoved;
//m_HSplitter.Cursor = Cursors.SizeWE;
m_HVal = 0.5f;
SetPanel(0, null);
SetPanel(1, null);
SplitterSize = 5;
SplittersVisible = false;
m_ZoomedSection = -1;
}
/// <summary>
/// Centers the panels so that they take even amount of space.
/// </summary>
public void CenterPanels()
{
m_HVal = 0.5f;
Invalidate();
}
public void SetHValue(float value)
{
if (value <= 1f || value >= 0)
m_HVal = value;
}
/// <summary>
/// Indicates whether any of the panels is zoomed.
/// </summary>
public bool IsZoomed { get { return m_ZoomedSection != -1; } }
/// <summary>
/// Gets or sets a value indicating whether splitters should be visible.
/// </summary>
public bool SplittersVisible
{
get { return m_HSplitter.ShouldDrawBackground; }
set
{
m_HSplitter.ShouldDrawBackground = value;
}
}
/// <summary>
/// Gets or sets the size of the splitter.
/// </summary>
public int SplitterSize { get { return m_BarSize; } set { m_BarSize = value; } }
private void UpdateHSplitter()
{
m_HSplitter.MoveTo((Width - m_HSplitter.Width) * (m_HVal), m_HSplitter.Y);
}
protected void OnHorizontalMoved(Base control)
{
m_HVal = CalculateValueHorizontal();
Invalidate();
}
private float CalculateValueHorizontal()
{
return m_HSplitter.X / (float)(Width - m_HSplitter.Width);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
m_HSplitter.SetSize(m_BarSize, Height);
UpdateHSplitter();
if (m_ZoomedSection == -1)
{
if (m_Sections[0] != null)
m_Sections[0].SetBounds(0, 0, m_HSplitter.X, Height);
if (m_Sections[1] != null)
m_Sections[1].SetBounds(m_HSplitter.X + m_BarSize, 0, Width - (m_HSplitter.X + m_BarSize), Height);
}
else
{
//This should probably use Fill docking instead
m_Sections[m_ZoomedSection].SetBounds(0, 0, Width, Height);
}
}
/// <summary>
/// Assigns a control to the specific inner section.
/// </summary>
/// <param name="index">Section index (0-3).</param>
/// <param name="panel">Control to assign.</param>
public void SetPanel(int index, Base panel)
{
m_Sections[index] = panel;
if (panel != null)
{
panel.Dock = Pos.None;
panel.Parent = this;
}
Invalidate();
}
/// <summary>
/// Gets the specific inner section.
/// </summary>
/// <param name="index">Section index (0-3).</param>
/// <returns>Specified section.</returns>
public Base GetPanel(int index)
{
return m_Sections[index];
}
/// <summary>
/// Internal handler for the zoom changed event.
/// </summary>
protected void OnZoomChanged()
{
if (ZoomChanged != null)
ZoomChanged.Invoke(this);
if (m_ZoomedSection == -1)
{
if (PanelUnZoomed != null)
PanelUnZoomed.Invoke(this);
}
else
{
if (PanelZoomed != null)
PanelZoomed.Invoke(this);
}
}
/// <summary>
/// Maximizes the specified panel so it fills the entire control.
/// </summary>
/// <param name="section">Panel index (0-3).</param>
public void Zoom(int section)
{
UnZoom();
if (m_Sections[section] != null)
{
for (int i = 0; i < 2; i++)
{
if (i != section && m_Sections[i] != null)
m_Sections[i].IsHidden = true;
}
m_ZoomedSection = section;
Invalidate();
}
OnZoomChanged();
}
/// <summary>
/// Restores the control so all panels are visible.
/// </summary>
public void UnZoom()
{
m_ZoomedSection = -1;
for (int i = 0; i < 2; i++)
{
if (m_Sections[i] != null)
m_Sections[i].IsHidden = false;
}
Invalidate();
OnZoomChanged();
}
}
}

View file

@ -0,0 +1,177 @@
using System;
using System.Drawing;
using System.Linq;
using Gwen.ControlInternal;
namespace Gwen.Control
{
/// <summary>
/// Movable window with title bar.
/// </summary>
public class WindowControl : ResizableControl
{
private readonly Dragger m_TitleBar;
private readonly Label m_Caption;
private readonly CloseButton m_CloseButton;
private bool m_DeleteOnClose;
private Modal m_Modal;
/// <summary>
/// Window caption.
/// </summary>
public String Caption { get { return m_Caption.Text; } set { m_Caption.Text = value; } }
/// <summary>
/// Determines whether the window has close button.
/// </summary>
public bool IsClosable { get { return !m_CloseButton.IsHidden; } set { m_CloseButton.IsHidden = !value; } }
/// <summary>
/// Determines whether the control should be disposed on close.
/// </summary>
public bool DeleteOnClose { get { return m_DeleteOnClose; } set { m_DeleteOnClose = value; } }
/// <summary>
/// Indicates whether the control is hidden.
/// </summary>
public override bool IsHidden
{
get { return base.IsHidden; }
set
{
if (!value)
BringToFront();
base.IsHidden = value;
}
}
/// <summary>
/// Initializes a new instance of the <see cref="WindowControl"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
/// <param name="caption">Window caption.</param>
/// <param name="modal">Determines whether the window should be modal.</param>
public WindowControl(Base parent, String caption = "", bool modal = false)
: base(parent)
{
m_TitleBar = new Dragger(this);
m_TitleBar.Height = 24;
m_TitleBar.Padding = Gwen.Padding.Zero;
m_TitleBar.Margin = new Margin(0, 0, 0, 4);
m_TitleBar.Target = this;
m_TitleBar.Dock = Pos.Top;
m_Caption = new Label(m_TitleBar);
m_Caption.Alignment = Pos.Left | Pos.CenterV;
m_Caption.Text = caption;
m_Caption.Dock = Pos.Fill;
m_Caption.Padding = new Padding(8, 0, 0, 0);
m_Caption.TextColor = Skin.Colors.Window.TitleInactive;
m_CloseButton = new CloseButton(m_TitleBar, this);
//m_CloseButton.Text = String.Empty;
m_CloseButton.SetSize(24, 24);
m_CloseButton.Dock = Pos.Right;
m_CloseButton.Clicked += CloseButtonPressed;
m_CloseButton.IsTabable = false;
m_CloseButton.Name = "closeButton";
//Create a blank content control, dock it to the top - Should this be a ScrollControl?
m_InnerPanel = new Base(this);
m_InnerPanel.Dock = Pos.Fill;
GetResizer(8).Hide();
BringToFront();
IsTabable = false;
Focus();
MinimumSize = new Point(100, 40);
ClampMovement = true;
KeyboardInputEnabled = false;
if (modal)
MakeModal();
}
protected virtual void CloseButtonPressed(Base control)
{
IsHidden = true;
if (m_Modal != null)
{
m_Modal.DelayedDelete();
m_Modal = null;
}
if (m_DeleteOnClose)
{
Parent.RemoveChild(this, true);
}
}
/// <summary>
/// Makes the window modal: covers the whole canvas and gets all input.
/// </summary>
/// <param name="dim">Determines whether all the background should be dimmed.</param>
public void MakeModal(bool dim = false)
{
if (m_Modal != null)
return;
m_Modal = new Modal(GetCanvas());
Parent = m_Modal;
if (dim)
m_Modal.ShouldDrawBackground = true;
else
m_Modal.ShouldDrawBackground = false;
}
/// <summary>
/// Indicates whether the control is on top of its parent's children.
/// </summary>
public override bool IsOnTop
{
get { return Parent.Children.Where(x => x is WindowControl).Last() == this; }
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
bool hasFocus = IsOnTop;
if (hasFocus)
m_Caption.TextColor = Skin.Colors.Window.TitleActive;
else
m_Caption.TextColor = Skin.Colors.Window.TitleInactive;
skin.DrawWindow(this, m_TitleBar.Bottom, hasFocus);
}
/// <summary>
/// Renders under the actual control (shadows etc).
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderUnder(Skin.Base skin)
{
base.RenderUnder(skin);
skin.DrawShadow(this);
}
public override void Touch()
{
base.Touch();
BringToFront();
}
/// <summary>
/// Renders the focus overlay.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderFocus(Skin.Base skin)
{
}
}
}

View file

@ -0,0 +1,87 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Item in CollapsibleCategory.
/// </summary>
public class CategoryButton : Button
{
internal bool m_Alt; // for alternate coloring
/// <summary>
/// Initializes a new instance of the <see cref="CategoryButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public CategoryButton(Base parent) : base(parent)
{
Alignment = Pos.Left | Pos.CenterV;
m_Alt = false;
IsToggle = true;
TextPadding = new Padding(3, 0, 3, 0);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
if (m_Alt)
{
if (IsDepressed || ToggleState)
Skin.Renderer.DrawColor = skin.Colors.Category.LineAlt.Button_Selected;
else if (IsHovered)
Skin.Renderer.DrawColor = skin.Colors.Category.LineAlt.Button_Hover;
else
Skin.Renderer.DrawColor = skin.Colors.Category.LineAlt.Button;
}
else
{
if (IsDepressed || ToggleState)
Skin.Renderer.DrawColor = skin.Colors.Category.Line.Button_Selected;
else if (IsHovered)
Skin.Renderer.DrawColor = skin.Colors.Category.Line.Button_Hover;
else
Skin.Renderer.DrawColor = skin.Colors.Category.Line.Button;
}
skin.Renderer.DrawFilledRect(RenderBounds);
}
/// <summary>
/// Updates control colors.
/// </summary>
public override void UpdateColors()
{
if (m_Alt)
{
if (IsDepressed || ToggleState)
{
TextColor = Skin.Colors.Category.LineAlt.Text_Selected;
return;
}
if (IsHovered)
{
TextColor = Skin.Colors.Category.LineAlt.Text_Hover;
return;
}
TextColor = Skin.Colors.Category.LineAlt.Text;
return;
}
if (IsDepressed || ToggleState)
{
TextColor = Skin.Colors.Category.Line.Text_Selected;
return;
}
if (IsHovered)
{
TextColor = Skin.Colors.Category.Line.Text_Hover;
return;
}
TextColor = Skin.Colors.Category.Line.Text;
}
}
}

View file

@ -0,0 +1,35 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Header of CollapsibleCategory.
/// </summary>
public class CategoryHeaderButton : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="CategoryHeaderButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public CategoryHeaderButton(Base parent)
: base(parent)
{
ShouldDrawBackground = false;
IsToggle = true;
Alignment = Pos.Center;
TextPadding = new Padding(3, 0, 3, 0);
}
/// <summary>
/// Updates control colors.
/// </summary>
public override void UpdateColors()
{
if (IsDepressed || ToggleState)
TextColor = Skin.Colors.Category.Header_Closed;
else
TextColor = Skin.Colors.Category.Header;
}
}
}

View file

@ -0,0 +1,33 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Window close button.
/// </summary>
public class CloseButton : Button
{
private readonly WindowControl m_Window;
/// <summary>
/// Initializes a new instance of the <see cref="CloseButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
/// <param name="owner">Window that owns this button.</param>
public CloseButton(Base parent, WindowControl owner)
: base(parent)
{
m_Window = owner;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawWindowCloseButton(this, IsDepressed && IsHovered, IsHovered && ShouldDrawHover, !m_Window.IsOnTop);
}
}
}

View file

@ -0,0 +1,39 @@
using System;
using System.Drawing;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Property button.
/// </summary>
public class ColorButton : Button
{
private Color m_Color;
/// <summary>
/// Current color value.
/// </summary>
public Color Color { get { return m_Color; } set { m_Color = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="ColorButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ColorButton(Base parent) : base(parent)
{
m_Color = Color.Black;
Text = String.Empty;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.Renderer.DrawColor = m_Color;
skin.Renderer.DrawFilledRect(RenderBounds);
}
}
}

View file

@ -0,0 +1,45 @@
using System;
using System.Drawing;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Color square.
/// </summary>
public class ColorDisplay : Base
{
private Color m_Color;
//private bool m_DrawCheckers;
/// <summary>
/// Initializes a new instance of the <see cref="ColorDisplay"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ColorDisplay(Base parent) : base(parent)
{
SetSize(32, 32);
m_Color = Color.FromArgb(255, 255, 0, 0);
//m_DrawCheckers = true;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawColorDisplay(this, m_Color);
}
/// <summary>
/// Current color.
/// </summary>
public Color Color { get { return m_Color; } set { m_Color = value; } }
//public bool DrawCheckers { get { return m_DrawCheckers; } set { m_DrawCheckers = value; } }
public int R { get { return m_Color.R; } set { m_Color = Color.FromArgb(m_Color.A, value, m_Color.G, m_Color.B); } }
public int G { get { return m_Color.G; } set { m_Color = Color.FromArgb(m_Color.A, m_Color.R, value, m_Color.B); } }
public int B { get { return m_Color.B; } set { m_Color = Color.FromArgb(m_Color.A, m_Color.R, m_Color.G, value); } }
public int A { get { return m_Color.A; } set { m_Color = Color.FromArgb(value, m_Color.R, m_Color.G, m_Color.B); } }
}
}

View file

@ -0,0 +1,35 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// ComboBox arrow.
/// </summary>
public class DownArrow : Base
{
private readonly ComboBox m_ComboBox;
/// <summary>
/// Initializes a new instance of the <see cref="DownArrow"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public DownArrow(ComboBox parent)
: base(parent) // or Base?
{
MouseInputEnabled = false;
SetSize(15, 15);
m_ComboBox = parent;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawComboBoxArrow(this, m_ComboBox.IsHovered, m_ComboBox.IsDepressed, m_ComboBox.IsOpen, m_ComboBox.IsDisabled);
}
}
}

View file

@ -0,0 +1,96 @@
using System;
using System.Drawing;
using Gwen.Control;
using Gwen.Input;
namespace Gwen.ControlInternal
{
/// <summary>
/// Base for controls that can be dragged by mouse.
/// </summary>
public class Dragger : Base
{
protected bool m_Held;
protected Point m_HoldPos;
protected Base m_Target;
internal Base Target { get { return m_Target; } set { m_Target = value; } }
/// <summary>
/// Indicates if the control is being dragged.
/// </summary>
public bool IsHeld { get { return m_Held; } }
/// <summary>
/// Event invoked when the control position has been changed.
/// </summary>
public event GwenEventHandler Dragged;
/// <summary>
/// Initializes a new instance of the <see cref="Dragger"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Dragger(Base parent) : base(parent)
{
MouseInputEnabled = true;
m_Held = false;
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
if (null == m_Target) return;
if (down)
{
m_Held = true;
m_HoldPos = m_Target.CanvasPosToLocal(new Point(x, y));
InputHandler.MouseFocus = this;
}
else
{
m_Held = false;
InputHandler.MouseFocus = null;
}
}
/// <summary>
/// Handler invoked on mouse moved event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="dx">X change.</param>
/// <param name="dy">Y change.</param>
protected override void OnMouseMoved(int x, int y, int dx, int dy)
{
if (null == m_Target) return;
if (!m_Held) return;
Point p = new Point(x - m_HoldPos.X, y - m_HoldPos.Y);
// Translate to parent
if (m_Target.Parent != null)
p = m_Target.Parent.CanvasPosToLocal(p);
//m_Target->SetPosition( p.x, p.y );
m_Target.MoveTo(p.X, p.Y);
if (Dragged != null)
Dragged.Invoke(this);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
}
}
}

View file

@ -0,0 +1,29 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Drag&drop highlight.
/// </summary>
public class Highlight : Base
{
/// <summary>
/// Initializes a new instance of the <see cref="Highlight"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Highlight(Base parent) : base(parent)
{
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawHighlight(this);
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Divider menu item.
/// </summary>
public class MenuDivider : Base
{
/// <summary>
/// Initializes a new instance of the <see cref="MenuDivider"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public MenuDivider(Base parent)
: base(parent)
{
Height = 1;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawMenuDivider(this);
}
}
}

View file

@ -0,0 +1,42 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Modal control for windows.
/// </summary>
public class Modal : Base
{
/// <summary>
/// Initializes a new instance of the <see cref="Modal"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Modal(Base parent)
: base(parent)
{
KeyboardInputEnabled = true;
MouseInputEnabled = true;
ShouldDrawBackground = true;
SetBounds(0, 0, GetCanvas().Width, GetCanvas().Height);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
SetBounds(0, 0, GetCanvas().Width, GetCanvas().Height);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawModalControl(this);
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Label for PropertyRow.
/// </summary>
public class PropertyRowLabel : Label
{
private readonly PropertyRow m_PropertyRow;
/// <summary>
/// Initializes a new instance of the <see cref="PropertyRowLabel"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public PropertyRowLabel(PropertyRow parent)
: base(parent)
{
Alignment = Pos.Left | Pos.CenterV;
m_PropertyRow = parent;
}
/// <summary>
/// Updates control colors.
/// </summary>
public override void UpdateColors()
{
if (IsDisabled)
{
TextColor = Skin.Colors.Button.Disabled;
return;
}
if (m_PropertyRow != null && m_PropertyRow.IsEditing)
{
TextColor = Skin.Colors.Properties.Label_Selected;
return;
}
if (m_PropertyRow != null && m_PropertyRow.IsHovered)
{
TextColor = Skin.Colors.Properties.Label_Hover;
return;
}
TextColor = Skin.Colors.Properties.Label_Normal;
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Properties node.
/// </summary>
public class PropertyTreeNode : TreeNode
{
/// <summary>
/// Initializes a new instance of the <see cref="PropertyTreeNode"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public PropertyTreeNode(Base parent)
: base(parent)
{
m_Title.TextColorOverride = Skin.Colors.Properties.Title;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawPropertyTreeNode(this, m_InnerPanel.X, m_InnerPanel.Y);
}
}
}

View file

@ -0,0 +1,154 @@
using System;
using System.Drawing;
//using System.Windows.Forms;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Grab point for resizing.
/// </summary>
public class Resizer : Dragger
{
private Pos m_ResizeDir;
/// <summary>
/// Invoked when the control has been resized.
/// </summary>
public event GwenEventHandler Resized;
/// <summary>
/// Initializes a new instance of the <see cref="Resizer"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Resizer(Base parent)
: base(parent)
{
m_ResizeDir = Pos.Left;
MouseInputEnabled = true;
SetSize(6, 6);
Target = parent;
}
/// <summary>
/// Handler invoked on mouse moved event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="dx">X change.</param>
/// <param name="dy">Y change.</param>
protected override void OnMouseMoved(int x, int y, int dx, int dy)
{
if (null == m_Target) return;
if (!m_Held) return;
Rectangle oldBounds = m_Target.Bounds;
Rectangle bounds = m_Target.Bounds;
Point min = m_Target.MinimumSize;
Point pCursorPos = m_Target.CanvasPosToLocal(new Point(x, y));
Point delta = m_Target.LocalPosToCanvas(m_HoldPos);
delta.X -= x;
delta.Y -= y;
if (0 != (m_ResizeDir & Pos.Left))
{
bounds.X -= delta.X;
bounds.Width += delta.X;
// Conform to minimum size here so we don't
// go all weird when we snap it in the base conrt
if (bounds.Width < min.X)
{
int diff = min.X - bounds.Width;
bounds.Width += diff;
bounds.X -= diff;
}
}
if (0 != (m_ResizeDir & Pos.Top))
{
bounds.Y -= delta.Y;
bounds.Height += delta.Y;
// Conform to minimum size here so we don't
// go all weird when we snap it in the base conrt
if (bounds.Height < min.Y)
{
int diff = min.Y - bounds.Height;
bounds.Height += diff;
bounds.Y -= diff;
}
}
if (0 != (m_ResizeDir & Pos.Right))
{
// This is complicated.
// Basically we want to use the HoldPos, so it doesn't snap to the edge of the control
// But we need to move the HoldPos with the window movement. Yikes.
// I actually think this might be a big hack around the way this control works with regards
// to the holdpos being on the parent panel.
int woff = bounds.Width - m_HoldPos.X;
int diff = bounds.Width;
bounds.Width = pCursorPos.X + woff;
if (bounds.Width < min.X) bounds.Width = min.X;
diff -= bounds.Width;
m_HoldPos.X -= diff;
}
if (0 != (m_ResizeDir & Pos.Bottom))
{
int hoff = bounds.Height - m_HoldPos.Y;
int diff = bounds.Height;
bounds.Height = pCursorPos.Y + hoff;
if (bounds.Height < min.Y) bounds.Height = min.Y;
diff -= bounds.Height;
m_HoldPos.Y -= diff;
}
m_Target.SetBounds(bounds);
if (Resized != null)
Resized.Invoke(this);
}
/// <summary>
/// Gets or sets the sizing direction.
/// </summary>
public Pos ResizeDir
{
set
{
m_ResizeDir = value;
if ((0 != (value & Pos.Left) && 0 != (value & Pos.Top)) || (0 != (value & Pos.Right) && 0 != (value & Pos.Bottom)))
{
//Cursor = Cursors.SizeNWSE;
return;
}
if ((0 != (value & Pos.Right) && 0 != (value & Pos.Top)) || (0 != (value & Pos.Left) && 0 != (value & Pos.Bottom)))
{
//Cursor = Cursors.SizeNESW;
return;
}
if (0 != (value & Pos.Right) || 0 != (value & Pos.Left))
{
//Cursor = Cursors.SizeWE;
return;
}
if (0 != (value & Pos.Top) || 0 != (value & Pos.Bottom))
{
//Cursor = Cursors.SizeNS;
return;
}
}
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Submenu indicator.
/// </summary>
public class RightArrow : Base
{
/// <summary>
/// Initializes a new instance of the <see cref="RightArrow"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public RightArrow(Base parent)
: base(parent)
{
MouseInputEnabled = false;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawMenuRightArrow(this);
}
}
}

View file

@ -0,0 +1,85 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Scrollbar bar.
/// </summary>
public class ScrollBarBar : Dragger
{
private bool m_Horizontal;
/// <summary>
/// Indicates whether the bar is horizontal.
/// </summary>
public bool IsHorizontal { get { return m_Horizontal; } set { m_Horizontal = value; } }
/// <summary>
/// Indicates whether the bar is vertical.
/// </summary>
public bool IsVertical { get { return !m_Horizontal; } set { m_Horizontal = !value; } }
/// <summary>
/// Initializes a new instance of the <see cref="ScrollBarBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ScrollBarBar(Base parent)
: base(parent)
{
RestrictToParent = true;
Target = this;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawScrollBarBar(this, m_Held, IsHovered, m_Horizontal);
base.Render(skin);
}
/// <summary>
/// Handler invoked on mouse moved event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="dx">X change.</param>
/// <param name="dy">Y change.</param>
protected override void OnMouseMoved(int x, int y, int dx, int dy)
{
base.OnMouseMoved(x, y, dx, dy);
if (!m_Held)
return;
InvalidateParent();
}
/// <summary>
/// Handler invoked on mouse click (left) event.
/// </summary>
/// <param name="x">X coordinate.</param>
/// <param name="y">Y coordinate.</param>
/// <param name="down">If set to <c>true</c> mouse button is down.</param>
protected override void OnMouseClickedLeft(int x, int y, bool down)
{
base.OnMouseClickedLeft(x, y, down);
InvalidateParent();
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
if (null == Parent)
return;
//Move to our current position to force clamping - is this a hack?
MoveTo(X, Y);
}
}
}

View file

@ -0,0 +1,52 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Scrollbar button.
/// </summary>
public class ScrollBarButton : Button
{
private Pos m_Direction;
/// <summary>
/// Initializes a new instance of the <see cref="ScrollBarButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public ScrollBarButton(Base parent)
: base(parent)
{
SetDirectionUp();
}
public virtual void SetDirectionUp()
{
m_Direction = Pos.Top;
}
public virtual void SetDirectionDown()
{
m_Direction = Pos.Bottom;
}
public virtual void SetDirectionLeft()
{
m_Direction = Pos.Left;
}
public virtual void SetDirectionRight()
{
m_Direction = Pos.Right;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawScrollButton(this, m_Direction, IsDepressed, IsHovered, IsDisabled);
}
}
}

View file

@ -0,0 +1,38 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Slider bar.
/// </summary>
public class SliderBar : Dragger
{
private bool m_bHorizontal;
/// <summary>
/// Indicates whether the bar is horizontal.
/// </summary>
public bool IsHorizontal { get { return m_bHorizontal; } set { m_bHorizontal = value; } }
/// <summary>
/// Initializes a new instance of the <see cref="SliderBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public SliderBar(Base parent)
: base(parent)
{
Target = this;
RestrictToParent = true;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawSliderButton(this, IsHeld, IsHorizontal);
}
}
}

View file

@ -0,0 +1,41 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Splitter bar.
/// </summary>
public class SplitterBar : Dragger
{
/// <summary>
/// Initializes a new instance of the <see cref="SplitterBar"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public SplitterBar(Base parent)
: base(parent)
{
Target = this;
RestrictToParent = true;
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
if (ShouldDrawBackground)
skin.DrawButton(this, true, false, IsDisabled);
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
MoveTo(X, Y);
}
}
}

View file

@ -0,0 +1,28 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Inner panel of tab control.
/// </summary>
public class TabControlInner : Base
{
/// <summary>
/// Initializes a new instance of the <see cref="TabControlInner"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
internal TabControlInner(Base parent) : base(parent)
{
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawTabControl(this);
}
}
}

View file

@ -0,0 +1,210 @@
//#define DEBUG_TEXT_MEASURE
using System;
using System.Drawing;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Displays text. Always sized to contents.
/// </summary>
public class Text : Base
{
private String m_String;
private Font m_Font;
/// <summary>
/// Font used to display the text.
/// </summary>
/// <remarks>
/// The font is not being disposed by this class.
/// </remarks>
public Font Font
{
get { return m_Font; }
set
{
m_Font = value;
SizeToContents();
}
}
/// <summary>
/// Text to display.
/// </summary>
public String String
{
get { return m_String; }
set
{
m_String = value;
SizeToContents();
}
}
/// <summary>
/// Text color.
/// </summary>
public Color TextColor { get; set; }
/// <summary>
/// Determines whether the control should be automatically resized to fit the text.
/// </summary>
//public bool AutoSizeToContents { get; set; } // [omeg] added
/// <summary>
/// Text length in characters.
/// </summary>
public int Length { get { return String.Length; } }
/// <summary>
/// Text color override - used by tooltips.
/// </summary>
public Color TextColorOverride { get; set; }
/// <summary>
/// Text override - used to display different string.
/// </summary>
public String TextOverride { get; set; }
/// <summary>
/// Initializes a new instance of the <see cref="Text"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public Text(Base parent)
: base(parent)
{
m_Font = Skin.DefaultFont;
m_String = string.Empty;
TextColor = Skin.Colors.Label.Default;
MouseInputEnabled = false;
TextColorOverride = Color.FromArgb(0, 255, 255, 255); // A==0, override disabled
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
if (Length == 0 || Font == null) return;
if (TextColorOverride.A == 0)
skin.Renderer.DrawColor = TextColor;
else
skin.Renderer.DrawColor = TextColorOverride;
skin.Renderer.RenderText(Font, Point.Empty, TextOverride ?? String);
#if DEBUG_TEXT_MEASURE
{
Point lastPos = Point.Empty;
for (int i = 0; i < m_String.Length + 1; i++)
{
String sub = (TextOverride ?? String).Substring(0, i);
Point p = Skin.Renderer.MeasureText(Font, sub);
Rectangle rect = new Rectangle();
rect.Location = lastPos;
rect.Size = new Size(p.X - lastPos.X, p.Y);
skin.Renderer.DrawColor = Color.FromArgb(64, 0, 0, 0);
skin.Renderer.DrawLinedRect(rect);
lastPos = new Point(rect.Right, 0);
}
}
#endif
}
/// <summary>
/// Lays out the control's interior according to alignment, padding, dock etc.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Layout(Skin.Base skin)
{
SizeToContents();
base.Layout(skin);
}
/// <summary>
/// Handler invoked when control's scale changes.
/// </summary>
protected override void OnScaleChanged()
{
Invalidate();
}
/// <summary>
/// Sizes the control to its contents.
/// </summary>
public void SizeToContents()
{
if (String == null)
return;
if (Font == null)
{
throw new InvalidOperationException("Text.SizeToContents() - No Font!!\n");
}
Point p = new Point(1, Font.Size);
if (Length > 0)
{
p = Skin.Renderer.MeasureText(Font, TextOverride ?? String);
}
if (p.X == Width && p.Y == Height)
return;
SetSize(p.X, p.Y);
Invalidate();
InvalidateParent();
}
/// <summary>
/// Gets the coordinates of specified character in the text.
/// </summary>
/// <param name="index">Character index.</param>
/// <returns>Character position in local coordinates.</returns>
public Point GetCharacterPosition(int index)
{
if (Length == 0 || index == 0)
{
return new Point(0, 0);
}
String sub = (TextOverride ?? String).Substring(0, index);
Point p = Skin.Renderer.MeasureText(Font, sub);
return p;
}
/// <summary>
/// Searches for a character closest to given point.
/// </summary>
/// <param name="p">Point.</param>
/// <returns>Character index.</returns>
public int GetClosestCharacter(Point p)
{
int distance = MaxCoord;
int c = 0;
for (int i = 0; i < String.Length + 1; i++)
{
Point cp = GetCharacterPosition(i);
int dist = Math.Abs(cp.X - p.X); // TODO: handle multiline
if (dist > distance)
continue;
distance = dist;
c = i;
}
return c;
}
}
}

View file

@ -0,0 +1,50 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Tree node label.
/// </summary>
public class TreeNodeLabel : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="TreeNodeLabel"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TreeNodeLabel(Base parent)
: base(parent)
{
Alignment = Pos.Left | Pos.CenterV;
ShouldDrawBackground = false;
Height = 16;
TextPadding = new Padding(3, 0, 3, 0);
}
/// <summary>
/// Updates control colors.
/// </summary>
public override void UpdateColors()
{
if (IsDisabled)
{
TextColor = Skin.Colors.Button.Disabled;
return;
}
if (IsDepressed || ToggleState)
{
TextColor = Skin.Colors.Tree.Selected;
return;
}
if (IsHovered)
{
TextColor = Skin.Colors.Tree.Hover;
return;
}
TextColor = Skin.Colors.Tree.Normal;
}
}
}

View file

@ -0,0 +1,40 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Tree node toggle button (the little plus sign).
/// </summary>
public class TreeToggleButton : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="TreeToggleButton"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public TreeToggleButton(Base parent)
: base(parent)
{
IsToggle = true;
IsTabable = false;
}
/// <summary>
/// Renders the focus overlay.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void RenderFocus(Skin.Base skin)
{
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawTreeButton(this, ToggleState);
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Numeric down arrow.
/// </summary>
internal class UpDownButton_Down : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="UpDownButton_Down"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public UpDownButton_Down(Base parent)
: base(parent)
{
SetSize(7, 7);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawNumericUpDownButton(this, IsDepressed, false);
}
}
}

View file

@ -0,0 +1,30 @@
using System;
using Gwen.Control;
namespace Gwen.ControlInternal
{
/// <summary>
/// Numeric up arrow.
/// </summary>
public class UpDownButton_Up : Button
{
/// <summary>
/// Initializes a new instance of the <see cref="UpDownButton_Up"/> class.
/// </summary>
/// <param name="parent">Parent control.</param>
public UpDownButton_Up(Base parent)
: base(parent)
{
SetSize(7, 7);
}
/// <summary>
/// Renders the control using specified skin.
/// </summary>
/// <param name="skin">Skin to use.</param>
protected override void Render(Skin.Base skin)
{
skin.DrawNumericUpDownButton(this, IsDepressed, true);
}
}
}

View file

@ -0,0 +1,244 @@
using System;
using System.Drawing;
//using System.Windows.Forms;
using Gwen.Control;
using Gwen.Input;
namespace Gwen.DragDrop
{
/// <summary>
/// Drag and drop handling.
/// </summary>
public static class DragAndDrop
{
public static Package CurrentPackage;
public static Base HoveredControl;
public static Base SourceControl;
private static Base m_LastPressedControl;
private static Base m_NewHoveredControl;
private static Point m_LastPressedPos;
private static int m_MouseX;
private static int m_MouseY;
private static bool onDrop(int x, int y)
{
bool success = false;
if (HoveredControl != null)
{
HoveredControl.DragAndDrop_HoverLeave(CurrentPackage);
success = HoveredControl.DragAndDrop_HandleDrop(CurrentPackage, x, y);
}
// Report back to the source control, to tell it if we've been successful.
SourceControl.DragAndDrop_EndDragging(success, x, y);
CurrentPackage = null;
SourceControl = null;
return true;
}
private static bool ShouldStartDraggingControl( int x, int y )
{
// We're not holding a control down..
if (m_LastPressedControl == null)
return false;
// Not been dragged far enough
int length = Math.Abs(x - m_LastPressedPos.X) + Math.Abs(y - m_LastPressedPos.Y);
if (length < 5)
return false;
// Create the dragging package
CurrentPackage = m_LastPressedControl.DragAndDrop_GetPackage(m_LastPressedPos.X, m_LastPressedPos.Y);
// We didn't create a package!
if (CurrentPackage == null)
{
m_LastPressedControl = null;
SourceControl = null;
return false;
}
// Now we're dragging something!
SourceControl = m_LastPressedControl;
InputHandler.MouseFocus = null;
m_LastPressedControl = null;
CurrentPackage.DrawControl = null;
// Some controls will want to decide whether they should be dragged at that moment.
// This function is for them (it defaults to true)
if (!SourceControl.DragAndDrop_ShouldStartDrag())
{
SourceControl = null;
CurrentPackage = null;
return false;
}
SourceControl.DragAndDrop_StartDragging(CurrentPackage, m_LastPressedPos.X, m_LastPressedPos.Y);
return true;
}
private static void UpdateHoveredControl(Base control, int x, int y)
{
//
// We use this global variable to represent our hovered control
// That way, if the new hovered control gets deleted in one of the
// Hover callbacks, we won't be left with a hanging pointer.
// This isn't ideal - but it's minimal.
//
m_NewHoveredControl = control;
// Nothing to change..
if (HoveredControl == m_NewHoveredControl)
return;
// We changed - tell the old hovered control that it's no longer hovered.
if (HoveredControl != null && HoveredControl != m_NewHoveredControl)
HoveredControl.DragAndDrop_HoverLeave(CurrentPackage);
// If we're hovering where the control came from, just forget it.
// By changing it to null here we're not going to show any error cursors
// it will just do nothing if you drop it.
if (m_NewHoveredControl == SourceControl)
m_NewHoveredControl = null;
// Check to see if the new potential control can accept this type of package.
// If not, ignore it and show an error cursor.
while (m_NewHoveredControl != null && !m_NewHoveredControl.DragAndDrop_CanAcceptPackage(CurrentPackage))
{
// We can't drop on this control, so lets try to drop
// onto its parent..
m_NewHoveredControl = m_NewHoveredControl.Parent;
// Its parents are dead. We can't drop it here.
// Show the NO WAY cursor.
if (m_NewHoveredControl == null)
{
//Platform.Neutral.SetCursor(Cursors.No);
}
}
// Become out new hovered control
HoveredControl = m_NewHoveredControl;
// If we exist, tell us that we've started hovering.
if (HoveredControl != null)
{
HoveredControl.DragAndDrop_HoverEnter(CurrentPackage, x, y);
}
m_NewHoveredControl = null;
}
public static bool Start(Base control, Package package)
{
if (CurrentPackage != null)
{
return false;
}
CurrentPackage = package;
SourceControl = control;
return true;
}
public static bool OnMouseButton(Base hoveredControl, int x, int y, bool down)
{
if (!down)
{
m_LastPressedControl = null;
// Not carrying anything, allow normal actions
if (CurrentPackage == null)
return false;
// We were carrying something, drop it.
onDrop(x, y);
return true;
}
if (hoveredControl == null)
return false;
if (!hoveredControl.DragAndDrop_Draggable())
return false;
// Store the last clicked on control. Don't do anything yet,
// we'll check it in OnMouseMoved, and if it moves further than
// x pixels with the mouse down, we'll start to drag.
m_LastPressedPos = new Point(x, y);
m_LastPressedControl = hoveredControl;
return false;
}
public static void OnMouseMoved(Base hoveredControl, int x, int y)
{
// Always keep these up to date, they're used to draw the dragged control.
m_MouseX = x;
m_MouseY = y;
// If we're not carrying anything, then check to see if we should
// pick up from a control that we're holding down. If not, then forget it.
if (CurrentPackage == null && !ShouldStartDraggingControl(x, y))
return;
// Swap to this new hovered control and notify them of the change.
UpdateHoveredControl(hoveredControl, x, y);
if (HoveredControl == null)
return;
// Update the hovered control every mouse move, so it can show where
// the dropped control will land etc..
HoveredControl.DragAndDrop_Hover(CurrentPackage, x, y);
// Override the cursor - since it might have been set my underlying controls
// Ideally this would show the 'being dragged' control. TODO
//Platform.Neutral.SetCursor(Cursors.Default);
hoveredControl.Redraw();
}
public static void RenderOverlay(Canvas canvas, Skin.Base skin)
{
if (CurrentPackage == null)
return;
if (CurrentPackage.DrawControl == null)
return;
Point old = skin.Renderer.RenderOffset;
skin.Renderer.AddRenderOffset(new Rectangle(
m_MouseX - SourceControl.X - CurrentPackage.HoldOffset.X,
m_MouseY - SourceControl.Y - CurrentPackage.HoldOffset.Y, 0, 0));
CurrentPackage.DrawControl.DoRender(skin);
skin.Renderer.RenderOffset = old;
}
public static void ControlDeleted(Base control)
{
if (SourceControl == control)
{
SourceControl = null;
CurrentPackage = null;
HoveredControl = null;
m_LastPressedControl = null;
}
if (m_LastPressedControl == control)
m_LastPressedControl = null;
if (HoveredControl == control)
HoveredControl = null;
if (m_NewHoveredControl == control)
m_NewHoveredControl = null;
}
}
}

15
Gwen/DragDrop/Package.cs Normal file
View file

@ -0,0 +1,15 @@
using System;
using System.Drawing;
using Gwen.Control;
namespace Gwen.DragDrop
{
public class Package
{
public String Name;
public object UserData;
public bool IsDraggable;
public Base DrawControl;
public Point HoldOffset;
}
}

98
Gwen/Font.cs Normal file
View file

@ -0,0 +1,98 @@
using System;
namespace Gwen
{
/// <summary>
/// Represents font resource.
/// </summary>
public class Font : IDisposable
{
/// <summary>
/// Font face name. Exact meaning depends on renderer.
/// </summary>
public String FaceName { get; set; }
/// <summary>
/// Font size.
/// </summary>
public int Size { get; set; }
/// <summary>
/// Enables or disables font smoothing (default: disabled).
/// </summary>
public bool Smooth { get; set; }
//public bool Bold { get; set; }
//public bool DropShadow { get; set; }
/// <summary>
/// This should be set by the renderer if it tries to use a font where it's null.
/// </summary>
public object RendererData { get; set; }
/// <summary>
/// This is the real font size, after it's been scaled by Renderer.Scale()
/// </summary>
public float RealSize { get; set; }
private readonly Renderer.Base m_Renderer;
/// <summary>
/// Initializes a new instance of the <see cref="Font"/> class.
/// </summary>
public Font(Renderer.Base renderer)
: this(renderer, "Arial", 10)
{
}
/// <summary>
/// Initializes a new instance of the <see cref="Font"/> class.
/// </summary>
/// <param name="renderer">Renderer to use.</param>
/// <param name="faceName">Face name.</param>
/// <param name="size">Font size.</param>
public Font(Renderer.Base renderer, String faceName, int size = 10)
{
m_Renderer = renderer;
FaceName = faceName;
Size = size;
Smooth = false;
//Bold = false;
//DropShadow = false;
}
/// <summary>
/// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
/// </summary>
public void Dispose()
{
m_Renderer.FreeFont(this);
GC.SuppressFinalize(this);
}
#if DEBUG
~Font()
{
throw new InvalidOperationException(String.Format("IDisposable object finalized: {0}", GetType()));
//Debug.Print(String.Format("IDisposable object finalized: {0}", GetType()));
}
#endif
/// <summary>
/// Duplicates font data (except renderer data which must be reinitialized).
/// </summary>
/// <returns></returns>
public Font Copy()
{
Font f = new Font(m_Renderer, FaceName);
f.Size = Size;
f.RealSize = RealSize;
f.RendererData = null; // must be reinitialized
//f.Bold = Bold;
//f.DropShadow = DropShadow;
return f;
}
}
}

157
Gwen/Gwen.csproj Normal file
View file

@ -0,0 +1,157 @@
<?xml version="1.0" encoding="utf-8"?>
<Project DefaultTargets="Build" ToolsVersion="4.0" xmlns="http://schemas.microsoft.com/developer/msbuild/2003">
<PropertyGroup>
<Configuration Condition=" '$(Configuration)' == '' ">Debug</Configuration>
<Platform Condition=" '$(Platform)' == '' ">AnyCPU</Platform>
<ProductVersion>10.0.0</ProductVersion>
<SchemaVersion>2.0</SchemaVersion>
<ProjectGuid>{D3F5E624-3AF2-418F-A180-8A4172928065}</ProjectGuid>
<OutputType>Library</OutputType>
<RootNamespace>Gwen</RootNamespace>
<AssemblyName>Gwen</AssemblyName>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Debug|AnyCPU' ">
<DebugSymbols>true</DebugSymbols>
<DebugType>full</DebugType>
<Optimize>false</Optimize>
<OutputPath>bin\Debug</OutputPath>
<DefineConstants>DEBUG;</DefineConstants>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<GenerateDocumentation>true</GenerateDocumentation>
</PropertyGroup>
<PropertyGroup Condition=" '$(Configuration)|$(Platform)' == 'Release|AnyCPU' ">
<DebugType>none</DebugType>
<Optimize>true</Optimize>
<OutputPath>bin\Release</OutputPath>
<ErrorReport>prompt</ErrorReport>
<WarningLevel>4</WarningLevel>
<ConsolePause>false</ConsolePause>
<GenerateDocumentation>true</GenerateDocumentation>
</PropertyGroup>
<ItemGroup>
<Reference Include="System" />
<Reference Include="System.Drawing" />
</ItemGroup>
<Import Project="$(MSBuildBinPath)\Microsoft.CSharp.targets" />
<ItemGroup>
<Compile Include="Align.cs" />
<Compile Include="Font.cs" />
<Compile Include="HSV.cs" />
<Compile Include="Key.cs" />
<Compile Include="Margin.cs" />
<Compile Include="Padding.cs" />
<Compile Include="Pos.cs" />
<Compile Include="Texture.cs" />
<Compile Include="ToolTip.cs" />
<Compile Include="Util.cs" />
<Compile Include="Anim\Animation.cs" />
<Compile Include="Anim\TimedAnimation.cs" />
<Compile Include="Anim\Size\Height.cs" />
<Compile Include="Anim\Size\Width.cs" />
<Compile Include="Control\Base.cs" />
<Compile Include="Control\Button.cs" />
<Compile Include="Control\Canvas.cs" />
<Compile Include="Control\CheckBox.cs" />
<Compile Include="Control\CollapsibleCategory.cs" />
<Compile Include="Control\CollapsibleList.cs" />
<Compile Include="Control\ColorLerpBox.cs" />
<Compile Include="Control\ColorPicker.cs" />
<Compile Include="Control\ColorSlider.cs" />
<Compile Include="Control\ComboBox.cs" />
<Compile Include="Control\CrossSplitter.cs" />
<Compile Include="Control\DockBase.cs" />
<Compile Include="Control\DockedTabControl.cs" />
<Compile Include="Control\GroupBox.cs" />
<Compile Include="Control\HSVColorPicker.cs" />
<Compile Include="Control\HorizontalScrollBar.cs" />
<Compile Include="Control\HorizontalSlider.cs" />
<Compile Include="Control\HorizontalSplitter.cs" />
<Compile Include="Control\IColorPicker.cs" />
<Compile Include="Control\ImagePanel.cs" />
<Compile Include="Control\Label.cs" />
<Compile Include="Control\LabelClickable.cs" />
<Compile Include="Control\LabeledCheckBox.cs" />
<Compile Include="Control\LabeledRadioButton.cs" />
<Compile Include="Control\ListBox.cs" />
<Compile Include="Control\ListBoxRow.cs" />
<Compile Include="Control\Menu.cs" />
<Compile Include="Control\MenuItem.cs" />
<Compile Include="Control\MenuStrip.cs" />
<Compile Include="Control\MessageBox.cs" />
<Compile Include="Control\NumericUpDown.cs" />
<Compile Include="Control\ProgressBar.cs" />
<Compile Include="Control\Properties.cs" />
<Compile Include="Control\PropertyRow.cs" />
<Compile Include="Control\PropertyTree.cs" />
<Compile Include="Control\RadioButton.cs" />
<Compile Include="Control\RadioButtonGroup.cs" />
<Compile Include="Control\ResizableControl.cs" />
<Compile Include="Control\RichLabel.cs" />
<Compile Include="Control\ScrollBar.cs" />
<Compile Include="Control\ScrollControl.cs" />
<Compile Include="Control\Slider.cs" />
<Compile Include="Control\StatusBar.cs" />
<Compile Include="Control\TabButton.cs" />
<Compile Include="Control\TabControl.cs" />
<Compile Include="Control\TabStrip.cs" />
<Compile Include="Control\TabTitleBar.cs" />
<Compile Include="Control\TextBox.cs" />
<Compile Include="Control\TextBoxNumeric.cs" />
<Compile Include="Control\TextBoxPassword.cs" />
<Compile Include="Control\TreeControl.cs" />
<Compile Include="Control\TreeNode.cs" />
<Compile Include="Control\VerticalScrollBar.cs" />
<Compile Include="Control\VerticalSlider.cs" />
<Compile Include="Control\VerticalSplitter.cs" />
<Compile Include="Control\WindowControl.cs" />
<Compile Include="Control\Layout\Positioner.cs" />
<Compile Include="Control\Layout\Splitter.cs" />
<Compile Include="Control\Layout\Table.cs" />
<Compile Include="Control\Layout\TableRow.cs" />
<Compile Include="Control\Property\Base.cs" />
<Compile Include="Control\Property\Check.cs" />
<Compile Include="Control\Property\Color.cs" />
<Compile Include="Control\Property\Text.cs" />
<Compile Include="ControlInternal\CategoryButton.cs" />
<Compile Include="ControlInternal\CategoryHeaderButton.cs" />
<Compile Include="ControlInternal\CloseButton.cs" />
<Compile Include="ControlInternal\ColorButton.cs" />
<Compile Include="ControlInternal\ColorDisplay.cs" />
<Compile Include="ControlInternal\DownArrow.cs" />
<Compile Include="ControlInternal\Dragger.cs" />
<Compile Include="ControlInternal\Highlight.cs" />
<Compile Include="ControlInternal\MenuDivider.cs" />
<Compile Include="ControlInternal\Modal.cs" />
<Compile Include="ControlInternal\PropertyRowLabel.cs" />
<Compile Include="ControlInternal\PropertyTreeNode.cs" />
<Compile Include="ControlInternal\Resizer.cs" />
<Compile Include="ControlInternal\RightArrow.cs" />
<Compile Include="ControlInternal\ScrollBarBar.cs" />
<Compile Include="ControlInternal\ScrollBarButton.cs" />
<Compile Include="ControlInternal\SliderBar.cs" />
<Compile Include="ControlInternal\SplitterBar.cs" />
<Compile Include="ControlInternal\TabControlInner.cs" />
<Compile Include="ControlInternal\Text.cs" />
<Compile Include="ControlInternal\TreeNodeLabel.cs" />
<Compile Include="ControlInternal\TreeToggleButton.cs" />
<Compile Include="ControlInternal\UpDownButton_Down.cs" />
<Compile Include="ControlInternal\UpDownButton_Up.cs" />
<Compile Include="DragDrop\DragAndDrop.cs" />
<Compile Include="DragDrop\Package.cs" />
<Compile Include="Input\InputHandler.cs" />
<Compile Include="Input\KeyData.cs" />
<Compile Include="Platform\Neutral.cs" />
<Compile Include="Platform\Windows.cs" />
<Compile Include="Properties\AssemblyInfo.cs" />
<Compile Include="Renderer\Base.cs" />
<Compile Include="Renderer\ICacheToTexture.cs" />
<Compile Include="Skin\Base.cs" />
<Compile Include="Skin\Simple.cs" />
<Compile Include="Skin\SkinColors.cs" />
<Compile Include="Skin\TexturedBase.cs" />
<Compile Include="Skin\Texturing\Bordered.cs" />
<Compile Include="Skin\Texturing\Single.cs" />
</ItemGroup>
</Project>

11
Gwen/HSV.cs Normal file
View file

@ -0,0 +1,11 @@
using System;
namespace Gwen
{
public struct HSV
{
public float h;
public float s;
public float v;
}
}

410
Gwen/Input/InputHandler.cs Normal file
View file

@ -0,0 +1,410 @@
using System;
using System.Drawing;
using System.Linq;
using System.Text;
using Gwen.Control;
using Gwen.DragDrop;
namespace Gwen.Input
{
/// <summary>
/// Input handling.
/// </summary>
public static class InputHandler
{
private static readonly KeyData m_KeyData = new KeyData();
private static readonly float[] m_LastClickTime = new float[MaxMouseButtons];
private static Point m_LastClickPos;
/// <summary>
/// Control currently hovered by mouse.
/// </summary>
public static Base HoveredControl;
/// <summary>
/// Control that corrently has keyboard focus.
/// </summary>
public static Base KeyboardFocus;
/// <summary>
/// Control that currently has mouse focus.
/// </summary>
public static Base MouseFocus;
/// <summary>
/// Maximum number of mouse buttons supported.
/// </summary>
public static int MaxMouseButtons { get { return 5; } }
/// <summary>
/// Maximum time in seconds between mouse clicks to be recognized as double click.
/// </summary>
public static float DoubleClickSpeed { get { return 0.5f; } }
/// <summary>
/// Time in seconds between autorepeating of keys.
/// </summary>
public static float KeyRepeatRate { get { return 0.03f; } }
/// <summary>
/// Time in seconds before key starts to autorepeat.
/// </summary>
public static float KeyRepeatDelay { get { return 0.5f; } }
/// <summary>
/// Indicates whether the left mouse button is down.
/// </summary>
public static bool IsLeftMouseDown { get { return m_KeyData.LeftMouseDown; } }
/// <summary>
/// Indicates whether the right mouse button is down.
/// </summary>
public static bool IsRightMouseDown { get { return m_KeyData.RightMouseDown; } }
/// <summary>
/// Current mouse position.
/// </summary>
public static Point MousePosition; // not property to allow modification of Point fields
/// <summary>
/// Indicates whether the shift key is down.
/// </summary>
public static bool IsShiftDown { get { return IsKeyDown(Key.Shift); } }
/// <summary>
/// Indicates whether the control key is down.
/// </summary>
public static bool IsControlDown { get { return IsKeyDown(Key.Control); } }
/// <summary>
/// Checks if the given key is pressed.
/// </summary>
/// <param name="key">Key to check.</param>
/// <returns>True if the key is down.</returns>
public static bool IsKeyDown(Key key)
{
return m_KeyData.KeyState[(int)key];
}
/// <summary>
/// Handles copy, paste etc.
/// </summary>
/// <param name="canvas">Canvas.</param>
/// <param name="chr">Input character.</param>
/// <returns>True if the key was handled.</returns>
public static bool DoSpecialKeys(Base canvas, char chr)
{
if (null == KeyboardFocus) return false;
if (KeyboardFocus.GetCanvas() != canvas) return false;
if (!KeyboardFocus.IsVisible) return false;
if (!IsControlDown) return false;
if (chr == 'C' || chr == 'c')
{
KeyboardFocus.InputCopy(null);
return true;
}
if (chr == 'V' || chr == 'v')
{
KeyboardFocus.InputPaste(null);
return true;
}
if (chr == 'X' || chr == 'x')
{
KeyboardFocus.InputCut(null);
return true;
}
if (chr == 'A' || chr == 'a')
{
KeyboardFocus.InputSelectAll(null);
return true;
}
return false;
}
/// <summary>
/// Handles accelerator input.
/// </summary>
/// <param name="canvas">Canvas.</param>
/// <param name="chr">Input character.</param>
/// <returns>True if the key was handled.</returns>
public static bool HandleAccelerator(Base canvas, char chr)
{
//Build the accelerator search string
StringBuilder accelString = new StringBuilder();
if (IsControlDown)
accelString.Append("CTRL+");
if (IsShiftDown)
accelString.Append("SHIFT+");
// [omeg] todo: alt?
accelString.Append(chr);
String acc = accelString.ToString();
//Debug::Msg("Accelerator string :%S\n", accelString.c_str());)
if (KeyboardFocus != null && KeyboardFocus.HandleAccelerator(acc))
return true;
if (MouseFocus != null && MouseFocus.HandleAccelerator(acc))
return true;
if (canvas.HandleAccelerator(acc))
return true;
return false;
}
/// <summary>
/// Mouse moved handler.
/// </summary>
/// <param name="canvas">Canvas.</param>
/// <param name="x"></param>
/// <param name="y"></param>
/// <param name="dx"></param>
/// <param name="dy"></param>
public static void OnMouseMoved(Base canvas, int x, int y, int dx, int dy)
{
// Send input to canvas for study
MousePosition.X = x;
MousePosition.Y = y;
UpdateHoveredControl(canvas);
}
/// <summary>
/// Handles focus updating and key autorepeats.
/// </summary>
/// <param name="control">Unused.</param>
public static void OnCanvasThink(Base control)
{
if (MouseFocus != null && !MouseFocus.IsVisible)
MouseFocus = null;
if (KeyboardFocus != null && (!KeyboardFocus.IsVisible || !KeyboardFocus.KeyboardInputEnabled))
KeyboardFocus = null;
if (null == KeyboardFocus) return;
if (KeyboardFocus.GetCanvas() != control) return;
float time = Platform.Neutral.GetTimeInSeconds();
//
// Simulate Key-Repeats
//
for (int i = 0; i < (int)Key.Count; i++)
{
if (m_KeyData.KeyState[i] && m_KeyData.Target != KeyboardFocus)
{
m_KeyData.KeyState[i] = false;
continue;
}
if (m_KeyData.KeyState[i] && time > m_KeyData.NextRepeat[i])
{
m_KeyData.NextRepeat[i] = Platform.Neutral.GetTimeInSeconds() + KeyRepeatRate;
if (KeyboardFocus != null)
{
KeyboardFocus.InputKeyPressed((Key)i);
}
}
}
}
/// <summary>
/// Mouse click handler.
/// </summary>
/// <param name="canvas">Canvas.</param>
/// <param name="mouseButton">Mouse button number.</param>
/// <param name="down">Specifies if the button is down.</param>
/// <returns>True if handled.</returns>
public static bool OnMouseClicked(Base canvas, int mouseButton, bool down)
{
// If we click on a control that isn't a menu we want to close
// all the open menus. Menus are children of the canvas.
if (down && (null == HoveredControl || !HoveredControl.IsMenuComponent))
{
canvas.CloseMenus();
}
if (null == HoveredControl) return false;
if (HoveredControl.GetCanvas() != canvas) return false;
if (!HoveredControl.IsVisible) return false;
if (HoveredControl == canvas) return false;
if (mouseButton > MaxMouseButtons)
return false;
if (mouseButton == 0)
m_KeyData.LeftMouseDown = down;
else if (mouseButton == 1)
m_KeyData.RightMouseDown = down;
// Double click.
// Todo: Shouldn't double click if mouse has moved significantly
bool isDoubleClick = false;
if (down &&
m_LastClickPos.X == MousePosition.X &&
m_LastClickPos.Y == MousePosition.Y &&
(Platform.Neutral.GetTimeInSeconds() - m_LastClickTime[mouseButton]) < DoubleClickSpeed)
{
isDoubleClick = true;
}
if (down && !isDoubleClick)
{
m_LastClickTime[mouseButton] = Platform.Neutral.GetTimeInSeconds();
m_LastClickPos = MousePosition;
}
if (down)
{
FindKeyboardFocus(HoveredControl);
}
HoveredControl.UpdateCursor();
// This tells the child it has been touched, which
// in turn tells its parents, who tell their parents.
// This is basically so that Windows can pop themselves
// to the top when one of their children have been clicked.
if (down)
HoveredControl.Touch();
#if GWEN_HOOKSYSTEM
if (bDown)
{
if (Hook::CallHook(&Hook::BaseHook::OnControlClicked, HoveredControl, MousePosition.x,
MousePosition.y))
return true;
}
#endif
switch (mouseButton)
{
case 0:
{
if (DragAndDrop.OnMouseButton(HoveredControl, MousePosition.X, MousePosition.Y, down))
return true;
if (isDoubleClick)
HoveredControl.InputMouseDoubleClickedLeft(MousePosition.X, MousePosition.Y);
else
HoveredControl.InputMouseClickedLeft(MousePosition.X, MousePosition.Y, down);
return true;
}
case 1:
{
if (isDoubleClick)
HoveredControl.InputMouseDoubleClickedRight(MousePosition.X, MousePosition.Y);
else
HoveredControl.InputMouseClickedRight(MousePosition.X, MousePosition.Y, down);
return true;
}
}
return false;
}
/// <summary>
/// Key handler.
/// </summary>
/// <param name="canvas">Canvas.</param>
/// <param name="key">Key.</param>
/// <param name="down">True if the key is down.</param>
/// <returns>True if handled.</returns>
public static bool OnKeyEvent(Base canvas, Key key, bool down)
{
if (null == KeyboardFocus) return false;
if (KeyboardFocus.GetCanvas() != canvas) return false;
if (!KeyboardFocus.IsVisible) return false;
int iKey = (int)key;
if (down)
{
if (!m_KeyData.KeyState[iKey])
{
m_KeyData.KeyState[iKey] = true;
m_KeyData.NextRepeat[iKey] = Platform.Neutral.GetTimeInSeconds() + KeyRepeatDelay;
m_KeyData.Target = KeyboardFocus;
return KeyboardFocus.InputKeyPressed(key);
}
}
else
{
if (m_KeyData.KeyState[iKey])
{
m_KeyData.KeyState[iKey] = false;
// BUG BUG. This causes shift left arrow in textboxes
// to not work. What is disabling it here breaking?
//m_KeyData.Target = NULL;
return KeyboardFocus.InputKeyPressed(key, false);
}
}
return false;
}
private static void UpdateHoveredControl(Base inCanvas)
{
Base hovered = inCanvas.GetControlAt(MousePosition.X, MousePosition.Y);
if (hovered != HoveredControl)
{
if (HoveredControl != null)
{
var oldHover = HoveredControl;
HoveredControl = null;
oldHover.InputMouseLeft();
}
HoveredControl = hovered;
if (HoveredControl != null)
{
HoveredControl.InputMouseEntered();
}
}
if (MouseFocus != null && MouseFocus.GetCanvas() == inCanvas)
{
if (HoveredControl != null)
{
var oldHover = HoveredControl;
HoveredControl = null;
oldHover.Redraw();
}
HoveredControl = MouseFocus;
}
}
private static void FindKeyboardFocus(Base control)
{
if (null == control) return;
if (control.KeyboardInputEnabled)
{
//Make sure none of our children have keyboard focus first - todo recursive
if (control.Children.Any(child => child == KeyboardFocus))
{
return;
}
control.Focus();
return;
}
FindKeyboardFocus(control.Parent);
return;
}
}
}

Some files were not shown because too many files have changed in this diff Show more