From 10e057953edad5b3e437977e0c7b954d7214faad Mon Sep 17 00:00:00 2001 From: gered Date: Thu, 28 Mar 2013 18:47:01 -0400 Subject: [PATCH] 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. --- .gitignore | 2 + Gwen/Align.cs | 118 + Gwen/Anim/Animation.cs | 57 + Gwen/Anim/Size/Height.cs | 38 + Gwen/Anim/Size/Width.cs | 38 + Gwen/Anim/TimedAnimation.cs | 69 + Gwen/Control/Base.cs | 2126 ++++++++++++++++++ Gwen/Control/Button.cs | 318 +++ Gwen/Control/Canvas.cs | 298 +++ Gwen/Control/CheckBox.cs | 113 + Gwen/Control/CollapsibleCategory.cs | 176 ++ Gwen/Control/CollapsibleList.cs | 130 ++ Gwen/Control/ColorLerpBox.cs | 207 ++ Gwen/Control/ColorPicker.cs | 256 +++ Gwen/Control/ColorSlider.cs | 152 ++ Gwen/Control/ComboBox.cs | 238 ++ Gwen/Control/CrossSplitter.cs | 282 +++ Gwen/Control/DockBase.cs | 440 ++++ Gwen/Control/DockedTabControl.cs | 81 + Gwen/Control/GroupBox.cs | 74 + Gwen/Control/HSVColorPicker.cs | 198 ++ Gwen/Control/HorizontalScrollBar.cs | 203 ++ Gwen/Control/HorizontalSlider.cs | 63 + Gwen/Control/HorizontalSplitter.cs | 215 ++ Gwen/Control/IColorPicker.cs | 10 + Gwen/Control/ImagePanel.cs | 77 + Gwen/Control/Label.cs | 211 ++ Gwen/Control/LabelClickable.cs | 30 + Gwen/Control/LabeledCheckBox.cs | 95 + Gwen/Control/LabeledRadioButton.cs | 93 + Gwen/Control/Layout/Positioner.cs | 53 + Gwen/Control/Layout/Splitter.cs | 95 + Gwen/Control/Layout/Table.cs | 247 ++ Gwen/Control/Layout/TableRow.cs | 221 ++ Gwen/Control/ListBox.cs | 325 +++ Gwen/Control/ListBoxRow.cs | 66 + Gwen/Control/Menu.cs | 209 ++ Gwen/Control/MenuItem.cs | 255 +++ Gwen/Control/MenuStrip.cs | 78 + Gwen/Control/MessageBox.cs | 67 + Gwen/Control/NumericUpDown.cs | 152 ++ Gwen/Control/ProgressBar.cs | 72 + Gwen/Control/Properties.cs | 108 + Gwen/Control/Property/Base.cs | 55 + Gwen/Control/Property/Check.cs | 71 + Gwen/Control/Property/Color.cs | 126 ++ Gwen/Control/Property/Text.cs | 59 + Gwen/Control/PropertyRow.cs | 123 + Gwen/Control/PropertyTree.cs | 38 + Gwen/Control/RadioButton.cs | 39 + Gwen/Control/RadioButtonGroup.cs | 137 ++ Gwen/Control/ResizableControl.cs | 160 ++ Gwen/Control/RichLabel.cs | 254 +++ Gwen/Control/ScrollBar.cs | 133 ++ Gwen/Control/ScrollControl.cs | 301 +++ Gwen/Control/Slider.cs | 236 ++ Gwen/Control/StatusBar.cs | 44 + Gwen/Control/TabButton.cs | 203 ++ Gwen/Control/TabControl.cs | 250 ++ Gwen/Control/TabStrip.cs | 204 ++ Gwen/Control/TabTitleBar.cs | 41 + Gwen/Control/TextBox.cs | 607 +++++ Gwen/Control/TextBoxNumeric.cs | 85 + Gwen/Control/TextBoxPassword.cs | 41 + Gwen/Control/TreeControl.cs | 95 + Gwen/Control/TreeNode.cs | 326 +++ Gwen/Control/VerticalScrollBar.cs | 193 ++ Gwen/Control/VerticalSlider.cs | 63 + Gwen/Control/VerticalSplitter.cs | 221 ++ Gwen/Control/WindowControl.cs | 177 ++ Gwen/ControlInternal/CategoryButton.cs | 87 + Gwen/ControlInternal/CategoryHeaderButton.cs | 35 + Gwen/ControlInternal/CloseButton.cs | 33 + Gwen/ControlInternal/ColorButton.cs | 39 + Gwen/ControlInternal/ColorDisplay.cs | 45 + Gwen/ControlInternal/DownArrow.cs | 35 + Gwen/ControlInternal/Dragger.cs | 96 + Gwen/ControlInternal/Highlight.cs | 29 + Gwen/ControlInternal/MenuDivider.cs | 30 + Gwen/ControlInternal/Modal.cs | 42 + Gwen/ControlInternal/PropertyRowLabel.cs | 50 + Gwen/ControlInternal/PropertyTreeNode.cs | 30 + Gwen/ControlInternal/Resizer.cs | 154 ++ Gwen/ControlInternal/RightArrow.cs | 30 + Gwen/ControlInternal/ScrollBarBar.cs | 85 + Gwen/ControlInternal/ScrollBarButton.cs | 52 + Gwen/ControlInternal/SliderBar.cs | 38 + Gwen/ControlInternal/SplitterBar.cs | 41 + Gwen/ControlInternal/TabControlInner.cs | 28 + Gwen/ControlInternal/Text.cs | 210 ++ Gwen/ControlInternal/TreeNodeLabel.cs | 50 + Gwen/ControlInternal/TreeToggleButton.cs | 40 + Gwen/ControlInternal/UpDownButton_Down.cs | 30 + Gwen/ControlInternal/UpDownButton_Up.cs | 30 + Gwen/DragDrop/DragAndDrop.cs | 244 ++ Gwen/DragDrop/Package.cs | 15 + Gwen/Font.cs | 98 + Gwen/Gwen.csproj | 157 ++ Gwen/HSV.cs | 11 + Gwen/Input/InputHandler.cs | 410 ++++ Gwen/Input/KeyData.cs | 24 + Gwen/Key.cs | 29 + Gwen/Margin.cs | 70 + Gwen/Padding.cs | 65 + Gwen/Platform/Neutral.cs | 185 ++ Gwen/Platform/Windows.cs | 58 + Gwen/Pos.cs | 21 + Gwen/Properties/AssemblyInfo.cs | 37 + Gwen/Renderer/Base.cs | 439 ++++ Gwen/Renderer/ICacheToTexture.cs | 36 + Gwen/Skin/Base.cs | 269 +++ Gwen/Skin/Simple.cs | 705 ++++++ Gwen/Skin/SkinColors.cs | 118 + Gwen/Skin/TexturedBase.cs | 1183 ++++++++++ Gwen/Skin/Texturing/Bordered.cs | 125 + Gwen/Skin/Texturing/Single.cs | 66 + Gwen/Texture.cs | 96 + Gwen/ToolTip.cs | 74 + Gwen/Util.cs | 146 ++ Gwen/readme.txt | 1 + GwenCS.sln | 20 + 121 files changed, 18679 insertions(+) create mode 100644 .gitignore create mode 100644 Gwen/Align.cs create mode 100644 Gwen/Anim/Animation.cs create mode 100644 Gwen/Anim/Size/Height.cs create mode 100644 Gwen/Anim/Size/Width.cs create mode 100644 Gwen/Anim/TimedAnimation.cs create mode 100644 Gwen/Control/Base.cs create mode 100644 Gwen/Control/Button.cs create mode 100644 Gwen/Control/Canvas.cs create mode 100644 Gwen/Control/CheckBox.cs create mode 100644 Gwen/Control/CollapsibleCategory.cs create mode 100644 Gwen/Control/CollapsibleList.cs create mode 100644 Gwen/Control/ColorLerpBox.cs create mode 100644 Gwen/Control/ColorPicker.cs create mode 100644 Gwen/Control/ColorSlider.cs create mode 100644 Gwen/Control/ComboBox.cs create mode 100644 Gwen/Control/CrossSplitter.cs create mode 100644 Gwen/Control/DockBase.cs create mode 100644 Gwen/Control/DockedTabControl.cs create mode 100644 Gwen/Control/GroupBox.cs create mode 100644 Gwen/Control/HSVColorPicker.cs create mode 100644 Gwen/Control/HorizontalScrollBar.cs create mode 100644 Gwen/Control/HorizontalSlider.cs create mode 100644 Gwen/Control/HorizontalSplitter.cs create mode 100644 Gwen/Control/IColorPicker.cs create mode 100644 Gwen/Control/ImagePanel.cs create mode 100644 Gwen/Control/Label.cs create mode 100644 Gwen/Control/LabelClickable.cs create mode 100644 Gwen/Control/LabeledCheckBox.cs create mode 100644 Gwen/Control/LabeledRadioButton.cs create mode 100644 Gwen/Control/Layout/Positioner.cs create mode 100644 Gwen/Control/Layout/Splitter.cs create mode 100644 Gwen/Control/Layout/Table.cs create mode 100644 Gwen/Control/Layout/TableRow.cs create mode 100644 Gwen/Control/ListBox.cs create mode 100644 Gwen/Control/ListBoxRow.cs create mode 100644 Gwen/Control/Menu.cs create mode 100644 Gwen/Control/MenuItem.cs create mode 100644 Gwen/Control/MenuStrip.cs create mode 100644 Gwen/Control/MessageBox.cs create mode 100644 Gwen/Control/NumericUpDown.cs create mode 100644 Gwen/Control/ProgressBar.cs create mode 100644 Gwen/Control/Properties.cs create mode 100644 Gwen/Control/Property/Base.cs create mode 100644 Gwen/Control/Property/Check.cs create mode 100644 Gwen/Control/Property/Color.cs create mode 100644 Gwen/Control/Property/Text.cs create mode 100644 Gwen/Control/PropertyRow.cs create mode 100644 Gwen/Control/PropertyTree.cs create mode 100644 Gwen/Control/RadioButton.cs create mode 100644 Gwen/Control/RadioButtonGroup.cs create mode 100644 Gwen/Control/ResizableControl.cs create mode 100644 Gwen/Control/RichLabel.cs create mode 100644 Gwen/Control/ScrollBar.cs create mode 100644 Gwen/Control/ScrollControl.cs create mode 100644 Gwen/Control/Slider.cs create mode 100644 Gwen/Control/StatusBar.cs create mode 100644 Gwen/Control/TabButton.cs create mode 100644 Gwen/Control/TabControl.cs create mode 100644 Gwen/Control/TabStrip.cs create mode 100644 Gwen/Control/TabTitleBar.cs create mode 100644 Gwen/Control/TextBox.cs create mode 100644 Gwen/Control/TextBoxNumeric.cs create mode 100644 Gwen/Control/TextBoxPassword.cs create mode 100644 Gwen/Control/TreeControl.cs create mode 100644 Gwen/Control/TreeNode.cs create mode 100644 Gwen/Control/VerticalScrollBar.cs create mode 100644 Gwen/Control/VerticalSlider.cs create mode 100644 Gwen/Control/VerticalSplitter.cs create mode 100644 Gwen/Control/WindowControl.cs create mode 100644 Gwen/ControlInternal/CategoryButton.cs create mode 100644 Gwen/ControlInternal/CategoryHeaderButton.cs create mode 100644 Gwen/ControlInternal/CloseButton.cs create mode 100644 Gwen/ControlInternal/ColorButton.cs create mode 100644 Gwen/ControlInternal/ColorDisplay.cs create mode 100644 Gwen/ControlInternal/DownArrow.cs create mode 100644 Gwen/ControlInternal/Dragger.cs create mode 100644 Gwen/ControlInternal/Highlight.cs create mode 100644 Gwen/ControlInternal/MenuDivider.cs create mode 100644 Gwen/ControlInternal/Modal.cs create mode 100644 Gwen/ControlInternal/PropertyRowLabel.cs create mode 100644 Gwen/ControlInternal/PropertyTreeNode.cs create mode 100644 Gwen/ControlInternal/Resizer.cs create mode 100644 Gwen/ControlInternal/RightArrow.cs create mode 100644 Gwen/ControlInternal/ScrollBarBar.cs create mode 100644 Gwen/ControlInternal/ScrollBarButton.cs create mode 100644 Gwen/ControlInternal/SliderBar.cs create mode 100644 Gwen/ControlInternal/SplitterBar.cs create mode 100644 Gwen/ControlInternal/TabControlInner.cs create mode 100644 Gwen/ControlInternal/Text.cs create mode 100644 Gwen/ControlInternal/TreeNodeLabel.cs create mode 100644 Gwen/ControlInternal/TreeToggleButton.cs create mode 100644 Gwen/ControlInternal/UpDownButton_Down.cs create mode 100644 Gwen/ControlInternal/UpDownButton_Up.cs create mode 100644 Gwen/DragDrop/DragAndDrop.cs create mode 100644 Gwen/DragDrop/Package.cs create mode 100644 Gwen/Font.cs create mode 100644 Gwen/Gwen.csproj create mode 100644 Gwen/HSV.cs create mode 100644 Gwen/Input/InputHandler.cs create mode 100644 Gwen/Input/KeyData.cs create mode 100644 Gwen/Key.cs create mode 100644 Gwen/Margin.cs create mode 100644 Gwen/Padding.cs create mode 100644 Gwen/Platform/Neutral.cs create mode 100644 Gwen/Platform/Windows.cs create mode 100644 Gwen/Pos.cs create mode 100644 Gwen/Properties/AssemblyInfo.cs create mode 100644 Gwen/Renderer/Base.cs create mode 100644 Gwen/Renderer/ICacheToTexture.cs create mode 100644 Gwen/Skin/Base.cs create mode 100644 Gwen/Skin/Simple.cs create mode 100644 Gwen/Skin/SkinColors.cs create mode 100644 Gwen/Skin/TexturedBase.cs create mode 100644 Gwen/Skin/Texturing/Bordered.cs create mode 100644 Gwen/Skin/Texturing/Single.cs create mode 100644 Gwen/Texture.cs create mode 100644 Gwen/ToolTip.cs create mode 100644 Gwen/Util.cs create mode 100644 Gwen/readme.txt create mode 100644 GwenCS.sln diff --git a/.gitignore b/.gitignore new file mode 100644 index 0000000..b4665aa --- /dev/null +++ b/.gitignore @@ -0,0 +1,2 @@ +GwenCS.userprefs +Gwen/bin diff --git a/Gwen/Align.cs b/Gwen/Align.cs new file mode 100644 index 0000000..35677bb --- /dev/null +++ b/Gwen/Align.cs @@ -0,0 +1,118 @@ +using System; +using Gwen.Control; + +namespace Gwen +{ + /// + /// Utility class for manipulating control's position according to its parent. Rarely needed, use control.Dock. + /// + public static class Align + { + /// + /// Centers the control inside its parent. + /// + /// Control to center. + 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); + } + + /// + /// Moves the control to the left of its parent. + /// + /// + public static void AlignLeft(Base control) + { + Base parent = control.Parent; + if (null == parent) return; + + control.SetPosition(parent.Padding.Left, control.Y); + } + + /// + /// Centers the control horizontally inside its parent. + /// + /// + 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); + } + + /// + /// Moves the control to the right of its parent. + /// + /// + 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); + } + + /// + /// Moves the control to the top of its parent. + /// + /// + public static void AlignTop(Base control) + { + control.SetPosition(control.X, 0); + } + + /// + /// Centers the control vertically inside its parent. + /// + /// + public static void CenterVertically(Base control) + { + Base parent = control.Parent; + if (null == parent) return; + + control.SetPosition(control.X, (parent.Height - control.Height) / 2); + } + + /// + /// Moves the control to the bottom of its parent. + /// + /// + public static void AlignBottom(Base control) + { + Base parent = control.Parent; + if (null == parent) return; + + control.SetPosition(control.X, parent.Height - control.Height); + } + + /// + /// Places the control below other control (left aligned), taking margins into account. + /// + /// Control to place. + /// Anchor control. + /// Optional spacing. + public static void PlaceDownLeft(Base control, Base anchor, int spacing = 0) + { + control.SetPosition(anchor.X, anchor.Bottom + spacing); + } + + /// + /// Places the control to the right of other control (bottom aligned), taking margins into account. + /// + /// Control to place. + /// Anchor control. + /// Optional spacing. + public static void PlaceRightBottom(Base control, Base anchor, int spacing = 0) + { + control.SetPosition(anchor.Right + spacing, anchor.Y - control.Height + anchor.Height); + } + } +} diff --git a/Gwen/Anim/Animation.cs b/Gwen/Anim/Animation.cs new file mode 100644 index 0000000..2d2c6aa --- /dev/null +++ b/Gwen/Anim/Animation.cs @@ -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 g_AnimationsListed = new List(); // unused + private static readonly Dictionary> m_Animations = new Dictionary>(); + + 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(); + 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> 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); + } + } + } + } + } +} diff --git a/Gwen/Anim/Size/Height.cs b/Gwen/Anim/Size/Height.cs new file mode 100644 index 0000000..75263f3 --- /dev/null +++ b/Gwen/Anim/Size/Height.cs @@ -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; + } + } +} diff --git a/Gwen/Anim/Size/Width.cs b/Gwen/Anim/Size/Width.cs new file mode 100644 index 0000000..2627642 --- /dev/null +++ b/Gwen/Anim/Size/Width.cs @@ -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; + } + } +} diff --git a/Gwen/Anim/TimedAnimation.cs b/Gwen/Anim/TimedAnimation.cs new file mode 100644 index 0000000..ed5618d --- /dev/null +++ b/Gwen/Anim/TimedAnimation.cs @@ -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() + { } + } +} diff --git a/Gwen/Control/Base.cs b/Gwen/Control/Base.cs new file mode 100644 index 0000000..84901b0 --- /dev/null +++ b/Gwen/Control/Base.cs @@ -0,0 +1,2126 @@ +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Linq; +using System.Drawing; +//using System.Windows.Forms; +using Gwen.Anim; +using Gwen.DragDrop; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Base control class. + /// + public class Base : IDisposable + { + // this REALLY needs to be replaced with control-specific events + /// + /// Delegate used for all control event handlers. + /// + /// Event source. + public delegate void GwenEventHandler(Base control); + + private bool m_Disposed; + + private Base m_Parent; + + /// + /// This is the panel's actual parent - most likely the logical + /// parent's InnerPanel (if it has one). You should rarely need this. + /// + private Base m_ActualParent; + + /// + /// If the innerpanel exists our children will automatically become children of that + /// instead of us - allowing us to move them all around by moving that panel (useful for scrolling etc). + /// + protected Base m_InnerPanel; + + private Base m_ToolTip; + + private Skin.Base m_Skin; + + private Rectangle m_Bounds; + private Rectangle m_RenderBounds; + private Rectangle m_InnerBounds; + private Padding m_Padding; + private Margin m_Margin; + + private String m_Name; + + private bool m_RestrictToParent; + private bool m_Disabled; + private bool m_Hidden; + private bool m_MouseInputEnabled; + private bool m_KeyboardInputEnabled; + private bool m_DrawBackground; + + private Pos m_Dock; + + //private Cursor m_Cursor; + + private bool m_Tabable; + + private bool m_NeedsLayout; + private bool m_CacheTextureDirty; + private bool m_CacheToTexture; + + private Package m_DragAndDrop_Package; + + private object m_UserData; + + private bool m_DrawDebugOutlines; + + /// + /// Real list of children. + /// + private readonly List m_Children; + + /// + /// Invoked when mouse pointer enters the control. + /// + public event GwenEventHandler HoverEnter; + + /// + /// Invoked when mouse pointer leaves the control. + /// + public event GwenEventHandler HoverLeave; + + /// + /// Invoked when control's bounds have been changed. + /// + public event GwenEventHandler BoundsChanged; + + /// + /// Accelerator map. + /// + private readonly Dictionary m_Accelerators; + + public const int MaxCoord = 4096; // added here from various places in code + + /// + /// Logical list of children. If InnerPanel is not null, returns InnerPanel's children. + /// + public List Children + { + get + { + if (m_InnerPanel != null) + return m_InnerPanel.Children; + return m_Children; + } + } + + /// + /// The logical parent. It's usually what you expect, the control you've parented it to. + /// + public Base Parent + { + get { return m_Parent; } + set + { + if (m_Parent == value) + return; + + if (m_Parent != null) + { + m_Parent.RemoveChild(this, false); + } + + m_Parent = value; + m_ActualParent = null; + + if (m_Parent != null) + { + m_Parent.AddChild(this); + } + } + } + + // todo: ParentChanged event? + + /// + /// Dock position. + /// + public Pos Dock + { + get { return m_Dock; } + set + { + if (m_Dock == value) + return; + + m_Dock = value; + + Invalidate(); + InvalidateParent(); + } + } + + /// + /// Current skin. + /// + public Skin.Base Skin + { + get + { + if (m_Skin != null) + return m_Skin; + if (m_Parent != null) + return m_Parent.Skin; + + throw new InvalidOperationException("GetSkin: null"); + } + } + + /// + /// Current tooltip. + /// + public Base ToolTip + { + get { return m_ToolTip; } + set + { + m_ToolTip = value; + if (m_ToolTip != null) + { + m_ToolTip.Parent = this; + m_ToolTip.IsHidden = true; + } + } + } + + /// + /// Indicates whether this control is a menu component. + /// + internal virtual bool IsMenuComponent + { + get + { + if (m_Parent == null) + return false; + return m_Parent.IsMenuComponent; + } + } + + /// + /// Determines whether the control should be clipped to its bounds while rendering. + /// + protected virtual bool ShouldClip { get { return true; } } + + /// + /// Current padding - inner spacing. + /// + public Padding Padding + { + get { return m_Padding; } + set + { + if (m_Padding == value) + return; + + m_Padding = value; + Invalidate(); + InvalidateParent(); + } + } + + /// + /// Current margin - outer spacing. + /// + public Margin Margin + { + get { return m_Margin; } + set + { + if (m_Margin == value) + return; + + m_Margin = value; + Invalidate(); + InvalidateParent(); + } + } + + /// + /// Indicates whether the control is on top of its parent's children. + /// + public virtual bool IsOnTop { get { return this == Parent.m_Children.First(); } } // todo: validate + + /// + /// User data associated with the control. + /// + public object UserData { get { return m_UserData; } set { m_UserData = value; } } + + /// + /// Indicates whether the control is hovered by mouse pointer. + /// + public virtual bool IsHovered { get { return InputHandler.HoveredControl == this; } } + + /// + /// Indicates whether the control has focus. + /// + public bool HasFocus { get { return InputHandler.KeyboardFocus == this; } } + + /// + /// Indicates whether the control is disabled. + /// + public bool IsDisabled { get { return m_Disabled; } set { m_Disabled = value; } } + + /// + /// Indicates whether the control is hidden. + /// + public virtual bool IsHidden { get { return m_Hidden; } set { if (value == m_Hidden) return; m_Hidden = value; Invalidate(); } } + + /// + /// Determines whether the control's position should be restricted to parent's bounds. + /// + public bool RestrictToParent { get { return m_RestrictToParent; } set { m_RestrictToParent = value; } } + + /// + /// Determines whether the control receives mouse input events. + /// + public bool MouseInputEnabled { get { return m_MouseInputEnabled; } set { m_MouseInputEnabled = value; } } + + /// + /// Determines whether the control receives keyboard input events. + /// + public bool KeyboardInputEnabled { get { return m_KeyboardInputEnabled; } set { m_KeyboardInputEnabled = value; } } + + /* + /// + /// Gets or sets the mouse cursor when the cursor is hovering the control. + /// + public Cursor Cursor { get { return m_Cursor; } set { m_Cursor = value; } } + */ + + /// + /// Indicates whether the control is tabable (can be focused by pressing Tab). + /// + public bool IsTabable { get { return m_Tabable; } set { m_Tabable = value; } } + + /// + /// Indicates whether control's background should be drawn during rendering. + /// + public bool ShouldDrawBackground { get { return m_DrawBackground; } set { m_DrawBackground = value; } } + + /// + /// Indicates whether the renderer should cache drawing to a texture to improve performance (at the cost of memory). + /// + public bool ShouldCacheToTexture { get { return m_CacheToTexture; } set { m_CacheToTexture = value; /*Children.ForEach(x => x.ShouldCacheToTexture=value);*/ } } + + /// + /// Gets or sets the control's internal name. + /// + public String Name { get { return m_Name; } set { m_Name = value; } } + + /// + /// Control's size and position relative to the parent. + /// + public Rectangle Bounds { get { return m_Bounds; } } + + /// + /// Bounds for the renderer. + /// + public Rectangle RenderBounds { get { return m_RenderBounds; } } + + /// + /// Bounds adjusted by padding. + /// + public Rectangle InnerBounds { get { return m_InnerBounds; } } + + /// + /// Size restriction. + /// + public Point MinimumSize { get { return m_MinimumSize; } set { m_MinimumSize = value; } } + + /// + /// Size restriction. + /// + public Point MaximumSize { get { return m_MaximumSize; } set { m_MaximumSize = value; } } + + private Point m_MinimumSize = new Point(1, 1); + private Point m_MaximumSize = new Point(MaxCoord, MaxCoord); + + /// + /// Determines whether hover should be drawn during rendering. + /// + protected bool ShouldDrawHover { get { return InputHandler.MouseFocus == this || InputHandler.MouseFocus == null; } } + + protected virtual bool AccelOnlyFocus { get { return false; } } + protected virtual bool NeedsInputChars { get { return false; } } + + /// + /// Indicates whether the control and its parents are visible. + /// + public bool IsVisible + { + get + { + if (IsHidden) + return false; + + if (Parent != null) + return Parent.IsVisible; + + return true; + } + } + + /// + /// Leftmost coordinate of the control. + /// + public int X { get { return m_Bounds.X; } set { SetPosition(value, Y); } } + + /// + /// Topmost coordinate of the control. + /// + public int Y { get { return m_Bounds.Y; } set { SetPosition(X, value); } } + + // todo: Bottom/Right includes margin but X/Y not? + + public int Width { get { return m_Bounds.Width; } set { SetSize(value, Height); } } + public int Height { get { return m_Bounds.Height; } set { SetSize(Width, value); } } + public int Bottom { get { return m_Bounds.Bottom + m_Margin.Bottom; } } + public int Right { get { return m_Bounds.Right + m_Margin.Right; } } + + /// + /// Determines whether margin, padding and bounds outlines for the control will be drawn. Applied recursively to all children. + /// + public bool DrawDebugOutlines + { + get { return m_DrawDebugOutlines; } + set + { + if (m_DrawDebugOutlines == value) + return; + m_DrawDebugOutlines = value; + foreach (Base child in Children) + { + child.DrawDebugOutlines = value; + } + } + } + + public Color PaddingOutlineColor { get; set; } + public Color MarginOutlineColor { get; set; } + public Color BoundsOutlineColor { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Base(Base parent = null) + { + m_Children = new List(); + m_Accelerators = new Dictionary(); + + Parent = parent; + + m_Hidden = false; + m_Bounds = new Rectangle(0, 0, 10, 10); + m_Padding = Padding.Zero; + m_Margin = Margin.Zero; + + RestrictToParent = false; + + MouseInputEnabled = true; + KeyboardInputEnabled = false; + + Invalidate(); + //Cursor = Cursors.Default; + //ToolTip = null; + IsTabable = false; + ShouldDrawBackground = true; + m_Disabled = false; + m_CacheTextureDirty = true; + m_CacheToTexture = false; + + BoundsOutlineColor = Color.Red; + MarginOutlineColor = Color.Green; + PaddingOutlineColor = Color.Blue; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + //Debug.Print("Control.Base: Disposing {0} {1:X}", this, GetHashCode()); + if (m_Disposed) + { +#if DEBUG + throw new ObjectDisposedException(String.Format("Control.Base [{1:X}] disposed twice: {0}", this, GetHashCode())); +#else + return; +#endif + } + + if (InputHandler.HoveredControl == this) + InputHandler.HoveredControl = null; + if (InputHandler.KeyboardFocus == this) + InputHandler.KeyboardFocus = null; + if (InputHandler.MouseFocus == this) + InputHandler.MouseFocus = null; + + DragAndDrop.ControlDeleted(this); + Gwen.ToolTip.ControlDeleted(this); + Animation.Cancel(this); + + foreach (Base child in m_Children) + child.Dispose(); + + m_Children.Clear(); + + m_Disposed = true; + GC.SuppressFinalize(this); + } + +#if DEBUG + ~Base() + { + throw new InvalidOperationException(String.Format("IDisposable object finalized [{1:X}]: {0}", this, GetHashCode())); + //Debug.Print(String.Format("IDisposable object finalized: {0}", GetType())); + } +#endif + + /// + /// Detaches the control from canvas and adds to the deletion queue (processed in Canvas.DoThink). + /// + public void DelayedDelete() + { + GetCanvas().AddDelayedDelete(this); + } + + public override string ToString() + { + if (this is MenuItem) + return "[MenuItem: " + (this as MenuItem).Text + "]"; + if (this is Label) + return "[Label: " + (this as Label).Text + "]"; + if (this is ControlInternal.Text) + return "[Text: " + (this as ControlInternal.Text).String + "]"; + return GetType().ToString(); + } + + /// + /// Gets the canvas (root parent) of the control. + /// + /// + public virtual Canvas GetCanvas() + { + Base canvas = m_Parent; + if (canvas == null) + return null; + + return canvas.GetCanvas(); + } + + /// + /// Enables the control. + /// + public void Enable() + { + IsDisabled = false; + } + + /// + /// Disables the control. + /// + public void Disable() + { + IsDisabled = true; + } + + /// + /// Default accelerator handler. + /// + /// Event source. + private void DefaultAcceleratorHandler(Base control) + { + OnAccelerator(); + } + + /// + /// Default accelerator handler. + /// + protected virtual void OnAccelerator() + { + + } + + /// + /// Hides the control. + /// + public virtual void Hide() + { + IsHidden = true; + } + + /// + /// Shows the control. + /// + public virtual void Show() + { + IsHidden = false; + } + + /// + /// Creates a tooltip for the control. + /// + /// Tooltip text. + public virtual void SetToolTipText(String text) + { + Label tooltip = new Label(this); + tooltip.AutoSizeToContents = true; + tooltip.Text = text; + tooltip.TextColorOverride = Skin.Colors.TooltipText; + tooltip.Padding = new Padding(5, 3, 5, 3); + tooltip.SizeToContents(); + + ToolTip = tooltip; + } + + /// + /// Invalidates the control's children (relayout/repaint). + /// + /// Determines whether the operation should be carried recursively. + protected virtual void InvalidateChildren(bool recursive = false) + { + foreach (Base child in m_Children) + { + child.Invalidate(); + if (recursive) + child.InvalidateChildren(true); + } + + if (m_InnerPanel != null) + { + foreach (Base child in m_InnerPanel.m_Children) + { + child.Invalidate(); + if (recursive) + child.InvalidateChildren(true); + } + } + } + + /// + /// Invalidates the control. + /// + /// + /// Causes layout, repaint, invalidates cached texture. + /// + public virtual void Invalidate() + { + m_NeedsLayout = true; + m_CacheTextureDirty = true; + } + + /// + /// Sends the control to the bottom of paren't visibility stack. + /// + public virtual void SendToBack() + { + if (m_ActualParent == null) + return; + if (m_ActualParent.m_Children.Count == 0) + return; + if (m_ActualParent.m_Children.First() == this) + return; + + m_ActualParent.m_Children.Remove(this); + m_ActualParent.m_Children.Insert(0, this); + + InvalidateParent(); + } + + /// + /// Brings the control to the top of paren't visibility stack. + /// + public virtual void BringToFront() + { + if (m_ActualParent == null) + return; + if (m_ActualParent.m_Children.Last() == this) + return; + + m_ActualParent.m_Children.Remove(this); + m_ActualParent.m_Children.Add(this); + InvalidateParent(); + Redraw(); + } + + public virtual void BringNextToControl(Base child, bool behind) + { + if (null == m_ActualParent) + return; + + m_ActualParent.m_Children.Remove(this); + + // todo: validate + int idx = m_ActualParent.m_Children.IndexOf(child); + if (idx == m_ActualParent.m_Children.Count - 1) + { + BringToFront(); + return; + } + + if (behind) + { + ++idx; + + if (idx == m_ActualParent.m_Children.Count - 1) + { + BringToFront(); + return; + } + } + + m_ActualParent.m_Children.Insert(idx, this); + InvalidateParent(); + } + + /// + /// Finds a child by name. + /// + /// Child name. + /// Determines whether the search should be recursive. + /// Found control or null. + public virtual Base FindChildByName(String name, bool recursive = false) + { + Base b = m_Children.Find(x => x.m_Name == name); + if (b != null) + return b; + + if (recursive) + { + foreach (Base child in m_Children) + { + b = child.FindChildByName(name, true); + if (b != null) + return b; + } + } + return null; + } + + /// + /// Attaches specified control as a child of this one. + /// + /// + /// If InnerPanel is not null, it will become the parent. + /// + /// Control to be added as a child. + public virtual void AddChild(Base child) + { + if (m_InnerPanel != null) + { + m_InnerPanel.AddChild(child); + return; + } + + m_Children.Add(child); + OnChildAdded(child); + + child.m_ActualParent = this; + } + + /// + /// Detaches specified control from this one. + /// + /// Child to be removed. + /// Determines whether the child should be disposed (added to delayed delete queue). + public virtual void RemoveChild(Base child, bool dispose) + { + // If we removed our innerpanel + // remove our pointer to it + if (m_InnerPanel == child) + { + m_Children.Remove(m_InnerPanel); + m_InnerPanel.DelayedDelete(); + m_InnerPanel = null; + return; + } + + if (m_InnerPanel != null && m_InnerPanel.Children.Contains(child)) + { + m_InnerPanel.RemoveChild(child, dispose); + return; + } + + m_Children.Remove(child); + OnChildRemoved(child); + + if (dispose) + child.DelayedDelete(); + } + + /// + /// Removes all children (and disposes them). + /// + public virtual void DeleteAllChildren() + { + // todo: probably shouldn't invalidate after each removal + while (m_Children.Count > 0) + RemoveChild(m_Children[0], true); + } + + /// + /// Handler invoked when a child is added. + /// + /// Child added. + protected virtual void OnChildAdded(Base child) + { + Invalidate(); + } + + /// + /// Handler invoked when a child is removed. + /// + /// Child removed. + protected virtual void OnChildRemoved(Base child) + { + Invalidate(); + } + + /// + /// Moves the control by a specific amount. + /// + /// X-axis movement. + /// Y-axis movement. + public virtual void MoveBy(int x, int y) + { + SetBounds(X + x, Y + y, Width, Height); + } + + /// + /// Moves the control to a specific point. + /// + /// Target x coordinate. + /// Target y coordinate. + public virtual void MoveTo(float x, float y) + { + MoveTo((int)x, (int)y); + } + + /// + /// Moves the control to a specific point, clamping on paren't bounds if RestrictToParent is set. + /// + /// Target x coordinate. + /// Target y coordinate. + public virtual void MoveTo(int x, int y) + { + if (RestrictToParent && (Parent != null)) + { + Base parent = Parent; + if (x - Padding.Left < parent.Margin.Left) + x = parent.Margin.Left + Padding.Left; + if (y - Padding.Top < parent.Margin.Top) + y = parent.Margin.Top + Padding.Top; + if (x + Width + Padding.Right > parent.Width - parent.Margin.Right) + x = parent.Width - parent.Margin.Right - Width - Padding.Right; + if (y + Height + Padding.Bottom > parent.Height - parent.Margin.Bottom) + y = parent.Height - parent.Margin.Bottom - Height - Padding.Bottom; + } + + SetBounds(x, y, Width, Height); + } + + /// + /// Sets the control position. + /// + /// Target x coordinate. + /// Target y coordinate. + public virtual void SetPosition(float x, float y) + { + SetPosition((int)x, (int)y); + } + + /// + /// Sets the control position. + /// + /// Target x coordinate. + /// Target y coordinate. + public virtual void SetPosition(int x, int y) + { + SetBounds(x, y, Width, Height); + } + + /// + /// Sets the control size. + /// + /// New width. + /// New height. + /// True if bounds changed. + public virtual bool SetSize(int width, int height) + { + return SetBounds(X, Y, width, height); + } + + /// + /// Sets the control bounds. + /// + /// New bounds. + /// True if bounds changed. + public virtual bool SetBounds(Rectangle bounds) + { + return SetBounds(bounds.X, bounds.Y, bounds.Width, bounds.Height); + } + + /// + /// Sets the control bounds. + /// + /// X. + /// Y. + /// Width. + /// Height. + /// + /// True if bounds changed. + /// + public virtual bool SetBounds(float x, float y, float width, float height) + { + return SetBounds((int)x, (int)y, (int)width, (int)height); + } + + /// + /// Sets the control bounds. + /// + /// X position. + /// Y position. + /// Width. + /// Height. + /// + /// True if bounds changed. + /// + public virtual bool SetBounds(int x, int y, int width, int height) + { + if (m_Bounds.X == x && + m_Bounds.Y == y && + m_Bounds.Width == width && + m_Bounds.Height == height) + return false; + + Rectangle oldBounds = Bounds; + + m_Bounds.X = x; + m_Bounds.Y = y; + + m_Bounds.Width = width; + m_Bounds.Height = height; + + OnBoundsChanged(oldBounds); + + if (BoundsChanged != null) + BoundsChanged.Invoke(this); + + return true; + } + + /// + /// Positions the control inside its parent. + /// + /// Target position. + /// X padding. + /// Y padding. + public virtual void Position(Pos pos, int xpadding = 0, int ypadding = 0) // todo: a bit ambiguous name + { + int w = Parent.Width; + int h = Parent.Height; + Padding padding = Parent.Padding; + + int x = X; + int y = Y; + if (0 != (pos & Pos.Left)) x = padding.Left + xpadding; + if (0 != (pos & Pos.Right)) x = w - Width - padding.Right - xpadding; + if (0 != (pos & Pos.CenterH)) + x = (int)(padding.Left + xpadding + (w - Width - padding.Left - padding.Right) * 0.5f); + + if (0 != (pos & Pos.Top)) y = padding.Top + ypadding; + if (0 != (pos & Pos.Bottom)) y = h - Height - padding.Bottom - ypadding; + if (0 != (pos & Pos.CenterV)) + y = (int)(padding.Top + ypadding + (h - Height - padding.Bottom - padding.Top) * 0.5f); + + SetPosition(x, y); + } + + /// + /// Handler invoked when control's bounds change. + /// + /// Old bounds. + protected virtual void OnBoundsChanged(Rectangle oldBounds) + { + //Anything that needs to update on size changes + //Iterate my children and tell them I've changed + // + if (Parent != null) + Parent.OnChildBoundsChanged(oldBounds, this); + + + if (m_Bounds.Width != oldBounds.Width || m_Bounds.Height != oldBounds.Height) + { + Invalidate(); + } + + Redraw(); + UpdateRenderBounds(); + } + + /// + /// Handler invoked when control's scale changes. + /// + protected virtual void OnScaleChanged() + { + foreach (Base child in m_Children) + { + child.OnScaleChanged(); + } + } + + /// + /// Handler invoked when control children's bounds change. + /// + protected virtual void OnChildBoundsChanged(Rectangle oldChildBounds, Base child) + { + + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected virtual void Render(Skin.Base skin) + { + } + + /// + /// Renders the control to a cache using specified skin. + /// + /// Skin to use. + /// Root parent. + protected virtual void DoCacheRender(Skin.Base skin, Base master) + { + Renderer.Base render = skin.Renderer; + Renderer.ICacheToTexture cache = render.CTT; + + if (cache == null) + return; + + Point oldRenderOffset = render.RenderOffset; + Rectangle oldRegion = render.ClipRegion; + + if (this != master) + { + render.AddRenderOffset(Bounds); + render.AddClipRegion(Bounds); + } + else + { + render.RenderOffset = Point.Empty; + render.ClipRegion = new Rectangle(0, 0, Width, Height); + } + + if (m_CacheTextureDirty && render.ClipRegionVisible) + { + render.StartClip(); + + if (ShouldCacheToTexture) + cache.SetupCacheTexture(this); + + //Render myself first + //var old = render.ClipRegion; + //render.ClipRegion = Bounds; + //var old = render.RenderOffset; + //render.RenderOffset = new Point(Bounds.X, Bounds.Y); + Render(skin); + //render.RenderOffset = old; + //render.ClipRegion = old; + + if (m_Children.Count > 0) + { + //Now render my kids + foreach (Base child in m_Children) + { + if (child.IsHidden) + continue; + child.DoCacheRender(skin, master); + } + } + + if (ShouldCacheToTexture) + { + cache.FinishCacheTexture(this); + m_CacheTextureDirty = false; + } + } + + render.ClipRegion = oldRegion; + render.StartClip(); + render.RenderOffset = oldRenderOffset; + + if (ShouldCacheToTexture) + cache.DrawCachedControlTexture(this); + } + + /// + /// Rendering logic implementation. + /// + /// Skin to use. + internal virtual void DoRender(Skin.Base skin) + { + // If this control has a different skin, + // then so does its children. + if (m_Skin != null) + skin = m_Skin; + + // Do think + Think(); + + Renderer.Base render = skin.Renderer; + + if (render.CTT != null && ShouldCacheToTexture) + { + DoCacheRender(skin, this); + return; + } + + RenderRecursive(skin, Bounds); + + if (DrawDebugOutlines) + skin.DrawDebugOutlines(this); + } + + /// + /// Recursive rendering logic. + /// + /// Skin to use. + /// Clipping rectangle. + protected virtual void RenderRecursive(Skin.Base skin, Rectangle clipRect) + { + Renderer.Base render = skin.Renderer; + Point oldRenderOffset = render.RenderOffset; + + render.AddRenderOffset(clipRect); + + RenderUnder(skin); + + Rectangle oldRegion = render.ClipRegion; + + if (ShouldClip) + { + render.AddClipRegion(clipRect); + + if (!render.ClipRegionVisible) + { + render.RenderOffset = oldRenderOffset; + render.ClipRegion = oldRegion; + return; + } + + render.StartClip(); + } + + //Render myself first + Render(skin); + + if (m_Children.Count > 0) + { + //Now render my kids + foreach (Base child in m_Children) + { + if (child.IsHidden) + continue; + child.DoRender(skin); + } + } + + render.ClipRegion = oldRegion; + render.StartClip(); + RenderOver(skin); + + RenderFocus(skin); + + render.RenderOffset = oldRenderOffset; + } + + /// + /// Sets the control's skin. + /// + /// New skin. + /// Deterines whether to change children skin. + public virtual void SetSkin(Skin.Base skin, bool doChildren = false) + { + if (m_Skin == skin) + return; + m_Skin = skin; + Invalidate(); + Redraw(); + OnSkinChanged(skin); + + if (doChildren) + { + foreach (Base child in m_Children) + { + child.SetSkin(skin, true); + } + } + } + + /// + /// Handler invoked when control's skin changes. + /// + /// New skin. + protected virtual void OnSkinChanged(Skin.Base newSkin) + { + + } + + /// + /// Handler invoked on mouse wheel event. + /// + /// Scroll delta. + protected virtual bool OnMouseWheeled(int delta) + { + if (m_ActualParent != null) + return m_ActualParent.OnMouseWheeled(delta); + + return false; + } + + /// + /// Invokes mouse wheeled event (used by input system). + /// + internal bool InputMouseWheeled(int delta) + { + return OnMouseWheeled(delta); + } + + /// + /// Handler invoked on mouse moved event. + /// + /// X coordinate. + /// Y coordinate. + /// X change. + /// Y change. + protected virtual void OnMouseMoved(int x, int y, int dx, int dy) + { + + } + + /// + /// Invokes mouse moved event (used by input system). + /// + internal void InputMouseMoved(int x, int y, int dx, int dy) + { + OnMouseMoved(x, y, dx, dy); + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected virtual void OnMouseClickedLeft(int x, int y, bool down) + { + + } + + /// + /// Invokes left mouse click event (used by input system). + /// + internal void InputMouseClickedLeft(int x, int y, bool down) + { + OnMouseClickedLeft(x, y, down); + } + + /// + /// Handler invoked on mouse click (right) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected virtual void OnMouseClickedRight(int x, int y, bool down) + { + + } + + /// + /// Invokes right mouse click event (used by input system). + /// + internal void InputMouseClickedRight(int x, int y, bool down) + { + OnMouseClickedRight(x, y, down); + } + + /// + /// Handler invoked on mouse double click (left) event. + /// + /// X coordinate. + /// Y coordinate. + protected virtual void OnMouseDoubleClickedLeft(int x, int y) + { + OnMouseClickedLeft(x, y, true); // [omeg] should this be called? + } + + /// + /// Invokes left double mouse click event (used by input system). + /// + internal void InputMouseDoubleClickedLeft(int x, int y) + { + OnMouseDoubleClickedLeft(x, y); + } + + /// + /// Handler invoked on mouse double click (right) event. + /// + /// X coordinate. + /// Y coordinate. + protected virtual void OnMouseDoubleClickedRight(int x, int y) + { + OnMouseClickedRight(x, y, true); // [omeg] should this be called? + } + + /// + /// Invokes right double mouse click event (used by input system). + /// + internal void InputMouseDoubleClickedRight(int x, int y) + { + OnMouseDoubleClickedRight(x, y); + } + + /// + /// Handler invoked on mouse cursor entering control's bounds. + /// + protected virtual void OnMouseEntered() + { + if (HoverEnter != null) + HoverEnter.Invoke(this); + + if (ToolTip != null) + Gwen.ToolTip.Enable(this); + else if (Parent != null && Parent.ToolTip != null) + Gwen.ToolTip.Enable(Parent); + + Redraw(); + } + + /// + /// Invokes mouse enter event (used by input system). + /// + internal void InputMouseEntered() + { + OnMouseEntered(); + } + + /// + /// Handler invoked on mouse cursor leaving control's bounds. + /// + protected virtual void OnMouseLeft() + { + if (HoverLeave != null) + HoverLeave.Invoke(this); + + if (ToolTip != null) + Gwen.ToolTip.Disable(this); + + Redraw(); + } + + /// + /// Invokes mouse leave event (used by input system). + /// + internal void InputMouseLeft() + { + OnMouseLeft(); + } + + /// + /// Focuses the control. + /// + public virtual void Focus() + { + if (InputHandler.KeyboardFocus == this) + return; + + if (InputHandler.KeyboardFocus != null) + InputHandler.KeyboardFocus.OnLostKeyboardFocus(); + + InputHandler.KeyboardFocus = this; + OnKeyboardFocus(); + Redraw(); + } + + /// + /// Unfocuses the control. + /// + public virtual void Blur() + { + if (InputHandler.KeyboardFocus != this) + return; + + InputHandler.KeyboardFocus = null; + OnLostKeyboardFocus(); + Redraw(); + } + + /// + /// Control has been clicked - invoked by input system. Windows use it to propagate activation. + /// + public virtual void Touch() + { + if (Parent != null) + Parent.OnChildTouched(this); + } + + protected virtual void OnChildTouched(Base control) + { + Touch(); + } + + /// + /// Gets a child by its coordinates. + /// + /// Child X. + /// Child Y. + /// Control or null if not found. + public virtual Base GetControlAt(int x, int y) + { + if (IsHidden) + return null; + + if (x < 0 || y < 0 || x >= Width || y >= Height) + return null; + + // todo: convert to linq FindLast + var rev = ((IList)m_Children).Reverse(); // IList.Reverse creates new list, List.Reverse works in place.. go figure + foreach (Base child in rev) + { + Base found = child.GetControlAt(x - child.X, y - child.Y); + if (found != null) + return found; + } + + if (!MouseInputEnabled) + return null; + + return this; + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected virtual void Layout(Skin.Base skin) + { + if (skin.Renderer.CTT != null && ShouldCacheToTexture) + skin.Renderer.CTT.CreateControlCacheTexture(this); + } + + /// + /// Recursively lays out the control's interior according to alignment, margin, padding, dock etc. + /// + /// Skin to use. + protected virtual void RecurseLayout(Skin.Base skin) + { + if (m_Skin != null) + skin = m_Skin; + if (IsHidden) + return; + + if (m_NeedsLayout) + { + m_NeedsLayout = false; + Layout(skin); + } + + Rectangle bounds = RenderBounds; + + // Adjust bounds for padding + bounds.X += m_Padding.Left; + bounds.Width -= m_Padding.Left + m_Padding.Right; + bounds.Y += m_Padding.Top; + bounds.Height -= m_Padding.Top + m_Padding.Bottom; + + foreach (Base child in m_Children) + { + if (child.IsHidden) + continue; + + Pos dock = child.Dock; + + if (0 != (dock & Pos.Fill)) + continue; + + if (0 != (dock & Pos.Top)) + { + Margin margin = child.Margin; + + child.SetBounds(bounds.X + margin.Left, bounds.Y + margin.Top, + bounds.Width - margin.Left - margin.Right, child.Height); + + int height = margin.Top + margin.Bottom + child.Height; + bounds.Y += height; + bounds.Height -= height; + } + + if (0 != (dock & Pos.Left)) + { + Margin margin = child.Margin; + + child.SetBounds(bounds.X + margin.Left, bounds.Y + margin.Top, child.Width, + bounds.Height - margin.Top - margin.Bottom); + + int width = margin.Left + margin.Right + child.Width; + bounds.X += width; + bounds.Width -= width; + } + + if (0 != (dock & Pos.Right)) + { + // TODO: THIS MARGIN CODE MIGHT NOT BE FULLY FUNCTIONAL + Margin margin = child.Margin; + + child.SetBounds((bounds.X + bounds.Width) - child.Width - margin.Right, bounds.Y + margin.Top, + child.Width, bounds.Height - margin.Top - margin.Bottom); + + int width = margin.Left + margin.Right + child.Width; + bounds.Width -= width; + } + + if (0 != (dock & Pos.Bottom)) + { + // TODO: THIS MARGIN CODE MIGHT NOT BE FULLY FUNCTIONAL + Margin margin = child.Margin; + + child.SetBounds(bounds.X + margin.Left, + (bounds.Y + bounds.Height) - child.Height - margin.Bottom, + bounds.Width - margin.Left - margin.Right, child.Height); + bounds.Height -= child.Height + margin.Bottom + margin.Top; + } + + child.RecurseLayout(skin); + } + + m_InnerBounds = bounds; + + // + // Fill uses the left over space, so do that now. + // + foreach (Base child in m_Children) + { + Pos dock = child.Dock; + + if (!(0 != (dock & Pos.Fill))) + continue; + + Margin margin = child.Margin; + + child.SetBounds(bounds.X + margin.Left, bounds.Y + margin.Top, + bounds.Width - margin.Left - margin.Right, bounds.Height - margin.Top - margin.Bottom); + child.RecurseLayout(skin); + } + + PostLayout(skin); + + if (IsTabable) + { + if (GetCanvas().FirstTab == null) + GetCanvas().FirstTab = this; + if (GetCanvas().NextTab == null) + GetCanvas().NextTab = this; + } + + if (InputHandler.KeyboardFocus == this) + { + GetCanvas().NextTab = null; + } + } + + /// + /// Checks if the given control is a child of this instance. + /// + /// Control to examine. + /// True if the control is out child. + public bool IsChild(Base child) + { + return m_Children.Contains(child); + } + + /// + /// Converts local coordinates to canvas coordinates. + /// + /// Local coordinates. + /// Canvas coordinates. + public virtual Point LocalPosToCanvas(Point pnt) + { + if (m_Parent != null) + { + int x = pnt.X + X; + int y = pnt.Y + Y; + + // If our parent has an innerpanel and we're a child of it + // add its offset onto us. + // + if (m_Parent.m_InnerPanel != null && m_Parent.m_InnerPanel.IsChild(this)) + { + x += m_Parent.m_InnerPanel.X; + y += m_Parent.m_InnerPanel.Y; + } + + return m_Parent.LocalPosToCanvas(new Point(x, y)); + } + + return pnt; + } + + /// + /// Converts canvas coordinates to local coordinates. + /// + /// Canvas coordinates. + /// Local coordinates. + public virtual Point CanvasPosToLocal(Point pnt) + { + if (m_Parent != null) + { + int x = pnt.X - X; + int y = pnt.Y - Y; + + // If our parent has an innerpanel and we're a child of it + // add its offset onto us. + // + if (m_Parent.m_InnerPanel != null && m_Parent.m_InnerPanel.IsChild(this)) + { + x -= m_Parent.m_InnerPanel.X; + y -= m_Parent.m_InnerPanel.Y; + } + + + return m_Parent.CanvasPosToLocal(new Point(x, y)); + } + + return pnt; + } + + /// + /// Closes all menus recursively. + /// + public virtual void CloseMenus() + { + //Debug.Print("Base.CloseMenus: {0}", this); + + // todo: not very efficient with the copying and recursive closing, maybe store currently open menus somewhere (canvas)? + var childrenCopy = m_Children.FindAll(x => true); + foreach (Base child in childrenCopy) + { + child.CloseMenus(); + } + } + + /// + /// Copies Bounds to RenderBounds. + /// + protected virtual void UpdateRenderBounds() + { + m_RenderBounds.X = 0; + m_RenderBounds.Y = 0; + + m_RenderBounds.Width = m_Bounds.Width; + m_RenderBounds.Height = m_Bounds.Height; + } + + /// + /// Sets mouse cursor to current cursor. + /// + public virtual void UpdateCursor() + { + //Platform.Neutral.SetCursor(m_Cursor); + } + + // giver + public virtual Package DragAndDrop_GetPackage(int x, int y) + { + return m_DragAndDrop_Package; + } + + // giver + public virtual bool DragAndDrop_Draggable() + { + if (m_DragAndDrop_Package == null) + return false; + + return m_DragAndDrop_Package.IsDraggable; + } + + // giver + public virtual void DragAndDrop_SetPackage(bool draggable, String name = "", object userData = null) + { + if (m_DragAndDrop_Package == null) + { + m_DragAndDrop_Package = new Package(); + m_DragAndDrop_Package.IsDraggable = draggable; + m_DragAndDrop_Package.Name = name; + m_DragAndDrop_Package.UserData = userData; + } + } + + // giver + public virtual bool DragAndDrop_ShouldStartDrag() + { + return true; + } + + // giver + public virtual void DragAndDrop_StartDragging(Package package, int x, int y) + { + package.HoldOffset = CanvasPosToLocal(new Point(x, y)); + package.DrawControl = this; + } + + // giver + public virtual void DragAndDrop_EndDragging(bool success, int x, int y) + { + } + + // receiver + public virtual bool DragAndDrop_HandleDrop(Package p, int x, int y) + { + DragAndDrop.SourceControl.Parent = this; + return true; + } + + // receiver + public virtual void DragAndDrop_HoverEnter(Package p, int x, int y) + { + + } + + // receiver + public virtual void DragAndDrop_HoverLeave(Package p) + { + + } + + // receiver + public virtual void DragAndDrop_Hover(Package p, int x, int y) + { + + } + + // receiver + public virtual bool DragAndDrop_CanAcceptPackage(Package p) + { + return false; + } + + /// + /// Resizes the control to fit its children. + /// + /// Determines whether to change control's width. + /// Determines whether to change control's height. + /// True if bounds changed. + public virtual bool SizeToChildren(bool width = true, bool height = true) + { + Point size = GetChildrenSize(); + size.X += Padding.Right; + size.Y += Padding.Bottom; + return SetSize(width ? size.X : Width, height ? size.Y : Height); + } + + /// + /// Returns the total width and height of all children. + /// + /// Default implementation returns maximum size of children since the layout is unknown. + /// Implement this in derived compound controls to properly return their size. + /// + public virtual Point GetChildrenSize() + { + Point size = Point.Empty; + + foreach (Base child in m_Children) + { + if (child.IsHidden) + continue; + + size.X = Math.Max(size.X, child.Right); + size.Y = Math.Max(size.Y, child.Bottom); + } + + return size; + } + + /// + /// Handles keyboard accelerator. + /// + /// Accelerator text. + /// True if handled. + internal virtual bool HandleAccelerator(String accelerator) + { + if (InputHandler.KeyboardFocus == this || !AccelOnlyFocus) + { + if (m_Accelerators.ContainsKey(accelerator)) + { + m_Accelerators[accelerator].Invoke(this); + return true; + } + } + + return m_Children.Any(child => child.HandleAccelerator(accelerator)); + } + + /// + /// Adds keyboard accelerator. + /// + /// Accelerator text. + /// Handler. + public void AddAccelerator(String accelerator, GwenEventHandler handler) + { + accelerator = accelerator.Trim().ToUpperInvariant(); + m_Accelerators[accelerator] = handler; + } + + /// + /// Adds keyboard accelerator with a default handler. + /// + /// Accelerator text. + public void AddAccelerator(String accelerator) + { + m_Accelerators[accelerator] = DefaultAcceleratorHandler; + } + + /// + /// Function invoked after layout. + /// + /// Skin to use. + protected virtual void PostLayout(Skin.Base skin) + { + + } + + /// + /// Re-renders the control, invalidates cached texture. + /// + public virtual void Redraw() + { + UpdateColors(); + m_CacheTextureDirty = true; + if (m_Parent != null) + m_Parent.Redraw(); + } + + /// + /// Updates control colors. + /// + /// + /// Used in composite controls like lists to differentiate row colors etc. + /// + public virtual void UpdateColors() + { + + } + + /// + /// Invalidates control's parent. + /// + public void InvalidateParent() + { + if (m_Parent != null) + { + m_Parent.Invalidate(); + } + } + + /// + /// Handler for keyboard events. + /// + /// Key pressed. + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyPressed(Key key, bool down = true) + { + bool handled = false; + switch (key) + { + case Key.Tab: handled = OnKeyTab(down); break; + case Key.Space: handled = OnKeySpace(down); break; + case Key.Home: handled = OnKeyHome(down); break; + case Key.End: handled = OnKeyEnd(down); break; + case Key.Return: handled = OnKeyReturn(down); break; + case Key.Backspace: handled = OnKeyBackspace(down); break; + case Key.Delete: handled = OnKeyDelete(down); break; + case Key.Right: handled = OnKeyRight(down); break; + case Key.Left: handled = OnKeyLeft(down); break; + case Key.Up: handled = OnKeyUp(down); break; + case Key.Down: handled = OnKeyDown(down); break; + case Key.Escape: handled = OnKeyEscape(down); break; + default: break; + } + + if (!handled && Parent != null) + Parent.OnKeyPressed(key, down); + + return handled; + } + + /// + /// Invokes key press event (used by input system). + /// + internal bool InputKeyPressed(Key key, bool down = true) + { + return OnKeyPressed(key, down); + } + + /// + /// Handler for keyboard events. + /// + /// Key pressed. + /// True if handled. + protected virtual bool OnKeyReleaseed(Key key) + { + return OnKeyPressed(key, false); + } + + /// + /// Handler for Tab keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyTab(bool down) + { + if (!down) + return true; + + if (GetCanvas().NextTab != null) + { + GetCanvas().NextTab.Focus(); + Redraw(); + } + + return true; + } + + /// + /// Handler for Space keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeySpace(bool down) { return false; } + + /// + /// Handler for Return keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyReturn(bool down) { return false; } + + /// + /// Handler for Backspace keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyBackspace(bool down) { return false; } + + /// + /// Handler for Delete keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyDelete(bool down) { return false; } + + /// + /// Handler for Right Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyRight(bool down) { return false; } + + /// + /// Handler for Left Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyLeft(bool down) { return false; } + + /// + /// Handler for Home keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyHome(bool down) { return false; } + + /// + /// Handler for End keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyEnd(bool down) { return false; } + + /// + /// Handler for Up Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyUp(bool down) { return false; } + + /// + /// Handler for Down Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyDown(bool down) { return false; } + + /// + /// Handler for Escape keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// True if handled. + protected virtual bool OnKeyEscape(bool down) { return false; } + + /// + /// Handler for Paste event. + /// + /// Source control. + protected virtual void OnPaste(Base from) + { + } + + /// + /// Handler for Copy event. + /// + /// Source control. + protected virtual void OnCopy(Base from) + { + } + + /// + /// Handler for Cut event. + /// + /// Source control. + protected virtual void OnCut(Base from) + { + } + + /// + /// Handler for Select All event. + /// + /// Source control. + protected virtual void OnSelectAll(Base from) + { + } + + internal void InputCopy(Base from) + { + OnCopy(from); + } + + internal void InputPaste(Base from) + { + OnPaste(from); + } + + internal void InputCut(Base from) + { + OnCut(from); + } + + internal void InputSelectAll(Base from) + { + OnSelectAll(from); + } + + /// + /// Renders the focus overlay. + /// + /// Skin to use. + protected virtual void RenderFocus(Skin.Base skin) + { + if (InputHandler.KeyboardFocus != this) + return; + if (!IsTabable) + return; + + skin.DrawKeyboardHighlight(this, RenderBounds, 3); + } + + /// + /// Renders under the actual control (shadows etc). + /// + /// Skin to use. + protected virtual void RenderUnder(Skin.Base skin) + { + + } + + /// + /// Renders over the actual control (overlays). + /// + /// Skin to use. + protected virtual void RenderOver(Skin.Base skin) + { + + } + + /// + /// Called during rendering. + /// + public virtual void Think() + { + + } + + /// + /// Handler for gaining keyboard focus. + /// + protected virtual void OnKeyboardFocus() + { + + } + + /// + /// Handler for losing keyboard focus. + /// + protected virtual void OnLostKeyboardFocus() + { + + } + + /// + /// Handler for character input event. + /// + /// Character typed. + /// True if handled. + protected virtual bool OnChar(Char chr) + { + return false; + } + + internal bool InputChar(Char chr) + { + return OnChar(chr); + } + + public virtual void Anim_WidthIn(float length, float delay = 0.0f, float ease = 1.0f) + { + Animation.Add(this, new Anim.Size.Width(0, Width, length, false, delay, ease)); + Width = 0; + } + + public virtual void Anim_HeightIn(float length, float delay, float ease) + { + Animation.Add(this, new Anim.Size.Height(0, Height, length, false, delay, ease)); + Height = 0; + } + + public virtual void Anim_WidthOut(float length, bool hide, float delay, float ease) + { + Animation.Add(this, new Anim.Size.Width(Width, 0, length, hide, delay, ease)); + } + + public virtual void Anim_HeightOut(float length, bool hide, float delay, float ease) + { + Animation.Add(this, new Anim.Size.Height(Height, 0, length, hide, delay, ease)); + } + } +} diff --git a/Gwen/Control/Button.cs b/Gwen/Control/Button.cs new file mode 100644 index 0000000..9121a73 --- /dev/null +++ b/Gwen/Control/Button.cs @@ -0,0 +1,318 @@ +using System; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Button control. + /// + public class Button : Label + { + private bool m_Depressed; + private bool m_Toggle; + private bool m_ToggleStatus; + private bool m_CenterImage; + private ImagePanel m_Image; + + /// + /// Invoked when the button is released. + /// + public event GwenEventHandler Clicked; + + /// + /// Invoked when the button is pressed. + /// + public event GwenEventHandler Pressed; + + /// + /// Invoked when the button is released. + /// + public event GwenEventHandler Released; + + /// + /// Invoked when the button's toggle state has changed. + /// + public event GwenEventHandler Toggled; + + /// + /// Invoked when the button's toggle state has changed to On. + /// + public event GwenEventHandler ToggledOn; + + /// + /// Invoked when the button's toggle state has changed to Off. + /// + public event GwenEventHandler ToggledOff; + + /// + /// Invoked when the button has been double clicked. + /// + public event GwenEventHandler DoubleClickedLeft; + + /// + /// Indicates whether the button is depressed. + /// + public bool IsDepressed + { + get { return m_Depressed; } + set + { + if (m_Depressed == value) + return; + m_Depressed = value; + Redraw(); + } + } + + /// + /// Indicates whether the button is toggleable. + /// + public bool IsToggle { get { return m_Toggle; } set { m_Toggle = value; } } + + /// + /// Determines the button's toggle state. + /// + 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(); + } + } + + /// + /// Control constructor. + /// + /// Parent control. + public Button(Base parent) + : base(parent) + { + SetSize(100, 20); + MouseInputEnabled = true; + Alignment = Pos.Center; + TextPadding = new Padding(3, 3, 3, 3); + } + + /// + /// Toggles the button. + /// + public virtual void Toggle() + { + ToggleState = !ToggleState; + } + + /// + /// "Clicks" the button. + /// + public virtual void Press(Base control = null) + { + OnClicked(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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); + } + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + //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(); + } + + /// + /// Internal OnPressed implementation. + /// + protected virtual void OnClicked() + { + if (IsToggle) + { + Toggle(); + } + + if (Clicked != null) + Clicked.Invoke(this); + } + + /// + /// Sets the button's image. + /// + /// Texture name. Null to remove. + /// Determines whether the image should be centered. + 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); + } + + /// + /// Sizes to contents. + /// + public override void SizeToContents() + { + base.SizeToContents(); + if (m_Image != null) + { + int height = m_Image.Height + 4; + if (Height < height) + { + Height = height; + } + } + } + + /// + /// Handler for Space keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeySpace(bool down) + { + if (down) + OnClicked(); + return true; + } + + /// + /// Default accelerator handler. + /// + protected override void OnAccelerator() + { + OnClicked(); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + } + + /// + /// Updates control colors. + /// + 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; + } + + /// + /// Handler invoked on mouse double click (left) event. + /// + /// X coordinate. + /// Y coordinate. + protected override void OnMouseDoubleClickedLeft(int x, int y) + { + OnMouseClickedLeft(x, y, true); + if (DoubleClickedLeft != null) + DoubleClickedLeft.Invoke(this); + } + } +} diff --git a/Gwen/Control/Canvas.cs b/Gwen/Control/Canvas.cs new file mode 100644 index 0000000..d36200f --- /dev/null +++ b/Gwen/Control/Canvas.cs @@ -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 +{ + /// + /// Canvas control. It should be the root parent for all other controls. + /// + 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 m_DisposeQueue; // dictionary for faster access? + + /// + /// Scale for rendering. + /// + 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(); + } + } + + /// + /// Background color. + /// + public Color BackgroundColor { get { return m_BackgroundColor; } set { m_BackgroundColor = value; } } + + /// + /// 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. + /// + public bool NeedsRedraw { get { return m_NeedsRedraw; } set { m_NeedsRedraw = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Skin to use. + public Canvas(Skin.Base skin) + { + SetBounds(0, 0, 10000, 10000); + SetSkin(skin); + Scale = 1.0f; + BackgroundColor = Color.White; + ShouldDrawBackground = false; + + m_DisposeQueue = new List(); + } + + public override void Dispose() + { + ProcessDelayedDeletes(); + base.Dispose(); + } + + /// + /// Re-renders the control, invalidates cached texture. + /// + 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; + } + + /// + /// Additional initialization (which is sometimes not appropriate in the constructor) + /// + protected void Initialize() + { + + } + + /// + /// Renders the canvas. Call in your rendering loop. + /// + 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(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + //skin.Renderer.rnd = new Random(1); + base.Render(skin); + m_NeedsRedraw = false; + } + + /// + /// Handler invoked when control's bounds change. + /// + /// Old bounds. + protected override void OnBoundsChanged(Rectangle oldBounds) + { + base.OnBoundsChanged(oldBounds); + InvalidateChildren(true); + } + + /// + /// Processes input and layout. Also purges delayed delete queue. + /// + 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); + } + + /// + /// Adds given control to the delete queue and detaches it from canvas. Don't call from Dispose, it modifies child list. + /// + /// Control to delete. + 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(); + } + + /// + /// Handles mouse movement events. Called from Input subsystems. + /// + /// True if handled. + 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; + } + + /// + /// Handles mouse button events. Called from Input subsystems. + /// + /// True if handled. + public bool Input_MouseButton(int button, bool down) + { + if (IsHidden) return false; + + return InputHandler.OnMouseClicked(this, button, down); + } + + /// + /// Handles keyboard events. Called from Input subsystems. + /// + /// True if handled. + 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); + } + + /// + /// Handles keyboard events. Called from Input subsystems. + /// + /// True if handled. + 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); + } + + /// + /// Handles the mouse wheel events. Called from Input subsystems. + /// + /// True if handled. + 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); + } + } +} diff --git a/Gwen/Control/CheckBox.cs b/Gwen/Control/CheckBox.cs new file mode 100644 index 0000000..ebc002c --- /dev/null +++ b/Gwen/Control/CheckBox.cs @@ -0,0 +1,113 @@ +using System; + +namespace Gwen.Control +{ + /// + /// CheckBox control. + /// + public class CheckBox : Button + { + private bool m_Checked; + + /// + /// Indicates whether the checkbox is checked. + /// + public bool IsChecked + { + get { return m_Checked; } + set + { + if (m_Checked == value) return; + m_Checked = value; + OnCheckChanged(); + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public CheckBox(Base parent) + : base(parent) + { + SetSize(15, 15); + //m_Checked = true; // [omeg] why?! + //Toggle(); + } + + /// + /// Toggles the checkbox. + /// + public override void Toggle() + { + base.Toggle(); + IsChecked = !IsChecked; + } + + /// + /// Invoked when the checkbox has been checked. + /// + public event GwenEventHandler Checked; + + /// + /// Invoked when the checkbox has been unchecked. + /// + public event GwenEventHandler UnChecked; + + /// + /// Invoked when the checkbox state has been changed. + /// + public event GwenEventHandler CheckChanged; + + /// + /// Determines whether unchecking is allowed. + /// + protected virtual bool AllowUncheck { get { return true; } } + + /// + /// Handler for CheckChanged event. + /// + 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); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + base.Render(skin); + skin.DrawCheckBox(this, m_Checked, IsDepressed); + } + + /// + /// Internal OnPressed implementation. + /// + protected override void OnClicked() + { + if (IsDisabled) + return; + + if (IsChecked && !AllowUncheck) + { + return; + } + + Toggle(); + } + } +} diff --git a/Gwen/Control/CollapsibleCategory.cs b/Gwen/Control/CollapsibleCategory.cs new file mode 100644 index 0000000..a7914ec --- /dev/null +++ b/Gwen/Control/CollapsibleCategory.cs @@ -0,0 +1,176 @@ +using System; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// CollapsibleCategory control. Used in CollapsibleList. + /// + public class CollapsibleCategory : Base + { + private readonly Button m_HeaderButton; + private readonly CollapsibleList m_List; + + /// + /// Header text. + /// + public String Text { get { return m_HeaderButton.Text; } set { m_HeaderButton.Text = value; } } + + /// + /// Determines whether the category is collapsed (closed). + /// + public bool IsCollapsed { get { return m_HeaderButton.ToggleState; } set { m_HeaderButton.ToggleState = value; } } + + /// + /// Invoked when an entry has been selected. + /// + public event GwenEventHandler Selected; + + /// + /// Invoked when the category collapsed state has been changed (header button has been pressed). + /// + public event GwenEventHandler Collapsed; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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); + } + + /// + /// Gets the selected entry. + /// + public Button GetSelectedButton() + { + foreach (Base child in Children) + { + CategoryButton button = child as CategoryButton; + if (button == null) + continue; + + if (button.ToggleState) + return button; + } + + return null; + } + + /// + /// Handler for header button toggle event. + /// + /// Source control. + protected virtual void OnHeaderToggle(Base control) + { + if (Collapsed != null) + Collapsed.Invoke(this); + } + + /// + /// Handler for Selected event. + /// + /// Event source. + 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); + } + + /// + /// Adds a new entry. + /// + /// Entry name (displayed). + /// Newly created control. + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawCategoryInner(this, m_HeaderButton.ToggleState); + base.Render(skin); + } + + /// + /// Unselects all entries. + /// + public void UnselectAll() + { + foreach (Base child in Children) + { + CategoryButton button = child as CategoryButton; + if (button == null) + continue; + + button.ToggleState = false; + } + } + + /// + /// Function invoked after layout. + /// + /// Skin to use. + 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; + } + } + } +} diff --git a/Gwen/Control/CollapsibleList.cs b/Gwen/Control/CollapsibleList.cs new file mode 100644 index 0000000..8ef84e9 --- /dev/null +++ b/Gwen/Control/CollapsibleList.cs @@ -0,0 +1,130 @@ +using System; + +namespace Gwen.Control +{ + /// + /// CollapsibleList control. Groups CollapsibleCategory controls. + /// + public class CollapsibleList : ScrollControl + { + /// + /// Invoked when an entry has been selected. + /// + public event GwenEventHandler ItemSelected; + + /// + /// Invoked when a category collapsed state has been changed (header button has been pressed). + /// + public event GwenEventHandler CategoryCollapsed; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public CollapsibleList(Base parent) : base(parent) + { + EnableScroll(false, true); + AutoHideBars = true; + } + + // todo: iterator, make this as function? check if works + + /// + /// Selected entry. + /// + 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; + } + + /// + /// Adds a category to the list. + /// + /// Category control to add. + 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 + } + + /// + /// Adds a new category to the list. + /// + /// Name of the category. + /// Newly created control. + public virtual CollapsibleCategory Add(String categoryName) + { + CollapsibleCategory cat = new CollapsibleCategory(this); + cat.Text = categoryName; + Add(cat); + return cat; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawCategoryHolder(this); + base.Render(skin); + } + + /// + /// Unselects all entries. + /// + public virtual void UnselectAll() + { + foreach (Base child in Children) + { + CollapsibleCategory cat = child as CollapsibleCategory; + if (cat == null) + continue; + + cat.UnselectAll(); + } + } + + /// + /// Handler for ItemSelected event. + /// + /// Event source: . + protected virtual void OnCategorySelected(Base control) + { + CollapsibleCategory cat = control as CollapsibleCategory; + if (cat == null) return; + + if (ItemSelected != null) + ItemSelected.Invoke(this); + } + + /// + /// Handler for category collapsed event. + /// + /// Event source: . + protected virtual void OnCategoryCollapsed(Base control) + { + CollapsibleCategory cat = control as CollapsibleCategory; + if (cat == null) return; + + if (CategoryCollapsed != null) + CategoryCollapsed.Invoke(control); + } + } +} diff --git a/Gwen/Control/ColorLerpBox.cs b/Gwen/Control/ColorLerpBox.cs new file mode 100644 index 0000000..3014c4f --- /dev/null +++ b/Gwen/Control/ColorLerpBox.cs @@ -0,0 +1,207 @@ +using System; +using System.Drawing; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Linear-interpolated HSV color box. + /// + public class ColorLerpBox : Base + { + private Point m_CursorPos; + private bool m_Depressed; + private float m_Hue; + private Texture m_Texture; + + /// + /// Invoked when the selected color has been changed. + /// + public event GwenEventHandler ColorChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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 + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public override void Dispose() + { + if (m_Texture != null) + m_Texture.Dispose(); + base.Dispose(); + } + + /// + /// Linear color interpolation. + /// + public static Color Lerp(Color toColor, Color fromColor, float amount) + { + Color delta = toColor.Subtract(fromColor); + delta = delta.Multiply(amount); + return fromColor.Add(delta); + } + + /// + /// Selected color. + /// + public Color SelectedColor + { + get { return GetColorAt(m_CursorPos.X, m_CursorPos.Y); } + } + + /// + /// Sets the selected color. + /// + /// Value to set. + /// Deetrmines whether to only set H value (not SV). + 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); + } + + /// + /// Handler invoked on mouse moved event. + /// + /// X coordinate. + /// Y coordinate. + /// X change. + /// Y change. + 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); + } + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + m_Depressed = down; + if (down) + InputHandler.MouseFocus = this; + else + InputHandler.MouseFocus = null; + + OnMouseMoved(x, y, 0, 0); + } + + /// + /// Gets the color from specified coordinates. + /// + /// X + /// Y + /// Color value. + 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; + } + + /// + /// Invalidates the control. + /// + public override void Invalidate() + { + if (m_Texture != null) + { + m_Texture.Dispose(); + m_Texture = null; + } + base.Invalidate(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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); + } + } +} diff --git a/Gwen/Control/ColorPicker.cs b/Gwen/Control/ColorPicker.cs new file mode 100644 index 0000000..5683777 --- /dev/null +++ b/Gwen/Control/ColorPicker.cs @@ -0,0 +1,256 @@ +using System; +using System.Collections.Generic; +using System.Drawing; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// RGBA color picker. + /// + public class ColorPicker : Base, IColorPicker + { + private Color m_Color; + + /// + /// Selected color. + /// + public Color SelectedColor { get { return m_Color; } set { m_Color = value; UpdateControls(); } } + + /// + /// Red value of the selected color. + /// + public int R { get { return m_Color.R; } set { m_Color = Color.FromArgb(m_Color.A, value, m_Color.G, m_Color.B); } } + + /// + /// Green value of the selected color. + /// + public int G { get { return m_Color.G; } set { m_Color = Color.FromArgb(m_Color.A, m_Color.R, value, m_Color.B); } } + + /// + /// Blue value of the selected color. + /// + public int B { get { return m_Color.B; } set { m_Color = Color.FromArgb(m_Color.A, m_Color.R, m_Color.G, value); } } + + /// + /// Alpha value of the selected color. + /// + public int A { get { return m_Color.A; } set { m_Color = Color.FromArgb(value, m_Color.R, m_Color.G, m_Color.B); } } + + /// + /// Invoked when the selected color has been changed. + /// + public event GwenEventHandler ColorChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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( FindChildByName( "RedSlider", true ) ); + HorizontalSlider* greenSlider = gwen_cast( FindChildByName( "GreenSlider", true ) ); + HorizontalSlider* blueSlider = gwen_cast( FindChildByName( "BlueSlider", true ) ); + HorizontalSlider* alphaSlider = gwen_cast( 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() ) ); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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; + } + + /// + /// Determines whether the Alpha control is visible. + /// + 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(); + } + } + } +} diff --git a/Gwen/Control/ColorSlider.cs b/Gwen/Control/ColorSlider.cs new file mode 100644 index 0000000..eb89c5e --- /dev/null +++ b/Gwen/Control/ColorSlider.cs @@ -0,0 +1,152 @@ +using System; +using System.Drawing; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// HSV hue selector. + /// + public class ColorSlider : Base + { + private int m_SelectedDist; + private bool m_Depressed; + private Texture m_Texture; + + /// + /// Invoked when the selected color has been changed. + /// + public event GwenEventHandler ColorChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public ColorSlider(Base parent) + : base(parent) + { + SetSize(32, 128); + MouseInputEnabled = true; + m_Depressed = false; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public override void Dispose() + { + if (m_Texture != null) + m_Texture.Dispose(); + base.Dispose(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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); + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + m_Depressed = down; + if (down) + InputHandler.MouseFocus = this; + else + InputHandler.MouseFocus = null; + + OnMouseMoved(x, y, 0, 0); + } + + /// + /// Handler invoked on mouse moved event. + /// + /// X coordinate. + /// Y coordinate. + /// X change. + /// Y change. + 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); + } + + /// + /// Selected color. + /// + public Color SelectedColor { get { return GetColorAtHeight(m_SelectedDist); } set { SetColor(value); } } + } +} diff --git a/Gwen/Control/ComboBox.cs b/Gwen/Control/ComboBox.cs new file mode 100644 index 0000000..2e18a4f --- /dev/null +++ b/Gwen/Control/ComboBox.cs @@ -0,0 +1,238 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// ComboBox control. + /// + public class ComboBox : Button + { + private readonly Menu m_Menu; + private readonly Base m_Button; + private MenuItem m_SelectedItem; + + /// + /// Invoked when the selected item has changed. + /// + public event GwenEventHandler ItemSelected; + + /// + /// Indicates whether the combo menu is open. + /// + public bool IsOpen { get { return m_Menu != null && !m_Menu.IsHidden; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Selected item. + /// + /// Not just String property, because items also have internal names. + public Label SelectedItem { get { return m_SelectedItem; } } + + internal override bool IsMenuComponent + { + get { return true; } + } + + /// + /// Adds a new item. + /// + /// Item label (displayed). + /// Item name. + /// Newly created control. + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawComboBox(this, IsDepressed, IsOpen); + } + + /// + /// Internal Pressed implementation. + /// + protected override void OnClicked() + { + if (IsOpen) + { + GetCanvas().CloseMenus(); + return; + } + + bool wasMenuHidden = m_Menu.IsHidden; + + GetCanvas().CloseMenus(); + + if (wasMenuHidden) + { + Open(); + } + } + + /// + /// Removes all items. + /// + public virtual void DeleteAll() + { + if (m_Menu != null) + m_Menu.DeleteAll(); + } + + /// + /// Internal handler for item selected event. + /// + /// Event source. + 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(); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + m_Button.Position(Pos.Right|Pos.CenterV, 4, 0); + base.Layout(skin); + } + + /// + /// Handler for losing keyboard focus. + /// + protected override void OnLostKeyboardFocus() + { + TextColor = Color.Black; + } + + /// + /// Handler for gaining keyboard focus. + /// + protected override void OnKeyboardFocus() + { + //Until we add the blue highlighting again + TextColor = Color.Black; + } + + /// + /// Opens the combo. + /// + 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)); + } + + /// + /// Closes the combo. + /// + public virtual void Close() + { + if (m_Menu == null) + return; + + m_Menu.Hide(); + } + + /// + /// Handler for Down Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for Up Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Renders the focus overlay. + /// + /// Skin to use. + protected override void RenderFocus(Skin.Base skin) + { + + } + } +} diff --git a/Gwen/Control/CrossSplitter.cs b/Gwen/Control/CrossSplitter.cs new file mode 100644 index 0000000..2d67628 --- /dev/null +++ b/Gwen/Control/CrossSplitter.cs @@ -0,0 +1,282 @@ +using System; +//using System.Windows.Forms; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Splitter control. + /// + 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 + + /// + /// Invoked when one of the panels has been zoomed (maximized). + /// + public event GwenEventHandler PanelZoomed; + + /// + /// Invoked when one of the panels has been unzoomed (restored). + /// + public event GwenEventHandler PanelUnZoomed; + + /// + /// Invoked when the zoomed panel has been changed. + /// + public event GwenEventHandler ZoomChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Centers the panels so that they take even amount of space. + /// + public void CenterPanels() + { + m_HVal = 0.5f; + m_VVal = 0.5f; + Invalidate(); + } + + /// + /// Indicates whether any of the panels is zoomed. + /// + public bool IsZoomed { get { return m_ZoomedSection != -1; } } + + /// + /// Gets or sets a value indicating whether splitters should be visible. + /// + public bool SplittersVisible + { + get { return m_CSplitter.ShouldDrawBackground; } + set + { + m_CSplitter.ShouldDrawBackground = value; + m_VSplitter.ShouldDrawBackground = value; + m_HSplitter.ShouldDrawBackground = value; + } + } + + /// + /// Gets or sets the size of the splitter. + /// + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + } + + /// + /// Assigns a control to the specific inner section. + /// + /// Section index (0-3). + /// Control to assign. + public void SetPanel(int index, Base panel) + { + m_Sections[index] = panel; + + if (panel != null) + { + panel.Dock = Pos.None; + panel.Parent = this; + } + + Invalidate(); + } + + /// + /// Gets the specific inner section. + /// + /// Section index (0-3). + /// Specified section. + public Base GetPanel(int index) + { + return m_Sections[index]; + } + + /// + /// Internal handler for the zoom changed event. + /// + 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); + } + } + + /// + /// Maximizes the specified panel so it fills the entire control. + /// + /// Panel index (0-3). + 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(); + } + + /// + /// Restores the control so all panels are visible. + /// + 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(); + } + } +} diff --git a/Gwen/Control/DockBase.cs b/Gwen/Control/DockBase.cs new file mode 100644 index 0000000..8f6ee0c --- /dev/null +++ b/Gwen/Control/DockBase.cs @@ -0,0 +1,440 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; +using Gwen.DragDrop; + +namespace Gwen.Control +{ + /// + /// Base for dockable containers. + /// + 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? + + /// + /// Control docked on the left side. + /// + public DockBase LeftDock { get { return GetChildDock(Pos.Left); } } + + /// + /// Control docked on the right side. + /// + public DockBase RightDock { get { return GetChildDock(Pos.Right); } } + + /// + /// Control docked on the top side. + /// + public DockBase TopDock { get { return GetChildDock(Pos.Top); } } + + /// + /// Control docked on the bottom side. + /// + public DockBase BottomDock { get { return GetChildDock(Pos.Bottom); } } + + public TabControl TabControl { get { return m_DockedTabControl; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public DockBase(Base parent) + : base(parent) + { + Padding = Padding.One; + SetSize(200, 200); + } + + /// + /// Handler for Space keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeySpace(bool down) + { + // No action on space (default button action is to press) + return false; + } + + /// + /// Initializes an inner docked control for the specified position. + /// + /// Dock position. + 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); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + + } + + /// + /// Gets an inner docked control for the specified position. + /// + /// + /// + 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; + } + + /// + /// Calculates dock direction from dragdrop coordinates. + /// + /// X coordinate. + /// Y coordinate. + /// Dock direction. + 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; + } + + /// + /// Indicates whether the control contains any docked children. + /// + 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; + } + } + } + + /// + /// Renders over the actual control (overlays). + /// + /// Skin to use. + 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); + } + } +} diff --git a/Gwen/Control/DockedTabControl.cs b/Gwen/Control/DockedTabControl.cs new file mode 100644 index 0000000..3358dd6 --- /dev/null +++ b/Gwen/Control/DockedTabControl.cs @@ -0,0 +1,81 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Docked tab control. + /// + public class DockedTabControl : TabControl + { + private readonly TabTitleBar m_TitleBar; + + /// + /// Determines whether the title bar is visible. + /// + public bool TitleBarVisible { get { return !m_TitleBar.IsHidden; } set { m_TitleBar.IsHidden = !value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public DockedTabControl(Base parent) + : base(parent) + { + Dock = Pos.Fill; + + m_TitleBar = new TabTitleBar(this); + m_TitleBar.Dock = Pos.Top; + m_TitleBar.IsHidden = true; + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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(); + } + } +} diff --git a/Gwen/Control/GroupBox.cs b/Gwen/Control/GroupBox.cs new file mode 100644 index 0000000..d5624cc --- /dev/null +++ b/Gwen/Control/GroupBox.cs @@ -0,0 +1,74 @@ +using System; +using System.Drawing; + +namespace Gwen.Control +{ + /// + /// Group box (container). + /// + /// Don't use autosize with docking. + public class GroupBox : Label + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + base.Layout(skin); + if (AutoSizeToContents) + { + DoSizeToContents(); + } + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawGroupBox(this, TextX, TextHeight, TextWidth); + } + + /// + /// Sizes to contents. + /// + 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; + } + } +} diff --git a/Gwen/Control/HSVColorPicker.cs b/Gwen/Control/HSVColorPicker.cs new file mode 100644 index 0000000..27bddbd --- /dev/null +++ b/Gwen/Control/HSVColorPicker.cs @@ -0,0 +1,198 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// HSV color picker with "before" and "after" color boxes. + /// + 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; + + /// + /// Invoked when the selected color has changed. + /// + public event GwenEventHandler ColorChanged; + + /// + /// The "before" color. + /// + public Color DefaultColor { get { return m_Before.Color; } set { m_Before.Color = value; } } + + /// + /// Selected color. + /// + public Color SelectedColor { get { return m_LerpBox.SelectedColor; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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); + } + + /// + /// Sets the selected color. + /// + /// Color to set. + /// Determines whether only the hue should be set. + /// Determines whether the "before" color should be set as well. + 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(); + } + } +} diff --git a/Gwen/Control/HorizontalScrollBar.cs b/Gwen/Control/HorizontalScrollBar.cs new file mode 100644 index 0000000..5efae7b --- /dev/null +++ b/Gwen/Control/HorizontalScrollBar.cs @@ -0,0 +1,203 @@ +using System; +using System.Drawing; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Horizontal scrollbar. + /// + public class HorizontalScrollBar : ScrollBar + { + /// + /// Bar size (in pixels). + /// + public override int BarSize + { + get { return m_Bar.Width; } + set { m_Bar.Width = value; } + } + + /// + /// Bar position (in pixels). + /// + public override int BarPos + { + get { return m_Bar.X - Height; } + } + + /// + /// Indicates whether the bar is horizontal. + /// + public override bool IsHorizontal + { + get { return true; } + } + + /// + /// Button size (in pixels). + /// + public override int ButtonSize + { + get { return Height; } + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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; + } + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + 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)); + } + + /// + /// Sets the scroll amount (0-1). + /// + /// Scroll amount. + /// Determines whether the control should be updated. + /// + /// True if control state changed. + /// + 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; + } + + /// + /// Handler for the BarMoved event. + /// + /// Event source. + protected override void OnBarMoved(Base control) + { + if (m_Bar.IsHeld) + { + SetScrollAmount(CalculateScrolledAmount(), false); + base.OnBarMoved(control); + } + else + InvalidateParent(); + } + } +} diff --git a/Gwen/Control/HorizontalSlider.cs b/Gwen/Control/HorizontalSlider.cs new file mode 100644 index 0000000..c945e54 --- /dev/null +++ b/Gwen/Control/HorizontalSlider.cs @@ -0,0 +1,63 @@ +using System; +using System.Drawing; + +namespace Gwen.Control +{ + /// + /// Horizontal slider. + /// + public class HorizontalSlider : Slider + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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); + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + m_SliderBar.SetSize(15, Height); + UpdateBarFromValue(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawSlider(this, true, m_SnapToNotches ? m_NotchCount : 0, m_SliderBar.Width); + } + } +} diff --git a/Gwen/Control/HorizontalSplitter.cs b/Gwen/Control/HorizontalSplitter.cs new file mode 100644 index 0000000..7e73d5a --- /dev/null +++ b/Gwen/Control/HorizontalSplitter.cs @@ -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 + + /// + /// Invoked when one of the panels has been zoomed (maximized). + /// + public event GwenEventHandler PanelZoomed; + + /// + /// Invoked when one of the panels has been unzoomed (restored). + /// + public event GwenEventHandler PanelUnZoomed; + + /// + /// Invoked when the zoomed panel has been changed. + /// + public event GwenEventHandler ZoomChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Centers the panels so that they take even amount of space. + /// + public void CenterPanels() + { + m_VVal = 0.5f; + Invalidate(); + } + + /// + /// Indicates whether any of the panels is zoomed. + /// + public bool IsZoomed { get { return m_ZoomedSection != -1; } } + + /// + /// Gets or sets a value indicating whether splitters should be visible. + /// + public bool SplittersVisible + { + get { return m_VSplitter.ShouldDrawBackground; } + set + { + m_VSplitter.ShouldDrawBackground = value; + } + } + + /// + /// Gets or sets the size of the splitter. + /// + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + } + + /// + /// Assigns a control to the specific inner section. + /// + /// Section index (0-3). + /// Control to assign. + public void SetPanel(int index, Base panel) + { + m_Sections[index] = panel; + + if (panel != null) + { + panel.Dock = Pos.None; + panel.Parent = this; + } + + Invalidate(); + } + + /// + /// Gets the specific inner section. + /// + /// Section index (0-3). + /// Specified section. + public Base GetPanel(int index) + { + return m_Sections[index]; + } + + /// + /// Internal handler for the zoom changed event. + /// + 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); + } + } + + /// + /// Maximizes the specified panel so it fills the entire control. + /// + /// Panel index (0-3). + 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(); + } + + /// + /// Restores the control so all panels are visible. + /// + 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(); + } + } +} diff --git a/Gwen/Control/IColorPicker.cs b/Gwen/Control/IColorPicker.cs new file mode 100644 index 0000000..70916eb --- /dev/null +++ b/Gwen/Control/IColorPicker.cs @@ -0,0 +1,10 @@ +using System; +using System.Drawing; + +namespace Gwen.Control +{ + public interface IColorPicker + { + Color SelectedColor { get; } + } +} diff --git a/Gwen/Control/ImagePanel.cs b/Gwen/Control/ImagePanel.cs new file mode 100644 index 0000000..f2885e3 --- /dev/null +++ b/Gwen/Control/ImagePanel.cs @@ -0,0 +1,77 @@ +using System; +using System.Drawing; + +namespace Gwen.Control +{ + /// + /// Image container. + /// + public class ImagePanel : Base + { + private readonly Texture m_Texture; + private readonly float[] m_uv; + private Color m_DrawColor; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public override void Dispose() + { + m_Texture.Dispose(); + base.Dispose(); + } + + /// + /// Sets the texture coordinates of the image. + /// + 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; + } + + /// + /// Texture name. + /// + public String ImageName + { + get { return m_Texture.Name; } + set { m_Texture.Load(value); } + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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]); + } + + /// + /// Sizes the control to its contents. + /// + public virtual void SizeToContents() + { + SetSize(m_Texture.Width, m_Texture.Height); + } + } +} diff --git a/Gwen/Control/Label.cs b/Gwen/Control/Label.cs new file mode 100644 index 0000000..4c106e9 --- /dev/null +++ b/Gwen/Control/Label.cs @@ -0,0 +1,211 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Static text label. + /// + public class Label : Base + { + private readonly Text m_Text; + private Pos m_Align; + private Padding m_TextPadding; + private bool m_AutoSizeToContents; + + /// + /// Text alignment. + /// + public Pos Alignment { get { return m_Align; } set { m_Align = value; Invalidate(); } } + + /// + /// Text. + /// + public String Text { get { return m_Text.String; } set { SetText(value); } } + + /// + /// Font. + /// + public Font Font + { + get { return m_Text.Font; } + set + { + m_Text.Font = value; + if (m_AutoSizeToContents) + SizeToContents(); + Invalidate(); + } + } + + /// + /// Text color. + /// + public Color TextColor { get { return m_Text.TextColor; } set { m_Text.TextColor = value; } } + + /// + /// Override text color (used by tooltips). + /// + public Color TextColorOverride { get { return m_Text.TextColorOverride; } set { m_Text.TextColorOverride = value; } } + + /// + /// Text override - used to display different string. + /// + public String TextOverride { get { return m_Text.TextOverride; } set { m_Text.TextOverride = value; } } + + /// + /// Width of the text (in pixels). + /// + public int TextWidth { get { return m_Text.Width; } } + + /// + /// Height of the text (in pixels). + /// + public int TextHeight { get { return m_Text.Height; } } + + public int TextX { get { return m_Text.X; } } + public int TextY { get { return m_Text.Y; } } + + /// + /// Text length (in characters). + /// + 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; } + + /// + /// Determines if the control should autosize to its text. + /// + public bool AutoSizeToContents { get { return m_AutoSizeToContents; } set { m_AutoSizeToContents = value; Invalidate(); InvalidateParent(); } } + + /// + /// Text padding. + /// + public Padding TextPadding { get { return m_TextPadding; } set { m_TextPadding = value; Invalidate(); InvalidateParent(); } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Returns index of the character closest to specified point (in canvas coordinates). + /// + /// + /// + /// + protected int GetClosestCharacter(int x, int y) + { + return m_Text.GetClosestCharacter(m_Text.CanvasPosToLocal(new Point(x, y))); + } + + /// + /// Sets the position of the internal text control. + /// + /// + /// + protected void SetTextPosition(int x, int y) + { + m_Text.SetPosition(x, y); + } + + /// + /// Handler for text changed event. + /// + protected virtual void OnTextChanged() + {} + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + + /// + /// Sets the label text. + /// + /// Text to set. + /// Determines whether to invoke "text changed" event. + 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(); + } + + /// + /// Gets the coordinates of specified character. + /// + /// Character index. + /// Character coordinates (local). + 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); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + } + } +} diff --git a/Gwen/Control/LabelClickable.cs b/Gwen/Control/LabelClickable.cs new file mode 100644 index 0000000..7c1f17b --- /dev/null +++ b/Gwen/Control/LabelClickable.cs @@ -0,0 +1,30 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Clickable label (for checkboxes etc). + /// + public class LabelClickable : Button + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public LabelClickable(Base parent) + : base(parent) + { + IsToggle = false; + Alignment = Pos.Left | Pos.CenterV; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + // no button look + } + } +} diff --git a/Gwen/Control/LabeledCheckBox.cs b/Gwen/Control/LabeledCheckBox.cs new file mode 100644 index 0000000..fcb4fb7 --- /dev/null +++ b/Gwen/Control/LabeledCheckBox.cs @@ -0,0 +1,95 @@ +using System; + +namespace Gwen.Control +{ + /// + /// CheckBox with label. + /// + public class LabeledCheckBox : Base + { + private readonly CheckBox m_CheckBox; + private readonly LabelClickable m_Label; + + /// + /// Invoked when the control has been checked. + /// + public event GwenEventHandler Checked; + + /// + /// Invoked when the control has been unchecked. + /// + public event GwenEventHandler UnChecked; + + /// + /// Invoked when the control's check has been changed. + /// + public event GwenEventHandler CheckChanged; + + /// + /// Indicates whether the control is checked. + /// + public bool IsChecked { get { return m_CheckBox.IsChecked; } set { m_CheckBox.IsChecked = value; } } + + /// + /// Label text. + /// + public String Text { get { return m_Label.Text; } set { m_Label.Text = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Handler for CheckChanged event. + /// + 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); + } + + /// + /// Handler for Space keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeySpace(bool down) + { + base.OnKeySpace(down); + if (!down) + m_CheckBox.IsChecked = !m_CheckBox.IsChecked; + return true; + } + } +} diff --git a/Gwen/Control/LabeledRadioButton.cs b/Gwen/Control/LabeledRadioButton.cs new file mode 100644 index 0000000..141244c --- /dev/null +++ b/Gwen/Control/LabeledRadioButton.cs @@ -0,0 +1,93 @@ +using System; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// RadioButton with label. + /// + public class LabeledRadioButton : Base + { + private readonly RadioButton m_RadioButton; + private readonly LabelClickable m_Label; + + /// + /// Label text. + /// + public String Text { get { return m_Label.Text; } set { m_Label.Text = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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); + } + + /// + /// Renders the focus overlay. + /// + /// Skin to use. + 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; } } + + /// + /// Handler for Space keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeySpace(bool down) + { + if (down) + m_RadioButton.IsChecked = !m_RadioButton.IsChecked; + return true; + } + + /// + /// Selects the radio button. + /// + public virtual void Select() + { + m_RadioButton.IsChecked = true; + } + } +} diff --git a/Gwen/Control/Layout/Positioner.cs b/Gwen/Control/Layout/Positioner.cs new file mode 100644 index 0000000..8358ee4 --- /dev/null +++ b/Gwen/Control/Layout/Positioner.cs @@ -0,0 +1,53 @@ +using System; + +namespace Gwen.Control.Layout +{ + /// + /// Helper control that positions its children in a specific way. + /// + public class Positioner : Base + { + private Pos m_Pos; + + /// + /// Children position. + /// + public Pos Pos { get { return m_Pos; } set { m_Pos = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Positioner(Base parent) : base(parent) + { + Pos = Pos.Left | Pos.Top; + } + + /// + /// Function invoked after layout. + /// + /// Skin to use. + protected override void PostLayout(Skin.Base skin) + { + foreach (Base child in Children) // ok? + { + child.Position(m_Pos); + } + } + } + + /// + /// Helper class that centers all its children. + /// + public class Center : Positioner + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Center(Base parent) : base(parent) + { + Pos = Pos.Center; + } + } +} diff --git a/Gwen/Control/Layout/Splitter.cs b/Gwen/Control/Layout/Splitter.cs new file mode 100644 index 0000000..998c1bd --- /dev/null +++ b/Gwen/Control/Layout/Splitter.cs @@ -0,0 +1,95 @@ +using System; + +namespace Gwen.Control.Layout +{ + /// + /// Base splitter class. + /// + public class Splitter : Base + { + private readonly Base[] m_Panel; + private readonly bool[] m_Scale; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Splitter(Base parent) : base(parent) + { + m_Panel = new Base[2]; + m_Scale = new bool[2]; + m_Scale[0] = true; + m_Scale[1] = true; + } + + /// + /// Sets the contents of a splitter panel. + /// + /// Panel index (0-1). + /// Panel contents. + /// Determines whether the content is to be scaled. + 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; + } + } + + /// + /// Gets the contents of a secific panel. + /// + /// Panel index (0-1). + /// + Base GetPanel(int panelIndex) + { + if (panelIndex < 0 || panelIndex > 1) + throw new ArgumentException("Invalid panel index", "panelIndex"); + return m_Panel[panelIndex]; + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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(); + } + } +} diff --git a/Gwen/Control/Layout/Table.cs b/Gwen/Control/Layout/Table.cs new file mode 100644 index 0000000..d2228c2 --- /dev/null +++ b/Gwen/Control/Layout/Table.cs @@ -0,0 +1,247 @@ +using System; +using System.Linq; + +namespace Gwen.Control.Layout +{ + /// + /// Base class for multi-column tables. + /// + 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; + + /// + /// Column count (default 1). + /// + public int ColumnCount { get { return m_ColumnCount; } set { SetColumnCount(value); Invalidate(); } } + + /// + /// Row count. + /// + public int RowCount { get { return Children.Count; } } + + /// + /// Gets or sets default height for new table rows. + /// + public int DefaultRowHeight { get { return m_DefaultRowHeight; } set { m_DefaultRowHeight = value; } } + + /// + /// Returns specific row of the table. + /// + /// Row index. + /// Row at the specified index. + public TableRow this[int index] { get { return Children[index] as TableRow; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Sets the number of columns. + /// + /// Number of columns. + public void SetColumnCount(int count) + { + if (m_ColumnCount == count) return; + foreach (TableRow row in Children.OfType()) + { + row.ColumnCount = count; + } + + m_ColumnCount = count; + } + + /// + /// Sets the column width (in pixels). + /// + /// Column index. + /// Column width. + public void SetColumnWidth(int column, int width) + { + if (m_ColumnWidth[column] == width) + return; + m_ColumnWidth[column] = width; + Invalidate(); + } + + /// + /// Gets the column width (in pixels). + /// + /// Column index. + /// Column width. + public int GetColumnWidth(int column) + { + return m_ColumnWidth[column]; + } + + /// + /// Adds a new empty row. + /// + /// Newly created row. + public TableRow AddRow() + { + TableRow row = new TableRow(this); + row.ColumnCount = m_ColumnCount; + row.Height = m_DefaultRowHeight; + row.Dock = Pos.Top; + return row; + } + + /// + /// Adds a new row. + /// + /// Row to add. + public void AddRow(TableRow row) + { + row.Parent = this; + row.ColumnCount = m_ColumnCount; + row.Height = m_DefaultRowHeight; + row.Dock = Pos.Top; + } + + /// + /// Adds a new row with specified text in first column. + /// + /// Text to add. + /// New row. + public TableRow AddRow(String text) + { + var row = AddRow(); + row.SetCellText(0, text); + return row; + } + + /// + /// Removes a row by reference. + /// + /// Row to remove. + public void RemoveRow(TableRow row) + { + RemoveChild(row, true); + } + + /// + /// Removes a row by index. + /// + /// Row index. + public void RemoveRow(int idx) + { + var row = Children[idx]; + RemoveRow(row as TableRow); + } + + /// + /// Removes all rows. + /// + public void RemoveAll() + { + while (RowCount > 0) + RemoveRow(0); + } + + /// + /// Gets the index of a specified row. + /// + /// Row to search for. + /// Row index if found, -1 otherwise. + public int GetRowIndex(TableRow row) + { + return Children.IndexOf(row); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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; + } + } + + /// + /// Sizes to fit contents. + /// + 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(); + } + } +} diff --git a/Gwen/Control/Layout/TableRow.cs b/Gwen/Control/Layout/TableRow.cs new file mode 100644 index 0000000..9348b26 --- /dev/null +++ b/Gwen/Control/Layout/TableRow.cs @@ -0,0 +1,221 @@ +using System; +using System.Drawing; + +namespace Gwen.Control.Layout +{ + /// + /// Single table row. + /// + 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]; + } + + /// + /// Invoked when the row has been selected. + /// + public event GwenEventHandler Selected; + + /// + /// Column count. + /// + public int ColumnCount { get { return m_ColumnCount; } set { SetColumnCount(value); } } + + /// + /// Indicates whether the row is even or odd (used for alternate coloring). + /// + public bool EvenRow { get { return m_EvenRow; } set { m_EvenRow = value; } } + + /// + /// Text of the first column. + /// + public String Text { get { return GetText(0); } set { SetCellText(0, value); } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public TableRow(Base parent) + : base(parent) + { + m_Columns = new Label[MaxColumns]; + m_ColumnCount = 0; + KeyboardInputEnabled = true; + } + + /// + /// Sets the number of columns. + /// + /// Number of columns. + 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; + } + } + + /// + /// Sets the column width (in pixels). + /// + /// Column index. + /// Column width. + 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; + } + + /// + /// Sets the text of a specified cell. + /// + /// Column number. + /// Text to set. + public void SetCellText(int column, String text) + { + if (null == m_Columns[column]) + return; + + m_Columns[column].Text = text; + } + + /// + /// Sets the contents of a specified cell. + /// + /// Column number. + /// Cell contents. + /// Determines whether mouse input should be enabled for the cell. + 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; + } + + /// + /// Gets the contents of a specified cell. + /// + /// Column number. + /// Control embedded in the cell. + public Base GetCellContents(int column) + { + return m_Columns[column]; + } + + protected virtual void OnRowSelected() + { + if (Selected != null) + Selected.Invoke(this); + } + + /// + /// Sizes all cells to fit contents. + /// + 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); + } + + /// + /// Sets the text color for all cells. + /// + /// Text color. + public void SetTextColor(Color color) + { + for (int i = 0; i < m_ColumnCount; i++) + { + if (null == m_Columns[i]) continue; + m_Columns[i].TextColor = color; + } + } + + /// + /// Returns text of a specified row cell (default first). + /// + /// Column index. + /// Column cell text. + public String GetText(int column = 0) + { + return m_Columns[column].Text; + } + + /// + /// Handler for Copy event. + /// + /// Source control. + protected override void OnCopy(Base from) + { + Platform.Neutral.SetClipboardText(Text); + } + } +} diff --git a/Gwen/Control/ListBox.cs b/Gwen/Control/ListBox.cs new file mode 100644 index 0000000..e6159ec --- /dev/null +++ b/Gwen/Control/ListBox.cs @@ -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 +{ + /// + /// ListBox control. + /// + public class ListBox : ScrollControl + { + private readonly Table m_Table; + private readonly List m_SelectedRows; + + private bool m_MultiSelect; + private bool m_IsToggle; + private bool m_SizeToContents; + private Pos m_OldDock; // used while autosizing + + /// + /// Determines whether multiple rows can be selected at once. + /// + public bool AllowMultiSelect + { + get { return m_MultiSelect; } + set + { + m_MultiSelect = value; + if (value) + IsToggle = true; + } + } + + /// + /// Determines whether rows can be unselected by clicking on them again. + /// + public bool IsToggle { get { return m_IsToggle; } set { m_IsToggle = value; } } + + /// + /// Number of rows in the list box. + /// + public int RowCount { get { return m_Table.RowCount; } } + + /// + /// Returns specific row of the ListBox. + /// + /// Row index. + /// Row at the specified index. + public ListBoxRow this[int index] { get { return m_Table[index] as ListBoxRow; } } + + /// + /// List of selected rows. + /// + public IEnumerable SelectedRows { get { return m_SelectedRows; } } + + /// + /// First selected row (and only if list is not multiselectable). + /// + public TableRow SelectedRow + { + get + { + if (m_SelectedRows.Count == 0) + return null; + return m_SelectedRows[0]; + } + } + + /// + /// Gets the selected row number. + /// + public int SelectedRowIndex + { + get + { + var selected = SelectedRow; + if (selected == null) + return -1; + return m_Table.GetRowIndex(selected); + } + } + + /// + /// Column count of table rows. + /// + public int ColumnCount { get { return m_Table.ColumnCount; } set { m_Table.ColumnCount = value; Invalidate(); } } + + /// + /// Invoked when a row has been selected. + /// + public event GwenEventHandler RowSelected; + + /// + /// Invoked whan a row has beed unselected. + /// + public event GwenEventHandler RowUnselected; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public ListBox(Base parent) + : base(parent) + { + m_SelectedRows = new List(); + + 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; + } + + /// + /// Selects the specified row by index. + /// + /// Row to select. + /// Determines whether to deselect previously selected rows. + public void SelectRow(int index, bool clearOthers = false) + { + if (index < 0 || index >= m_Table.RowCount) + return; + + SelectRow(m_Table.Children[index], clearOthers); + } + + /// + /// Selects the specified row(s) by text. + /// + /// Text to search for (exact match). + /// Determines whether to deselect previously selected rows. + public void SelectRows(String rowText, bool clearOthers = false) + { + var rows = m_Table.Children.OfType().Where(x => x.Text == rowText); + foreach (ListBoxRow row in rows) + { + SelectRow(row, clearOthers); + } + } + + /// + /// Selects the specified row(s) by regex text search. + /// + /// Regex pattern to search for. + /// Regex options. + /// Determines whether to deselect previously selected rows. + public void SelectRowsByRegex(String pattern, RegexOptions regexOptions = RegexOptions.None, bool clearOthers = false) + { + var rows = m_Table.Children.OfType().Where(x => Regex.IsMatch(x.Text, pattern) ); + foreach (ListBoxRow row in rows) + { + SelectRow(row, clearOthers); + } + } + + /// + /// Slelects the specified row. + /// + /// Row to select. + /// Determines whether to deselect previously selected rows. + 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); + } + + /// + /// Removes the specified row by index. + /// + /// Row index. + public void RemoveRow(int idx) + { + m_Table.RemoveRow(idx); // this calls Dispose() + } + + /// + /// Adds a new row. + /// + /// Row text. + /// Newly created control. + public TableRow AddRow(String label) + { + return AddRow(label, String.Empty); + } + + /// + /// Adds a new row. + /// + /// Row text. + /// Internal control name. + /// Newly created control. + 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; + } + + /// + /// Sets the column width (in pixels). + /// + /// Column index. + /// Column width. + public void SetColumnWidth(int column, int width) + { + m_Table.SetColumnWidth(column, width); + Invalidate(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawListBox(this); + } + + /// + /// Deselects all rows. + /// + public virtual void UnselectAll() + { + foreach (ListBoxRow row in m_SelectedRows) + { + row.IsSelected = false; + if (RowUnselected != null) + RowUnselected.Invoke(this); + } + m_SelectedRows.Clear(); + } + + /// + /// Unselects the specified row. + /// + /// Row to unselect. + public void UnselectRow(ListBoxRow row) + { + row.IsSelected = false; + m_SelectedRows.Remove(row); + + if (RowUnselected != null) + RowUnselected.Invoke(this); + } + + /// + /// Handler for the row selection event. + /// + /// Event source. + 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); + } + } + + /// + /// Removes all rows. + /// + 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(); + } + } + } +} diff --git a/Gwen/Control/ListBoxRow.cs b/Gwen/Control/ListBoxRow.cs new file mode 100644 index 0000000..159f3e9 --- /dev/null +++ b/Gwen/Control/ListBoxRow.cs @@ -0,0 +1,66 @@ +using System; +using System.Drawing; +using Gwen.Control.Layout; + +namespace Gwen.Control +{ + /// + /// List box row (selectable). + /// + public class ListBoxRow : TableRow + { + private bool m_Selected; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public ListBoxRow(Base parent) + : base(parent) + { + MouseInputEnabled = true; + IsSelected = false; + } + + /// + /// Indicates whether the control is selected. + /// + 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); + } + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawListBoxLine(this, IsSelected, EvenRow); + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + if (down) + { + //IsSelected = true; // [omeg] ListBox manages that + OnRowSelected(); + } + } + } +} diff --git a/Gwen/Control/Menu.cs b/Gwen/Control/Menu.cs new file mode 100644 index 0000000..2b78723 --- /dev/null +++ b/Gwen/Control/Menu.cs @@ -0,0 +1,209 @@ +using System; +using System.Drawing; +using System.Linq; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Popup menu. + /// + 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; } } + + /// + /// Determines whether the menu should be disposed on close. + /// + public bool DeleteOnClose { get { return m_DeleteOnClose; } set { m_DeleteOnClose = value; } } + + /// + /// Determines whether the menu should open on mouse hover. + /// + protected virtual bool ShouldHoverOpenMenu { get { return true; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Menu(Base parent) + : base(parent) + { + SetBounds(0, 0, 10, 10); + Padding = Padding.Two; + IconMarginDisabled = false; + + AutoHideBars = true; + EnableScroll(false, true); + DeleteOnClose = false; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawMenu(this, IconMarginDisabled); + } + + /// + /// Renders under the actual control (shadows etc). + /// + /// Skin to use. + protected override void RenderUnder(Skin.Base skin) + { + base.RenderUnder(skin); + skin.DrawShadow(this); + } + + /// + /// Opens the menu. + /// + /// Unused. + public void Open(Pos pos) + { + IsHidden = false; + BringToFront(); + Point mouse = Input.InputHandler.MousePosition; + SetPosition(mouse.X, mouse.Y); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + + /// + /// Adds a new menu item. + /// + /// Item text. + /// Newly created control. + public virtual MenuItem AddItem(String text) + { + return AddItem(text, String.Empty); + } + + /// + /// Adds a new menu item. + /// + /// Item text. + /// Icon texture name. + /// Accelerator for this item. + /// Newly created control. + 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; + } + + /// + /// Add item handler. + /// + /// Item added. + 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); + } + + /// + /// Closes all submenus. + /// + public virtual void CloseAll() + { + //System.Diagnostics.Debug.Print("Menu.CloseAll: {0}", this); + Children.ForEach(child => { if (child is MenuItem) (child as MenuItem).CloseMenu(); }); + } + + /// + /// Indicates whether any (sub)menu is open. + /// + /// + public virtual bool IsMenuOpen() + { + return Children.Any(child => { if (child is MenuItem) return (child as MenuItem).IsMenuOpen; return false; }); + } + + /// + /// Mouse hover handler. + /// + /// Event source. + 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(); + } + + /// + /// Closes the current menu. + /// + public virtual void Close() + { + //System.Diagnostics.Debug.Print("Menu.Close: {0}", this); + IsHidden = true; + if (DeleteOnClose) + { + DelayedDelete(); + } + } + + /// + /// Closes all submenus and the current menu. + /// + public override void CloseMenus() + { + //System.Diagnostics.Debug.Print("Menu.CloseMenus: {0}", this); + base.CloseMenus(); + CloseAll(); + Close(); + } + + /// + /// Adds a divider menu item. + /// + public virtual void AddDivider() + { + MenuDivider divider = new MenuDivider(this); + divider.Dock = Pos.Top; + divider.Margin = new Margin(IconMarginDisabled ? 0 : 24, 0, 4, 0); + } + } +} diff --git a/Gwen/Control/MenuItem.cs b/Gwen/Control/MenuItem.cs new file mode 100644 index 0000000..0dd26ce --- /dev/null +++ b/Gwen/Control/MenuItem.cs @@ -0,0 +1,255 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Menu item. + /// + 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; + + /// + /// Indicates whether the item is on a menu strip. + /// + public bool IsOnStrip { get { return m_OnStrip; } set { m_OnStrip = value; } } + + /// + /// Determines if the menu item is checkable. + /// + public bool IsCheckable { get { return m_Checkable; } set { m_Checkable = value; } } + + /// + /// Indicates if the parent menu is open. + /// + public bool IsMenuOpen { get { if (m_Menu == null) return false; return !m_Menu.IsHidden; } } + + /// + /// Gets or sets the check value. + /// + 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); + } + } + } + + /// + /// Gets the parent menu. + /// + 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; + } + } + + /// + /// Invoked when the item is selected. + /// + public event GwenEventHandler Selected; + + /// + /// Invoked when the item is checked. + /// + public event GwenEventHandler Checked; + + /// + /// Invoked when the item is unchecked. + /// + public event GwenEventHandler UnChecked; + + /// + /// Invoked when the item's check value is changed. + /// + public event GwenEventHandler CheckChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public MenuItem(Base parent) + : base(parent) + { + m_OnStrip = false; + IsTabable = false; + IsCheckable = false; + IsChecked = false; + + m_Accelerator = new Label(this); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawMenuItem(this, IsMenuOpen, m_Checkable ? m_Checked : false); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + if (m_SubmenuArrow != null) + { + m_SubmenuArrow.Position(Pos.Right | Pos.CenterV, 4, 0); + } + base.Layout(skin); + } + + /// + /// Internal OnPressed implementation. + /// + 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(); + } + + /// + /// Toggles the menu open state. + /// + public void ToggleMenu() + { + if (IsMenuOpen) + CloseMenu(); + else + OpenMenu(); + } + + /// + /// Opens the menu. + /// + 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... + } + + /// + /// Closes the menu. + /// + 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 + } + } +} diff --git a/Gwen/Control/MenuStrip.cs b/Gwen/Control/MenuStrip.cs new file mode 100644 index 0000000..8052470 --- /dev/null +++ b/Gwen/Control/MenuStrip.cs @@ -0,0 +1,78 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Menu strip. + /// + public class MenuStrip : Menu + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public MenuStrip(Base parent) + : base(parent) + { + SetBounds(0, 0, 200, 22); + Dock = Pos.Top; + m_InnerPanel.Padding = new Padding(5, 0, 0, 0); + } + + /// + /// Closes the current menu. + /// + public override void Close() + { + + } + + /// + /// Renders under the actual control (shadows etc). + /// + /// Skin to use. + protected override void RenderUnder(Skin.Base skin) + { + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawMenuStrip(this); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + //TODO: We don't want to do vertical sizing the same as Menu, do nothing for now + } + + /// + /// Determines whether the menu should open on mouse hover. + /// + protected override bool ShouldHoverOpenMenu + { + get { return IsMenuOpen(); } + } + + /// + /// Add item handler. + /// + /// Item added. + 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; + } + } +} diff --git a/Gwen/Control/MessageBox.cs b/Gwen/Control/MessageBox.cs new file mode 100644 index 0000000..47bc204 --- /dev/null +++ b/Gwen/Control/MessageBox.cs @@ -0,0 +1,67 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Simple message box. + /// + public class MessageBox : WindowControl + { + private readonly Button m_Button; + private readonly Label m_Label; // should be rich label with maxwidth = parent + + /// + /// Invoked when the message box has been dismissed. + /// + public GwenEventHandler Dismissed; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + /// Message to display. + /// Window caption. + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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(); + } + } +} diff --git a/Gwen/Control/NumericUpDown.cs b/Gwen/Control/NumericUpDown.cs new file mode 100644 index 0000000..c2ff1ff --- /dev/null +++ b/Gwen/Control/NumericUpDown.cs @@ -0,0 +1,152 @@ +using System; +using Gwen.Control.Layout; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Numeric up/down. + /// + 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; + + /// + /// Minimum value. + /// + public int Min { get { return m_Min; } set { m_Min = value; } } + + /// + /// Maximum value. + /// + public int Max { get { return m_Max; } set { m_Max = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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"; + } + + /// + /// Invoked when the value has been changed. + /// + public event GwenEventHandler ValueChanged; + + /// + /// Handler for Up Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyUp(bool down) + { + if (down) OnButtonUp(null); + return true; + } + + /// + /// Handler for Down Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyDown(bool down) + { + if (down) OnButtonDown(null); + return true; + } + + /// + /// Handler for the button up event. + /// + /// Event source. + protected virtual void OnButtonUp(Base control) + { + Value = m_Value + 1; + } + + /// + /// Handler for the button down event. + /// + /// Event source. + protected virtual void OnButtonDown(Base control) + { + Value = m_Value - 1; + } + + /// + /// Determines whether the text can be assighed to the control. + /// + /// Text to evaluate. + /// True if the text is allowed. + 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; + } + + /// + /// Numeric value of the control. + /// + 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; + } + } + + /// + /// Handler for the text changed event. + /// + protected override void OnTextChanged() + { + base.OnTextChanged(); + if (ValueChanged != null) + ValueChanged.Invoke(this); + } + } +} diff --git a/Gwen/Control/ProgressBar.cs b/Gwen/Control/ProgressBar.cs new file mode 100644 index 0000000..811ed98 --- /dev/null +++ b/Gwen/Control/ProgressBar.cs @@ -0,0 +1,72 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Progress bar. + /// + public class ProgressBar : Label + { + private bool m_Horizontal; + private bool m_AutoLabel; + private float m_Progress; + + /// + /// Determines whether the control is horizontal. + /// + public bool IsHorizontal { get { return m_Horizontal; } set { m_Horizontal = value; } } + + /// + /// Progress value (0-1). + /// + 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() + "%"; + } + } + } + + /// + /// Determines whether the label text is autogenerated from value. + /// + public bool AutoLabel { get { return m_AutoLabel; } set { m_AutoLabel = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawProgressBar(this, m_Horizontal, m_Progress); + } + } +} diff --git a/Gwen/Control/Properties.cs b/Gwen/Control/Properties.cs new file mode 100644 index 0000000..da9c031 --- /dev/null +++ b/Gwen/Control/Properties.cs @@ -0,0 +1,108 @@ +using System; +//using System.Windows.Forms; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Properties table. + /// + public class Properties : Base + { + private readonly SplitterBar m_SplitterBar; + + /// + /// Returns the width of the first column (property names). + /// + public int SplitWidth { get { return m_SplitterBar.X; } } // todo: rename? + + /// + /// Invoked when a property value has been changed. + /// + public event GwenEventHandler ValueChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Function invoked after layout. + /// + /// Skin to use. + protected override void PostLayout(Skin.Base skin) + { + m_SplitterBar.Height = 0; + + if (SizeToChildren(false, true)) + { + InvalidateParent(); + } + + m_SplitterBar.SetSize(3, Height); + } + + /// + /// Handles the splitter moved event. + /// + /// Event source. + protected virtual void OnSplitterMoved(Base control) + { + InvalidateChildren(); + } + + /// + /// Adds a new text property row. + /// + /// Property name. + /// Initial value. + /// Newly created row. + public PropertyRow Add(String label, String value="") + { + return Add(label, new Property.Text(this), value); + } + + /// + /// Adds a new property row. + /// + /// Property name. + /// Property control. + /// Initial value. + /// Newly created row. + 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); + } + + /// + /// Deletes all rows. + /// + public void DeleteAll() + { + m_InnerPanel.DeleteAllChildren(); + } + } +} diff --git a/Gwen/Control/Property/Base.cs b/Gwen/Control/Property/Base.cs new file mode 100644 index 0000000..2084615 --- /dev/null +++ b/Gwen/Control/Property/Base.cs @@ -0,0 +1,55 @@ +using System; + +namespace Gwen.Control.Property +{ + /// + /// Base control for property entry. + /// + public class Base : Control.Base + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Base(Control.Base parent) : base(parent) + { + Height = 17; + } + + /// + /// Invoked when the property value has been changed. + /// + public event GwenEventHandler ValueChanged; + + /// + /// Property value (todo: always string, which is ugly. do something about it). + /// + public virtual String Value { get { return null; } set { SetValue(value, false); } } + + /// + /// Indicates whether the property value is being edited. + /// + 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(); + } + + /// + /// Sets the property value. + /// + /// Value to set. + /// Determines whether to fire "value changed" event. + public virtual void SetValue(String value, bool fireEvents = false) + { + + } + } +} diff --git a/Gwen/Control/Property/Check.cs b/Gwen/Control/Property/Check.cs new file mode 100644 index 0000000..35b56bc --- /dev/null +++ b/Gwen/Control/Property/Check.cs @@ -0,0 +1,71 @@ +using System; + +namespace Gwen.Control.Property +{ + /// + /// Checkable property. + /// + public class Check : Base + { + protected readonly Control.CheckBox m_CheckBox; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Property value. + /// + public override string Value + { + get { return m_CheckBox.IsChecked ? "1" : "0"; } + set { base.Value = value; } + } + + /// + /// Sets the property value. + /// + /// Value to set. + /// Determines whether to fire "value changed" event. + 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; + } + } + + /// + /// Indicates whether the property value is being edited. + /// + public override bool IsEditing + { + get { return m_CheckBox.HasFocus; } + } + + /// + /// Indicates whether the control is hovered by mouse pointer. + /// + public override bool IsHovered + { + get { return base.IsHovered || m_CheckBox.IsHovered; } + } + } +} diff --git a/Gwen/Control/Property/Color.cs b/Gwen/Control/Property/Color.cs new file mode 100644 index 0000000..42d8421 --- /dev/null +++ b/Gwen/Control/Property/Color.cs @@ -0,0 +1,126 @@ +using System; +using Gwen.ControlInternal; +using Gwen.Input; + +namespace Gwen.Control.Property +{ + /// + /// Color property. + /// + public class Color : Text + { + protected readonly ColorButton m_Button; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Color-select button press handler. + /// + /// Event source. + 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); + } + + /// + /// Color changed handler. + /// + /// Event source. + protected virtual void OnColorChanged(Control.Base control) + { + HSVColorPicker picker = control as HSVColorPicker; + SetTextFromColor(picker.SelectedColor); + DoChanged(); + } + + /// + /// Property value. + /// + public override string Value + { + get { return m_TextBox.Text; } + set { base.Value = value; } + } + + /// + /// Sets the property value. + /// + /// Value to set. + /// Determines whether to fire "value changed" event. + public override void SetValue(string value, bool fireEvents = false) + { + m_TextBox.SetText(value, fireEvents); + } + + /// + /// Indicates whether the property value is being edited. + /// + 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(); + } + } +} \ No newline at end of file diff --git a/Gwen/Control/Property/Text.cs b/Gwen/Control/Property/Text.cs new file mode 100644 index 0000000..46f2ea7 --- /dev/null +++ b/Gwen/Control/Property/Text.cs @@ -0,0 +1,59 @@ +using System; + +namespace Gwen.Control.Property +{ + /// + /// Text property. + /// + public class Text : Base + { + protected readonly TextBox m_TextBox; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Property value. + /// + public override string Value + { + get { return m_TextBox.Text; } + set { base.Value = value; } + } + + /// + /// Sets the property value. + /// + /// Value to set. + /// Determines whether to fire "value changed" event. + public override void SetValue(string value, bool fireEvents = false) + { + m_TextBox.SetText(value, fireEvents); + } + + /// + /// Indicates whether the property value is being edited. + /// + public override bool IsEditing + { + get { return m_TextBox.HasFocus; } + } + + /// + /// Indicates whether the control is hovered by mouse pointer. + /// + public override bool IsHovered + { + get { return base.IsHovered | m_TextBox.IsHovered; } + } + } +} diff --git a/Gwen/Control/PropertyRow.cs b/Gwen/Control/PropertyRow.cs new file mode 100644 index 0000000..f55b175 --- /dev/null +++ b/Gwen/Control/PropertyRow.cs @@ -0,0 +1,123 @@ +using System; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Single property row. + /// + public class PropertyRow : Base + { + private readonly Label m_Label; + private readonly Property.Base m_Property; + private bool m_LastEditing; + private bool m_LastHover; + + /// + /// Invoked when the property value has changed. + /// + public event GwenEventHandler ValueChanged; + + /// + /// Indicates whether the property value is being edited. + /// + public bool IsEditing { get { return m_Property != null && m_Property.IsEditing; } } + + /// + /// Property value. + /// + public String Value { get { return m_Property.Value; } set { m_Property.Value = value; } } + + /// + /// Indicates whether the control is hovered by mouse pointer. + /// + public override bool IsHovered + { + get + { + return base.IsHovered || (m_Property != null && m_Property.IsHovered); + } + } + + /// + /// Property name. + /// + public String Label { get { return m_Label.Text; } set { m_Label.Text = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + /// Property control associated with this row. + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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(); + } + } +} diff --git a/Gwen/Control/PropertyTree.cs b/Gwen/Control/PropertyTree.cs new file mode 100644 index 0000000..1d20f9a --- /dev/null +++ b/Gwen/Control/PropertyTree.cs @@ -0,0 +1,38 @@ +using System; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Property table/tree. + /// + public class PropertyTree : TreeControl + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public PropertyTree(Base parent) + : base(parent) + { + + } + + /// + /// Adds a new properties node. + /// + /// Node label. + /// Newly created control + 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; + } + } +} diff --git a/Gwen/Control/RadioButton.cs b/Gwen/Control/RadioButton.cs new file mode 100644 index 0000000..69494c8 --- /dev/null +++ b/Gwen/Control/RadioButton.cs @@ -0,0 +1,39 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Radio button. + /// + public class RadioButton : CheckBox + { + /// + /// Determines whether unchecking is allowed. + /// + protected override bool AllowUncheck + { + get { return false; } + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public RadioButton(Base parent) + : base(parent) + { + SetSize(15, 15); + MouseInputEnabled = true; + IsTabable = false; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawRadioButton(this, IsChecked, IsDepressed); + } + } +} diff --git a/Gwen/Control/RadioButtonGroup.cs b/Gwen/Control/RadioButtonGroup.cs new file mode 100644 index 0000000..302af0a --- /dev/null +++ b/Gwen/Control/RadioButtonGroup.cs @@ -0,0 +1,137 @@ +using System; +using System.Linq; + +namespace Gwen.Control +{ + /// + /// Radio button group. + /// + public class RadioButtonGroup : GroupBox + { + private LabeledRadioButton m_Selected; + + /// + /// Selected radio button. + /// + public LabeledRadioButton Selected { get { return m_Selected; } } + + /// + /// Internal name of the selected radio button. + /// + public String SelectedName { get { return m_Selected.Name; } } + + /// + /// Text of the selected radio button. + /// + public String SelectedLabel { get { return m_Selected.Text; } } + + /// + /// Index of the selected radio button. + /// + public int SelectedIndex { get { return Children.IndexOf(m_Selected); } } + + /// + /// Invoked when the selected option has changed. + /// + public event GwenEventHandler SelectionChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + /// Label for the outlining GroupBox. + public RadioButtonGroup(Base parent, String label) + : base(parent) + { + IsTabable = false; + KeyboardInputEnabled = true; + Text = label; + AutoSizeToContents = true; + } + + /// + /// Adds a new option. + /// + /// Option text. + /// Newly created control. + public virtual LabeledRadioButton AddOption(String text) + { + return AddOption(text, String.Empty); + } + + /// + /// Adds a new option. + /// + /// Option text. + /// Internal name. + /// Newly created control. + 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; + } + + /// + /// Handler for the option change. + /// + /// Event source. + protected virtual void OnRadioClicked(Base fromPanel) + { + RadioButton @checked = fromPanel as RadioButton; + foreach (LabeledRadioButton rb in Children.OfType()) // todo: optimize + { + if (rb.RadioButton == @checked) + m_Selected = rb; + else + rb.RadioButton.IsChecked = false; + } + + OnChanged(); + } + /* + /// + /// Sizes to contents. + /// + 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); + } + + /// + /// Selects the specified option. + /// + /// Option to select. + public void SetSelection(int index) + { + if (index < 0 || index >= Children.Count) + return; + + (Children[index] as LabeledRadioButton).RadioButton.Press(); + } + } +} diff --git a/Gwen/Control/ResizableControl.cs b/Gwen/Control/ResizableControl.cs new file mode 100644 index 0000000..4e30fbb --- /dev/null +++ b/Gwen/Control/ResizableControl.cs @@ -0,0 +1,160 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Base resizable control. + /// + public class ResizableControl : Base + { + private bool m_ClampMovement; + private readonly Resizer[] m_Resizer; + + /// + /// Determines whether control's position should be restricted to its parent bounds. + /// + public bool ClampMovement { get { return m_ClampMovement; } set { m_ClampMovement = value; } } + + /// + /// Invoked when the control has been resized. + /// + public event GwenEventHandler Resized; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Handler for the resized event. + /// + /// Event source. + protected virtual void OnResized(Base control) + { + if (Resized != null) + Resized.Invoke(this); + } + + protected Resizer GetResizer(int i) + { + return m_Resizer[i]; + } + + /// + /// Disables resizing. + /// + 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); + } + } + + /// + /// Enables resizing. + /// + 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 + } + } + + /// + /// Sets the control bounds. + /// + /// X position. + /// Y position. + /// Width. + /// Height. + /// + /// True if bounds changed. + /// + 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); + } + } +} diff --git a/Gwen/Control/RichLabel.cs b/Gwen/Control/RichLabel.cs new file mode 100644 index 0000000..8400161 --- /dev/null +++ b/Gwen/Control/RichLabel.cs @@ -0,0 +1,254 @@ +using System; +using System.Collections.Generic; +using System.Drawing; + +namespace Gwen.Control +{ + /// + /// Multiline label with text chunks having different color/font. + /// + 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 m_TextBlocks; + private readonly String[] newline; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public RichLabel(Base parent) + : base(parent) + { + newline = new string[] { Environment.NewLine }; + m_TextBlocks = new List(); + } + + /// + /// Adds a line break to the control. + /// + public void AddLineBreak() + { + TextBlock block = new TextBlock { Type = BlockType.NewLine }; + m_TextBlocks.Add(block); + } + + /// + /// Adds text to the control. + /// + /// Text to add. + /// Text color. + /// Font to use. + 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(); + } + } + + /// + /// Resizes the control to fit its children. + /// + /// Determines whether to change control's width. + /// Determines whether to change control's height. + /// + /// True if bounds changed. + /// + 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; + } + + /// + /// Handler invoked when control's bounds change. + /// + /// Old bounds. + protected override void OnBoundsChanged(Rectangle oldBounds) + { + base.OnBoundsChanged(oldBounds); + Rebuild(); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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; + } + } + } +} diff --git a/Gwen/Control/ScrollBar.cs b/Gwen/Control/ScrollBar.cs new file mode 100644 index 0000000..97ee009 --- /dev/null +++ b/Gwen/Control/ScrollBar.cs @@ -0,0 +1,133 @@ +using System; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Base class for scrollbars. + /// + 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; + + /// + /// Invoked when the bar is moved. + /// + public event GwenEventHandler BarMoved; + + /// + /// Bar size (in pixels). + /// + public virtual int BarSize { get; set; } + + /// + /// Bar position (in pixels). + /// + public virtual int BarPos { get { return 0; } } + + /// + /// Button size (in pixels). + /// + 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; } } + + /// + /// Indicates whether the bar is horizontal. + /// + public virtual bool IsHorizontal { get { return false; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Sets the scroll amount (0-1). + /// + /// Scroll amount. + /// Determines whether the control should be updated. + /// True if control state changed. + public virtual bool SetScrollAmount(float value, bool forceUpdate = false) + { + if (m_ScrollAmount == value && !forceUpdate) + return false; + m_ScrollAmount = value; + Invalidate(); + OnBarMoved(this); + return true; + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawScrollBar(this, IsHorizontal, m_Depressed); + } + + /// + /// Handler for the BarMoved event. + /// + /// The control. + 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() { } + } +} diff --git a/Gwen/Control/ScrollControl.cs b/Gwen/Control/ScrollControl.cs new file mode 100644 index 0000000..48ab029 --- /dev/null +++ b/Gwen/Control/ScrollControl.cs @@ -0,0 +1,301 @@ +using System; +using System.Linq; + +namespace Gwen.Control +{ + /// + /// Base for controls whose interior can be scrolled. + /// + 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; + + /// + /// Indicates whether the control can be scrolled horizontally. + /// + public bool CanScrollH { get { return m_CanScrollH; } } + + /// + /// Indicates whether the control can be scrolled vertically. + /// + public bool CanScrollV { get { return m_CanScrollV; } } + + /// + /// Determines whether the scroll bars should be hidden if not needed. + /// + public bool AutoHideBars { get { return m_AutoHideBars; } set { m_AutoHideBars = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + } + } + + /// + /// Enables or disables inner scrollbars. + /// + /// Determines whether the horizontal scrollbar should be enabled. + /// Determines whether the vertical scrollbar should be enabled. + 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(); + } + + /// + /// Handler invoked when control children's bounds change. + /// + /// + /// + protected override void OnChildBoundsChanged(System.Drawing.Rectangle oldChildBounds, Base child) + { + UpdateScrollBars(); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + UpdateScrollBars(); + base.Layout(skin); + } + + /// + /// Handler invoked on mouse wheel event. + /// + /// Scroll delta. + /// + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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(); + } + } +} diff --git a/Gwen/Control/Slider.cs b/Gwen/Control/Slider.cs new file mode 100644 index 0000000..501f17e --- /dev/null +++ b/Gwen/Control/Slider.cs @@ -0,0 +1,236 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Base slider. + /// + 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; + + /// + /// Number of notches on the slider axis. + /// + public int NotchCount { get { return m_NotchCount; } set { m_NotchCount = value; } } + + /// + /// Determines whether the slider should snap to notches. + /// + public bool SnapToNotches { get { return m_SnapToNotches; } set { m_SnapToNotches = value; } } + + /// + /// Minimum value. + /// + public float Min { get { return m_Min; } set { SetRange(value, m_Max); } } + + /// + /// Maximum value. + /// + public float Max { get { return m_Max; } set { SetRange(m_Min, value); } } + + /// + /// Current value. + /// + 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(); + } + } + + /// + /// Invoked when the value has been changed. + /// + public event GwenEventHandler ValueChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Handler for Right Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyRight(bool down) + { + if (down) + Value = Value + 1; + return true; + } + + /// + /// Handler for Up Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyUp(bool down) + { + if (down) + Value = Value + 1; + return true; + } + + /// + /// Handler for Left Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyLeft(bool down) + { + if (down) + Value = Value - 1; + return true; + } + + /// + /// Handler for Down Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyDown(bool down) + { + if (down) + Value = Value - 1; + return true; + } + + /// + /// Handler for Home keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyHome(bool down) + { + if (down) + Value = m_Min; + return true; + } + + /// + /// Handler for End keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyEnd(bool down) + { + if (down) + Value = m_Max; + return true; + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + + } + + 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(); + } + + /// + /// Sets the value range. + /// + /// Minimum value. + /// Maximum value. + public void SetRange(float min, float max) + { + m_Min = min; + m_Max = max; + } + + /// + /// Renders the focus overlay. + /// + /// Skin to use. + protected override void RenderFocus(Skin.Base skin) + { + if (InputHandler.KeyboardFocus != this) return; + if (!IsTabable) return; + + skin.DrawKeyboardHighlight(this, RenderBounds, 0); + } + } +} diff --git a/Gwen/Control/StatusBar.cs b/Gwen/Control/StatusBar.cs new file mode 100644 index 0000000..bbc58c4 --- /dev/null +++ b/Gwen/Control/StatusBar.cs @@ -0,0 +1,44 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Status bar. + /// + public class StatusBar : Label + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Adds a control to the bar. + /// + /// Control to add. + /// Determines whether the control should be added to the right side of the bar. + public void AddControl(Base control, bool right) + { + control.Parent = this; + control.Dock = right ? Pos.Right : Pos.Left; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawStatusBar(this); + } + } +} diff --git a/Gwen/Control/TabButton.cs b/Gwen/Control/TabButton.cs new file mode 100644 index 0000000..e587672 --- /dev/null +++ b/Gwen/Control/TabButton.cs @@ -0,0 +1,203 @@ +using System; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Tab header. + /// + public class TabButton : Button + { + private Base m_Page; + private TabControl m_Control; + + /// + /// Indicates whether the tab is active. + /// + 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; + } + } + + /// + /// Interior of the tab. + /// + public Base Page { get { return m_Page; } set { m_Page = value; } } + + /// + /// Determines whether the control should be clipped to its bounds while rendering. + /// + protected override bool ShouldClip + { + get { return false; } + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawTabButton(this, IsActive, m_Control.TabStrip.Dock); + } + + /// + /// Handler for Down Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyDown(bool down) + { + OnKeyRight(down); + return true; + } + + /// + /// Handler for Up Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyUp(bool down) + { + OnKeyLeft(down); + return true; + } + + /// + /// Handler for Right Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for Left Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Updates control colors. + /// + 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; + } + } +} diff --git a/Gwen/Control/TabControl.cs b/Gwen/Control/TabControl.cs new file mode 100644 index 0000000..8bd5a33 --- /dev/null +++ b/Gwen/Control/TabControl.cs @@ -0,0 +1,250 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Control with multiple tabs that can be reordered and dragged. + /// + public class TabControl : Base + { + private readonly TabStrip m_TabStrip; + private readonly ScrollBarButton[] m_Scroll; + private TabButton m_CurrentButton; + private int m_ScrollOffset; + + /// + /// Invoked when a tab has been added. + /// + public event GwenEventHandler TabAdded; + + /// + /// Invoked when a tab has been removed. + /// + public event GwenEventHandler TabRemoved; + + /// + /// Determines if tabs can be reordered by dragging. + /// + public bool AllowReorder { get { return m_TabStrip.AllowReorder; } set { m_TabStrip.AllowReorder = value; } } + + /// + /// Currently active tab button. + /// + public TabButton CurrentButton { get { return m_CurrentButton; } } + + /// + /// Current tab strip position. + /// + public Pos TabStripPosition { get { return m_TabStrip.StripPosition; }set { m_TabStrip.StripPosition = value; } } + + /// + /// Tab strip. + /// + public TabStrip TabStrip { get { return m_TabStrip; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Adds a new page/tab. + /// + /// Tab label. + /// Page contents. + /// Newly created control. + 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; + } + + /// + /// Adds a page/tab. + /// + /// Page to add. (well, it's a TabButton which is a parent to the page). + 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; + } + + /// + /// Handler for tab selection. + /// + /// Event source (TabButton). + 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(); + } + + /// + /// Function invoked after layout. + /// + /// Skin to use. + protected override void PostLayout(Skin.Base skin) + { + base.PostLayout(skin); + HandleOverflow(); + } + + /// + /// Handler for tab removing. + /// + /// + 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(); + } + + /// + /// Number of tabs in the control. + /// + 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; + } + } +} diff --git a/Gwen/Control/TabStrip.cs b/Gwen/Control/TabStrip.cs new file mode 100644 index 0000000..3e86c89 --- /dev/null +++ b/Gwen/Control/TabStrip.cs @@ -0,0 +1,204 @@ +using System; +using System.Drawing; +using Gwen.ControlInternal; +using Gwen.DragDrop; + +namespace Gwen.Control +{ + /// + /// Tab strip - groups TabButtons and allows reordering. + /// + public class TabStrip : Base + { + private Base m_TabDragControl; + private bool m_AllowReorder; + + /// + /// Determines whether it is possible to reorder tabs by mouse dragging. + /// + public bool AllowReorder { get { return m_AllowReorder; } set { m_AllowReorder = value; } } + + /// + /// Determines whether the control should be clipped to its bounds while rendering. + /// + protected override bool ShouldClip + { + get { return false; } + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public TabStrip(Base parent) + : base(parent) + { + m_AllowReorder = false; + } + + /// + /// Strip position (top/left/right/bottom). + /// + 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; + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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(); + } + } + } +} diff --git a/Gwen/Control/TabTitleBar.cs b/Gwen/Control/TabTitleBar.cs new file mode 100644 index 0000000..fcf1aa5 --- /dev/null +++ b/Gwen/Control/TabTitleBar.cs @@ -0,0 +1,41 @@ +using System; +using Gwen.DragDrop; + +namespace Gwen.Control +{ + /// + /// Titlebar for DockedTabControl. + /// + 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"); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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(); + } + } +} diff --git a/Gwen/Control/TextBox.cs b/Gwen/Control/TextBox.cs new file mode 100644 index 0000000..81cf90e --- /dev/null +++ b/Gwen/Control/TextBox.cs @@ -0,0 +1,607 @@ +using System; +using System.Drawing; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Text box (editable). + /// + 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; } } + + /// + /// Determines whether text should be selected when the control is focused. + /// + public bool SelectAllOnFocus { get { return m_SelectAll; } set { m_SelectAll = value; if (value) OnSelectAll(this); } } + + /// + /// Indicates whether the text has active selection. + /// + public bool HasSelection { get { return m_CursorPos != m_CursorEnd; } } + + /// + /// Invoked when the text has changed. + /// + public event GwenEventHandler TextChanged; + + /// + /// Invoked when the submit key has been pressed. + /// + public event GwenEventHandler SubmitPressed; + + /// + /// Current cursor position (character index). + /// + 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(); + } + } + + /// + /// Determines whether the control can insert text at a given cursor position. + /// + /// Text to check. + /// Cursor position. + /// True if allowed. + protected virtual bool IsTextAllowed(String text, int position) + { + return true; + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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); + } + + /// + /// Renders the focus overlay. + /// + /// Skin to use. + protected override void RenderFocus(Skin.Base skin) + { + // nothing + } + + /// + /// Handler for text changed event. + /// + 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); + } + + /// + /// Handler for character input event. + /// + /// Character typed. + /// + /// True if handled. + /// + protected override bool OnChar(char chr) + { + base.OnChar(chr); + + if (chr == '\t') return false; + + InsertText(chr.ToString()); + return true; + } + + /// + /// Inserts text at current cursor position, erasing selection if any. + /// + /// Text to insert. + 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(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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(); + } + + /// + /// Handler for Paste event. + /// + /// Source control. + protected override void OnPaste(Base from) + { + base.OnPaste(from); + InsertText(Platform.Neutral.GetClipboardText()); + } + + /// + /// Handler for Copy event. + /// + /// Source control. + protected override void OnCopy(Base from) + { + if (!HasSelection) return; + base.OnCopy(from); + + Platform.Neutral.SetClipboardText(GetSelection()); + } + + /// + /// Handler for Cut event. + /// + /// Source control. + protected override void OnCut(Base from) + { + if (!HasSelection) return; + base.OnCut(from); + + Platform.Neutral.SetClipboardText(GetSelection()); + EraseSelection(); + } + + /// + /// Handler for Select All event. + /// + /// Source control. + protected override void OnSelectAll(Base from) + { + //base.OnSelectAll(from); + m_CursorEnd = 0; + m_CursorPos = TextLength; + + RefreshCursorBounds(); + } + + /// + /// Handler invoked on mouse double click (left) event. + /// + /// X coordinate. + /// Y coordinate. + protected override void OnMouseDoubleClickedLeft(int x, int y) + { + //base.OnMouseDoubleClickedLeft(x, y); + OnSelectAll(this); + } + + /// + /// Handler for Return keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for Backspace keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for Delete keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for Left Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for Right Arrow keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for Home keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + 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; + } + + /// + /// Handler for End keyboard event. + /// + /// Indicates whether the key was pressed or released. + /// + /// True if handled. + /// + protected override bool OnKeyEnd(bool down) + { + base.OnKeyEnd(down); + m_CursorPos = TextLength; + + if (!Input.InputHandler.IsShiftDown) + { + m_CursorEnd = m_CursorPos; + } + + RefreshCursorBounds(); + return true; + } + + /// + /// Returns currently selected text. + /// + /// Current selection. + 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); + } + + /// + /// Deletes text. + /// + /// Starting cursor position. + /// Length in characters. + 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; + } + + /// + /// Deletes selected text. + /// + 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; + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + 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; + } + } + } + + /// + /// Handler invoked on mouse moved event. + /// + /// X coordinate. + /// Y coordinate. + /// X change. + /// Y change. + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + base.Layout(skin); + + RefreshCursorBounds(); + } + + /// + /// Handler for the return key. + /// + protected virtual void OnReturn() + { + if (SubmitPressed != null) + SubmitPressed.Invoke(this); + } + } +} diff --git a/Gwen/Control/TextBoxNumeric.cs b/Gwen/Control/TextBoxNumeric.cs new file mode 100644 index 0000000..f1b24ef --- /dev/null +++ b/Gwen/Control/TextBoxNumeric.cs @@ -0,0 +1,85 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Numeric text box - accepts only float numbers. + /// + public class TextBoxNumeric : TextBox + { + /// + /// Current numeric value. + /// + protected float m_Value; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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); + } + + /// + /// Determines whether the control can insert text at a given cursor position. + /// + /// Text to check. + /// Cursor position. + /// True if allowed. + protected override bool IsTextAllowed(String text, int position) + { + String newText = Text.Insert(position, text); + return IsTextAllowed(newText); + } + + /// + /// Current numerical value. + /// + public virtual float Value + { + get { return m_Value; } + set + { + m_Value = value; + Text = value.ToString(); + } + } + + // text -> value + /// + /// Handler for text changed event. + /// + protected override void OnTextChanged() + { + if (String.IsNullOrEmpty(Text) || Text == "-") + { + m_Value = 0; + //SetText("0"); + } + else + m_Value = float.Parse(Text); + base.OnTextChanged(); + } + + /// + /// Sets the control text. + /// + /// Text to set. + /// Determines whether to invoke "text changed" event. + public override void SetText(string str, bool doEvents = true) + { + if (IsTextAllowed(str)) + base.SetText(str, doEvents); + } + } +} diff --git a/Gwen/Control/TextBoxPassword.cs b/Gwen/Control/TextBoxPassword.cs new file mode 100644 index 0000000..aa7e267 --- /dev/null +++ b/Gwen/Control/TextBoxPassword.cs @@ -0,0 +1,41 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Text box with masked text. + /// + /// + /// This class doesn't prevent programatic access to the text in any way. + /// + public class TextBoxPassword : TextBox + { + private String m_Mask; + private char m_MaskCharacter; + + /// + /// Character used in place of actual characters for display. + /// + public char MaskCharacter { get { return m_MaskCharacter; } set { m_MaskCharacter = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public TextBoxPassword(Base parent) + : base(parent) + { + m_MaskCharacter = '*'; + } + + /// + /// Handler for text changed event. + /// + protected override void OnTextChanged() + { + m_Mask = new string(MaskCharacter, Text.Length); + TextOverride = m_Mask; + base.OnTextChanged(); + } + } +} diff --git a/Gwen/Control/TreeControl.cs b/Gwen/Control/TreeControl.cs new file mode 100644 index 0000000..53f2d7f --- /dev/null +++ b/Gwen/Control/TreeControl.cs @@ -0,0 +1,95 @@ +using System; + +namespace Gwen.Control +{ + /// + /// Tree control. + /// + public class TreeControl : TreeNode + { + private readonly ScrollControl m_ScrollControl; + private bool m_MultiSelect; + + /// + /// Determines if multiple nodes can be selected at the same time. + /// + public bool AllowMultiSelect { get { return m_MultiSelect; } set { m_MultiSelect = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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? + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + if (ShouldDrawBackground) + skin.DrawTreeControl(this); + } + + /// + /// Handler invoked when control children's bounds change. + /// + /// + /// + protected override void OnChildBoundsChanged(System.Drawing.Rectangle oldChildBounds, Base child) + { + if (m_ScrollControl != null) + m_ScrollControl.UpdateScrollBars(); + } + + /// + /// Removes all child nodes. + /// + public virtual void RemoveAll() + { + m_ScrollControl.DeleteAll(); + } + + /// + /// Handler for node added event. + /// + /// Node added. + public virtual void OnNodeAdded(TreeNode node) + { + node.LabelPressed += OnNodeSelected; + } + + /// + /// Handler for node selected event. + /// + /// Node selected. + protected virtual void OnNodeSelected(Base Control) + { + if (!m_MultiSelect /*|| InputHandler.InputHandler.IsKeyDown(Key.Control)*/) + UnselectAll(); + } + } +} diff --git a/Gwen/Control/TreeNode.cs b/Gwen/Control/TreeNode.cs new file mode 100644 index 0000000..bdc02cb --- /dev/null +++ b/Gwen/Control/TreeNode.cs @@ -0,0 +1,326 @@ +using System; +using System.Linq; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Tree control node. + /// + 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; + + /// + /// Indicates whether this is a root node. + /// + public bool IsRoot { get { return m_Root; } set { m_Root = value; } } + + /// + /// Parent tree control. + /// + public TreeControl TreeControl { get { return m_TreeControl; } set { m_TreeControl = value; } } + + /// + /// Determines whether the node is selectable. + /// + public bool IsSelectable { get { return m_Selectable; } set { m_Selectable = value; } } + + /// + /// Indicates whether the node is selected. + /// + 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); + } + } + } + + /// + /// Node's label. + /// + public String Text { get { return m_Title.Text; } set { m_Title.Text = value; } } + + /// + /// Invoked when the node label has been pressed. + /// + public event GwenEventHandler LabelPressed; + + /// + /// Invoked when the node's selected state has changed. + /// + public event GwenEventHandler SelectionChanged; + + /// + /// Invoked when the node has been selected. + /// + public event GwenEventHandler Selected; + + /// + /// Invoked when the node has been unselected. + /// + public event GwenEventHandler Unselected; + + /// + /// Invoked when the node has been expanded. + /// + public event GwenEventHandler Expanded; + + /// + /// Invoked when the node has been collapsed. + /// + public event GwenEventHandler Collapsed; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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 + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + + /// + /// Function invoked after layout. + /// + /// Skin to use. + protected override void PostLayout(Skin.Base skin) + { + if (SizeToChildren(false, true)) + { + InvalidateParent(); + } + } + + /// + /// Adds a new child node. + /// + /// Node's label. + /// Newly created control. + 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; + } + + /// + /// Opens the node. + /// + 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(); + } + + /// + /// Closes the node. + /// + 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(); + } + + /// + /// Opens the node and all child nodes. + /// + public void ExpandAll() + { + Open(); + foreach (Base child in Children) + { + TreeNode node = child as TreeNode; + if (node == null) + continue; + node.ExpandAll(); + } + } + + /// + /// Clears the selection on the node and all child nodes. + /// + 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(); + } + } + + /// + /// Handler for the toggle button. + /// + /// Event source. + protected virtual void OnToggleButtonPress(Base control) + { + if (m_ToggleButton.ToggleState) + { + Open(); + } + else + { + Close(); + } + } + + /// + /// Handler for label double click. + /// + /// Event source. + protected virtual void OnDoubleClickName(Base control) + { + if (!m_ToggleButton.IsVisible) + return; + m_ToggleButton.Toggle(); + } + + /// + /// Handler for label click. + /// + /// Event source. + protected virtual void OnClickName(Base control) + { + if (LabelPressed != null) + LabelPressed.Invoke(this); + IsSelected = !IsSelected; + } + } +} diff --git a/Gwen/Control/VerticalScrollBar.cs b/Gwen/Control/VerticalScrollBar.cs new file mode 100644 index 0000000..d62f0f6 --- /dev/null +++ b/Gwen/Control/VerticalScrollBar.cs @@ -0,0 +1,193 @@ +using System; +using System.Drawing; +using Gwen.Input; + +namespace Gwen.Control +{ + /// + /// Vertical scrollbar. + /// + public class VerticalScrollBar : ScrollBar + { + /// + /// Bar size (in pixels). + /// + public override int BarSize + { + get { return m_Bar.Height; } + set { m_Bar.Height = value; } + } + + /// + /// Bar position (in pixels). + /// + public override int BarPos + { + get { return m_Bar.Y - Width; } + } + + /// + /// Button size (in pixels). + /// + public override int ButtonSize + { + get { return Width; } + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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; + } + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + 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)); + } + + /// + /// Sets the scroll amount (0-1). + /// + /// Scroll amount. + /// Determines whether the control should be updated. + /// True if control state changed. + 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; + } + + /// + /// Handler for the BarMoved event. + /// + /// The control. + protected override void OnBarMoved(Base control) + { + if (m_Bar.IsHeld) + { + SetScrollAmount(CalculateScrolledAmount(), false); + base.OnBarMoved(control); + } + else + InvalidateParent(); + } + } +} diff --git a/Gwen/Control/VerticalSlider.cs b/Gwen/Control/VerticalSlider.cs new file mode 100644 index 0000000..0a0a0ff --- /dev/null +++ b/Gwen/Control/VerticalSlider.cs @@ -0,0 +1,63 @@ +using System; +using System.Drawing; + +namespace Gwen.Control +{ + /// + /// Vertical slider. + /// + public class VerticalSlider : Slider + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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))); + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + m_SliderBar.SetSize(Width, 15); + UpdateBarFromValue(); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawSlider(this, false, m_SnapToNotches ? m_NotchCount : 0, m_SliderBar.Height); + } + } +} diff --git a/Gwen/Control/VerticalSplitter.cs b/Gwen/Control/VerticalSplitter.cs new file mode 100644 index 0000000..f98a121 --- /dev/null +++ b/Gwen/Control/VerticalSplitter.cs @@ -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 + + /// + /// Invoked when one of the panels has been zoomed (maximized). + /// + public event GwenEventHandler PanelZoomed; + + /// + /// Invoked when one of the panels has been unzoomed (restored). + /// + public event GwenEventHandler PanelUnZoomed; + + /// + /// Invoked when the zoomed panel has been changed. + /// + public event GwenEventHandler ZoomChanged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Centers the panels so that they take even amount of space. + /// + public void CenterPanels() + { + m_HVal = 0.5f; + Invalidate(); + } + + public void SetHValue(float value) + { + if (value <= 1f || value >= 0) + m_HVal = value; + } + + /// + /// Indicates whether any of the panels is zoomed. + /// + public bool IsZoomed { get { return m_ZoomedSection != -1; } } + + /// + /// Gets or sets a value indicating whether splitters should be visible. + /// + public bool SplittersVisible + { + get { return m_HSplitter.ShouldDrawBackground; } + set + { + m_HSplitter.ShouldDrawBackground = value; + } + } + + /// + /// Gets or sets the size of the splitter. + /// + 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); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + } + + /// + /// Assigns a control to the specific inner section. + /// + /// Section index (0-3). + /// Control to assign. + public void SetPanel(int index, Base panel) + { + m_Sections[index] = panel; + + if (panel != null) + { + panel.Dock = Pos.None; + panel.Parent = this; + } + + Invalidate(); + } + + /// + /// Gets the specific inner section. + /// + /// Section index (0-3). + /// Specified section. + public Base GetPanel(int index) + { + return m_Sections[index]; + } + + /// + /// Internal handler for the zoom changed event. + /// + 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); + } + } + + /// + /// Maximizes the specified panel so it fills the entire control. + /// + /// Panel index (0-3). + 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(); + } + + /// + /// Restores the control so all panels are visible. + /// + 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(); + } + } +} diff --git a/Gwen/Control/WindowControl.cs b/Gwen/Control/WindowControl.cs new file mode 100644 index 0000000..55a2772 --- /dev/null +++ b/Gwen/Control/WindowControl.cs @@ -0,0 +1,177 @@ +using System; +using System.Drawing; +using System.Linq; +using Gwen.ControlInternal; + +namespace Gwen.Control +{ + /// + /// Movable window with title bar. + /// + 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; + + /// + /// Window caption. + /// + public String Caption { get { return m_Caption.Text; } set { m_Caption.Text = value; } } + + /// + /// Determines whether the window has close button. + /// + public bool IsClosable { get { return !m_CloseButton.IsHidden; } set { m_CloseButton.IsHidden = !value; } } + + /// + /// Determines whether the control should be disposed on close. + /// + public bool DeleteOnClose { get { return m_DeleteOnClose; } set { m_DeleteOnClose = value; } } + + /// + /// Indicates whether the control is hidden. + /// + public override bool IsHidden + { + get { return base.IsHidden; } + set + { + if (!value) + BringToFront(); + base.IsHidden = value; + } + } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + /// Window caption. + /// Determines whether the window should be modal. + 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); + } + } + + /// + /// Makes the window modal: covers the whole canvas and gets all input. + /// + /// Determines whether all the background should be dimmed. + 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; + } + + /// + /// Indicates whether the control is on top of its parent's children. + /// + public override bool IsOnTop + { + get { return Parent.Children.Where(x => x is WindowControl).Last() == this; } + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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); + } + + /// + /// Renders under the actual control (shadows etc). + /// + /// Skin to use. + protected override void RenderUnder(Skin.Base skin) + { + base.RenderUnder(skin); + skin.DrawShadow(this); + } + + public override void Touch() + { + base.Touch(); + BringToFront(); + } + + /// + /// Renders the focus overlay. + /// + /// Skin to use. + protected override void RenderFocus(Skin.Base skin) + { + + } + } +} diff --git a/Gwen/ControlInternal/CategoryButton.cs b/Gwen/ControlInternal/CategoryButton.cs new file mode 100644 index 0000000..19fb026 --- /dev/null +++ b/Gwen/ControlInternal/CategoryButton.cs @@ -0,0 +1,87 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Item in CollapsibleCategory. + /// + public class CategoryButton : Button + { + internal bool m_Alt; // for alternate coloring + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public CategoryButton(Base parent) : base(parent) + { + Alignment = Pos.Left | Pos.CenterV; + m_Alt = false; + IsToggle = true; + TextPadding = new Padding(3, 0, 3, 0); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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); + } + + /// + /// Updates control colors. + /// + 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; + } + } +} diff --git a/Gwen/ControlInternal/CategoryHeaderButton.cs b/Gwen/ControlInternal/CategoryHeaderButton.cs new file mode 100644 index 0000000..b7bc45d --- /dev/null +++ b/Gwen/ControlInternal/CategoryHeaderButton.cs @@ -0,0 +1,35 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Header of CollapsibleCategory. + /// + public class CategoryHeaderButton : Button + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public CategoryHeaderButton(Base parent) + : base(parent) + { + ShouldDrawBackground = false; + IsToggle = true; + Alignment = Pos.Center; + TextPadding = new Padding(3, 0, 3, 0); + } + + /// + /// Updates control colors. + /// + public override void UpdateColors() + { + if (IsDepressed || ToggleState) + TextColor = Skin.Colors.Category.Header_Closed; + else + TextColor = Skin.Colors.Category.Header; + } + } +} diff --git a/Gwen/ControlInternal/CloseButton.cs b/Gwen/ControlInternal/CloseButton.cs new file mode 100644 index 0000000..ad92770 --- /dev/null +++ b/Gwen/ControlInternal/CloseButton.cs @@ -0,0 +1,33 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Window close button. + /// + public class CloseButton : Button + { + private readonly WindowControl m_Window; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + /// Window that owns this button. + public CloseButton(Base parent, WindowControl owner) + : base(parent) + { + m_Window = owner; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawWindowCloseButton(this, IsDepressed && IsHovered, IsHovered && ShouldDrawHover, !m_Window.IsOnTop); + } + } +} diff --git a/Gwen/ControlInternal/ColorButton.cs b/Gwen/ControlInternal/ColorButton.cs new file mode 100644 index 0000000..49f89c1 --- /dev/null +++ b/Gwen/ControlInternal/ColorButton.cs @@ -0,0 +1,39 @@ +using System; +using System.Drawing; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Property button. + /// + public class ColorButton : Button + { + private Color m_Color; + + /// + /// Current color value. + /// + public Color Color { get { return m_Color; } set { m_Color = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public ColorButton(Base parent) : base(parent) + { + m_Color = Color.Black; + Text = String.Empty; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.Renderer.DrawColor = m_Color; + skin.Renderer.DrawFilledRect(RenderBounds); + } + } +} diff --git a/Gwen/ControlInternal/ColorDisplay.cs b/Gwen/ControlInternal/ColorDisplay.cs new file mode 100644 index 0000000..100cbbf --- /dev/null +++ b/Gwen/ControlInternal/ColorDisplay.cs @@ -0,0 +1,45 @@ +using System; +using System.Drawing; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Color square. + /// + public class ColorDisplay : Base + { + private Color m_Color; + //private bool m_DrawCheckers; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public ColorDisplay(Base parent) : base(parent) + { + SetSize(32, 32); + m_Color = Color.FromArgb(255, 255, 0, 0); + //m_DrawCheckers = true; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawColorDisplay(this, m_Color); + } + + /// + /// Current color. + /// + 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); } } + } +} diff --git a/Gwen/ControlInternal/DownArrow.cs b/Gwen/ControlInternal/DownArrow.cs new file mode 100644 index 0000000..cccbd31 --- /dev/null +++ b/Gwen/ControlInternal/DownArrow.cs @@ -0,0 +1,35 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// ComboBox arrow. + /// + public class DownArrow : Base + { + private readonly ComboBox m_ComboBox; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public DownArrow(ComboBox parent) + : base(parent) // or Base? + { + MouseInputEnabled = false; + SetSize(15, 15); + + m_ComboBox = parent; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawComboBoxArrow(this, m_ComboBox.IsHovered, m_ComboBox.IsDepressed, m_ComboBox.IsOpen, m_ComboBox.IsDisabled); + } + } +} diff --git a/Gwen/ControlInternal/Dragger.cs b/Gwen/ControlInternal/Dragger.cs new file mode 100644 index 0000000..781edaf --- /dev/null +++ b/Gwen/ControlInternal/Dragger.cs @@ -0,0 +1,96 @@ +using System; +using System.Drawing; +using Gwen.Control; +using Gwen.Input; + +namespace Gwen.ControlInternal +{ + /// + /// Base for controls that can be dragged by mouse. + /// + 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; } } + + /// + /// Indicates if the control is being dragged. + /// + public bool IsHeld { get { return m_Held; } } + + /// + /// Event invoked when the control position has been changed. + /// + public event GwenEventHandler Dragged; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Dragger(Base parent) : base(parent) + { + MouseInputEnabled = true; + m_Held = false; + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + 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; + } + } + + /// + /// Handler invoked on mouse moved event. + /// + /// X coordinate. + /// Y coordinate. + /// X change. + /// Y change. + 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); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + + } + } +} diff --git a/Gwen/ControlInternal/Highlight.cs b/Gwen/ControlInternal/Highlight.cs new file mode 100644 index 0000000..adad6f0 --- /dev/null +++ b/Gwen/ControlInternal/Highlight.cs @@ -0,0 +1,29 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Drag&drop highlight. + /// + public class Highlight : Base + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Highlight(Base parent) : base(parent) + { + + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawHighlight(this); + } + } +} diff --git a/Gwen/ControlInternal/MenuDivider.cs b/Gwen/ControlInternal/MenuDivider.cs new file mode 100644 index 0000000..8d5e9e9 --- /dev/null +++ b/Gwen/ControlInternal/MenuDivider.cs @@ -0,0 +1,30 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Divider menu item. + /// + public class MenuDivider : Base + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public MenuDivider(Base parent) + : base(parent) + { + Height = 1; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawMenuDivider(this); + } + } +} diff --git a/Gwen/ControlInternal/Modal.cs b/Gwen/ControlInternal/Modal.cs new file mode 100644 index 0000000..1362f93 --- /dev/null +++ b/Gwen/ControlInternal/Modal.cs @@ -0,0 +1,42 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Modal control for windows. + /// + public class Modal : Base + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Modal(Base parent) + : base(parent) + { + KeyboardInputEnabled = true; + MouseInputEnabled = true; + ShouldDrawBackground = true; + SetBounds(0, 0, GetCanvas().Width, GetCanvas().Height); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + SetBounds(0, 0, GetCanvas().Width, GetCanvas().Height); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawModalControl(this); + } + } +} diff --git a/Gwen/ControlInternal/PropertyRowLabel.cs b/Gwen/ControlInternal/PropertyRowLabel.cs new file mode 100644 index 0000000..d03a339 --- /dev/null +++ b/Gwen/ControlInternal/PropertyRowLabel.cs @@ -0,0 +1,50 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Label for PropertyRow. + /// + public class PropertyRowLabel : Label + { + private readonly PropertyRow m_PropertyRow; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public PropertyRowLabel(PropertyRow parent) + : base(parent) + { + Alignment = Pos.Left | Pos.CenterV; + m_PropertyRow = parent; + } + + /// + /// Updates control colors. + /// + 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; + } + } +} diff --git a/Gwen/ControlInternal/PropertyTreeNode.cs b/Gwen/ControlInternal/PropertyTreeNode.cs new file mode 100644 index 0000000..683c4fd --- /dev/null +++ b/Gwen/ControlInternal/PropertyTreeNode.cs @@ -0,0 +1,30 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Properties node. + /// + public class PropertyTreeNode : TreeNode + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public PropertyTreeNode(Base parent) + : base(parent) + { + m_Title.TextColorOverride = Skin.Colors.Properties.Title; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawPropertyTreeNode(this, m_InnerPanel.X, m_InnerPanel.Y); + } + } +} diff --git a/Gwen/ControlInternal/Resizer.cs b/Gwen/ControlInternal/Resizer.cs new file mode 100644 index 0000000..6400ca9 --- /dev/null +++ b/Gwen/ControlInternal/Resizer.cs @@ -0,0 +1,154 @@ +using System; +using System.Drawing; +//using System.Windows.Forms; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Grab point for resizing. + /// + public class Resizer : Dragger + { + private Pos m_ResizeDir; + + /// + /// Invoked when the control has been resized. + /// + public event GwenEventHandler Resized; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public Resizer(Base parent) + : base(parent) + { + m_ResizeDir = Pos.Left; + MouseInputEnabled = true; + SetSize(6, 6); + Target = parent; + } + + /// + /// Handler invoked on mouse moved event. + /// + /// X coordinate. + /// Y coordinate. + /// X change. + /// Y change. + 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); + } + + /// + /// Gets or sets the sizing direction. + /// + 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; + } + } + } + } +} diff --git a/Gwen/ControlInternal/RightArrow.cs b/Gwen/ControlInternal/RightArrow.cs new file mode 100644 index 0000000..008d73d --- /dev/null +++ b/Gwen/ControlInternal/RightArrow.cs @@ -0,0 +1,30 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Submenu indicator. + /// + public class RightArrow : Base + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public RightArrow(Base parent) + : base(parent) + { + MouseInputEnabled = false; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawMenuRightArrow(this); + } + } +} diff --git a/Gwen/ControlInternal/ScrollBarBar.cs b/Gwen/ControlInternal/ScrollBarBar.cs new file mode 100644 index 0000000..884347b --- /dev/null +++ b/Gwen/ControlInternal/ScrollBarBar.cs @@ -0,0 +1,85 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Scrollbar bar. + /// + public class ScrollBarBar : Dragger + { + private bool m_Horizontal; + + /// + /// Indicates whether the bar is horizontal. + /// + public bool IsHorizontal { get { return m_Horizontal; } set { m_Horizontal = value; } } + + /// + /// Indicates whether the bar is vertical. + /// + public bool IsVertical { get { return !m_Horizontal; } set { m_Horizontal = !value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public ScrollBarBar(Base parent) + : base(parent) + { + RestrictToParent = true; + Target = this; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawScrollBarBar(this, m_Held, IsHovered, m_Horizontal); + base.Render(skin); + } + + /// + /// Handler invoked on mouse moved event. + /// + /// X coordinate. + /// Y coordinate. + /// X change. + /// Y change. + protected override void OnMouseMoved(int x, int y, int dx, int dy) + { + base.OnMouseMoved(x, y, dx, dy); + if (!m_Held) + return; + + InvalidateParent(); + } + + /// + /// Handler invoked on mouse click (left) event. + /// + /// X coordinate. + /// Y coordinate. + /// If set to true mouse button is down. + protected override void OnMouseClickedLeft(int x, int y, bool down) + { + base.OnMouseClickedLeft(x, y, down); + InvalidateParent(); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + 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); + } + } +} diff --git a/Gwen/ControlInternal/ScrollBarButton.cs b/Gwen/ControlInternal/ScrollBarButton.cs new file mode 100644 index 0000000..55ac7c8 --- /dev/null +++ b/Gwen/ControlInternal/ScrollBarButton.cs @@ -0,0 +1,52 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Scrollbar button. + /// + public class ScrollBarButton : Button + { + private Pos m_Direction; + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawScrollButton(this, m_Direction, IsDepressed, IsHovered, IsDisabled); + } + } +} diff --git a/Gwen/ControlInternal/SliderBar.cs b/Gwen/ControlInternal/SliderBar.cs new file mode 100644 index 0000000..1582467 --- /dev/null +++ b/Gwen/ControlInternal/SliderBar.cs @@ -0,0 +1,38 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Slider bar. + /// + public class SliderBar : Dragger + { + private bool m_bHorizontal; + + /// + /// Indicates whether the bar is horizontal. + /// + public bool IsHorizontal { get { return m_bHorizontal; } set { m_bHorizontal = value; } } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public SliderBar(Base parent) + : base(parent) + { + Target = this; + RestrictToParent = true; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawSliderButton(this, IsHeld, IsHorizontal); + } + } +} diff --git a/Gwen/ControlInternal/SplitterBar.cs b/Gwen/ControlInternal/SplitterBar.cs new file mode 100644 index 0000000..ef791a1 --- /dev/null +++ b/Gwen/ControlInternal/SplitterBar.cs @@ -0,0 +1,41 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Splitter bar. + /// + public class SplitterBar : Dragger + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public SplitterBar(Base parent) + : base(parent) + { + Target = this; + RestrictToParent = true; + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + if (ShouldDrawBackground) + skin.DrawButton(this, true, false, IsDisabled); + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + MoveTo(X, Y); + } + } +} diff --git a/Gwen/ControlInternal/TabControlInner.cs b/Gwen/ControlInternal/TabControlInner.cs new file mode 100644 index 0000000..2b4b9ec --- /dev/null +++ b/Gwen/ControlInternal/TabControlInner.cs @@ -0,0 +1,28 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Inner panel of tab control. + /// + public class TabControlInner : Base + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + internal TabControlInner(Base parent) : base(parent) + { + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawTabControl(this); + } + } +} diff --git a/Gwen/ControlInternal/Text.cs b/Gwen/ControlInternal/Text.cs new file mode 100644 index 0000000..9edec6f --- /dev/null +++ b/Gwen/ControlInternal/Text.cs @@ -0,0 +1,210 @@ +//#define DEBUG_TEXT_MEASURE + +using System; +using System.Drawing; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Displays text. Always sized to contents. + /// + public class Text : Base + { + private String m_String; + private Font m_Font; + + /// + /// Font used to display the text. + /// + /// + /// The font is not being disposed by this class. + /// + public Font Font + { + get { return m_Font; } + set + { + m_Font = value; + SizeToContents(); + } + } + + /// + /// Text to display. + /// + public String String + { + get { return m_String; } + set + { + m_String = value; + SizeToContents(); + } + } + + /// + /// Text color. + /// + public Color TextColor { get; set; } + + /// + /// Determines whether the control should be automatically resized to fit the text. + /// + //public bool AutoSizeToContents { get; set; } // [omeg] added + + /// + /// Text length in characters. + /// + public int Length { get { return String.Length; } } + + /// + /// Text color override - used by tooltips. + /// + public Color TextColorOverride { get; set; } + + /// + /// Text override - used to display different string. + /// + public String TextOverride { get; set; } + + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + 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 + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + 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 + } + + /// + /// Lays out the control's interior according to alignment, padding, dock etc. + /// + /// Skin to use. + protected override void Layout(Skin.Base skin) + { + SizeToContents(); + base.Layout(skin); + } + + /// + /// Handler invoked when control's scale changes. + /// + protected override void OnScaleChanged() + { + Invalidate(); + } + + /// + /// Sizes the control to its contents. + /// + 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(); + } + + /// + /// Gets the coordinates of specified character in the text. + /// + /// Character index. + /// Character position in local coordinates. + 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; + } + + /// + /// Searches for a character closest to given point. + /// + /// Point. + /// Character index. + 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; + } + } +} diff --git a/Gwen/ControlInternal/TreeNodeLabel.cs b/Gwen/ControlInternal/TreeNodeLabel.cs new file mode 100644 index 0000000..d1c9102 --- /dev/null +++ b/Gwen/ControlInternal/TreeNodeLabel.cs @@ -0,0 +1,50 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Tree node label. + /// + public class TreeNodeLabel : Button + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public TreeNodeLabel(Base parent) + : base(parent) + { + Alignment = Pos.Left | Pos.CenterV; + ShouldDrawBackground = false; + Height = 16; + TextPadding = new Padding(3, 0, 3, 0); + } + + /// + /// Updates control colors. + /// + 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; + } + } +} diff --git a/Gwen/ControlInternal/TreeToggleButton.cs b/Gwen/ControlInternal/TreeToggleButton.cs new file mode 100644 index 0000000..71d0b6a --- /dev/null +++ b/Gwen/ControlInternal/TreeToggleButton.cs @@ -0,0 +1,40 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Tree node toggle button (the little plus sign). + /// + public class TreeToggleButton : Button + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public TreeToggleButton(Base parent) + : base(parent) + { + IsToggle = true; + IsTabable = false; + } + + /// + /// Renders the focus overlay. + /// + /// Skin to use. + protected override void RenderFocus(Skin.Base skin) + { + + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawTreeButton(this, ToggleState); + } + } +} diff --git a/Gwen/ControlInternal/UpDownButton_Down.cs b/Gwen/ControlInternal/UpDownButton_Down.cs new file mode 100644 index 0000000..6968783 --- /dev/null +++ b/Gwen/ControlInternal/UpDownButton_Down.cs @@ -0,0 +1,30 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Numeric down arrow. + /// + internal class UpDownButton_Down : Button + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public UpDownButton_Down(Base parent) + : base(parent) + { + SetSize(7, 7); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawNumericUpDownButton(this, IsDepressed, false); + } + } +} diff --git a/Gwen/ControlInternal/UpDownButton_Up.cs b/Gwen/ControlInternal/UpDownButton_Up.cs new file mode 100644 index 0000000..a2f6bb4 --- /dev/null +++ b/Gwen/ControlInternal/UpDownButton_Up.cs @@ -0,0 +1,30 @@ +using System; +using Gwen.Control; + +namespace Gwen.ControlInternal +{ + /// + /// Numeric up arrow. + /// + public class UpDownButton_Up : Button + { + /// + /// Initializes a new instance of the class. + /// + /// Parent control. + public UpDownButton_Up(Base parent) + : base(parent) + { + SetSize(7, 7); + } + + /// + /// Renders the control using specified skin. + /// + /// Skin to use. + protected override void Render(Skin.Base skin) + { + skin.DrawNumericUpDownButton(this, IsDepressed, true); + } + } +} diff --git a/Gwen/DragDrop/DragAndDrop.cs b/Gwen/DragDrop/DragAndDrop.cs new file mode 100644 index 0000000..fd6ba8f --- /dev/null +++ b/Gwen/DragDrop/DragAndDrop.cs @@ -0,0 +1,244 @@ +using System; +using System.Drawing; +//using System.Windows.Forms; +using Gwen.Control; +using Gwen.Input; + +namespace Gwen.DragDrop +{ + /// + /// Drag and drop handling. + /// + 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; + } + } +} diff --git a/Gwen/DragDrop/Package.cs b/Gwen/DragDrop/Package.cs new file mode 100644 index 0000000..0947483 --- /dev/null +++ b/Gwen/DragDrop/Package.cs @@ -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; + } +} diff --git a/Gwen/Font.cs b/Gwen/Font.cs new file mode 100644 index 0000000..6d468a9 --- /dev/null +++ b/Gwen/Font.cs @@ -0,0 +1,98 @@ +using System; + +namespace Gwen +{ + /// + /// Represents font resource. + /// + public class Font : IDisposable + { + /// + /// Font face name. Exact meaning depends on renderer. + /// + public String FaceName { get; set; } + + /// + /// Font size. + /// + public int Size { get; set; } + + /// + /// Enables or disables font smoothing (default: disabled). + /// + public bool Smooth { get; set; } + + //public bool Bold { get; set; } + //public bool DropShadow { get; set; } + + /// + /// This should be set by the renderer if it tries to use a font where it's null. + /// + public object RendererData { get; set; } + + /// + /// This is the real font size, after it's been scaled by Renderer.Scale() + /// + public float RealSize { get; set; } + + private readonly Renderer.Base m_Renderer; + + /// + /// Initializes a new instance of the class. + /// + public Font(Renderer.Base renderer) + : this(renderer, "Arial", 10) + { + + } + + /// + /// Initializes a new instance of the class. + /// + /// Renderer to use. + /// Face name. + /// Font size. + public Font(Renderer.Base renderer, String faceName, int size = 10) + { + m_Renderer = renderer; + FaceName = faceName; + Size = size; + Smooth = false; + //Bold = false; + //DropShadow = false; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + 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 + + /// + /// Duplicates font data (except renderer data which must be reinitialized). + /// + /// + 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; + } + } +} diff --git a/Gwen/Gwen.csproj b/Gwen/Gwen.csproj new file mode 100644 index 0000000..d17cb97 --- /dev/null +++ b/Gwen/Gwen.csproj @@ -0,0 +1,157 @@ + + + + Debug + AnyCPU + 10.0.0 + 2.0 + {D3F5E624-3AF2-418F-A180-8A4172928065} + Library + Gwen + Gwen + + + true + full + false + bin\Debug + DEBUG; + prompt + 4 + false + true + + + none + true + bin\Release + prompt + 4 + false + true + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + + \ No newline at end of file diff --git a/Gwen/HSV.cs b/Gwen/HSV.cs new file mode 100644 index 0000000..daf1540 --- /dev/null +++ b/Gwen/HSV.cs @@ -0,0 +1,11 @@ +using System; + +namespace Gwen +{ + public struct HSV + { + public float h; + public float s; + public float v; + } +} diff --git a/Gwen/Input/InputHandler.cs b/Gwen/Input/InputHandler.cs new file mode 100644 index 0000000..f5ba899 --- /dev/null +++ b/Gwen/Input/InputHandler.cs @@ -0,0 +1,410 @@ +using System; +using System.Drawing; +using System.Linq; +using System.Text; +using Gwen.Control; +using Gwen.DragDrop; + +namespace Gwen.Input +{ + /// + /// Input handling. + /// + 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; + + /// + /// Control currently hovered by mouse. + /// + public static Base HoveredControl; + + /// + /// Control that corrently has keyboard focus. + /// + public static Base KeyboardFocus; + + /// + /// Control that currently has mouse focus. + /// + public static Base MouseFocus; + + /// + /// Maximum number of mouse buttons supported. + /// + public static int MaxMouseButtons { get { return 5; } } + + /// + /// Maximum time in seconds between mouse clicks to be recognized as double click. + /// + public static float DoubleClickSpeed { get { return 0.5f; } } + + /// + /// Time in seconds between autorepeating of keys. + /// + public static float KeyRepeatRate { get { return 0.03f; } } + + /// + /// Time in seconds before key starts to autorepeat. + /// + public static float KeyRepeatDelay { get { return 0.5f; } } + + /// + /// Indicates whether the left mouse button is down. + /// + public static bool IsLeftMouseDown { get { return m_KeyData.LeftMouseDown; } } + + /// + /// Indicates whether the right mouse button is down. + /// + public static bool IsRightMouseDown { get { return m_KeyData.RightMouseDown; } } + + /// + /// Current mouse position. + /// + public static Point MousePosition; // not property to allow modification of Point fields + + /// + /// Indicates whether the shift key is down. + /// + public static bool IsShiftDown { get { return IsKeyDown(Key.Shift); } } + + /// + /// Indicates whether the control key is down. + /// + public static bool IsControlDown { get { return IsKeyDown(Key.Control); } } + + /// + /// Checks if the given key is pressed. + /// + /// Key to check. + /// True if the key is down. + public static bool IsKeyDown(Key key) + { + return m_KeyData.KeyState[(int)key]; + } + + /// + /// Handles copy, paste etc. + /// + /// Canvas. + /// Input character. + /// True if the key was handled. + 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; + } + + /// + /// Handles accelerator input. + /// + /// Canvas. + /// Input character. + /// True if the key was handled. + 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; + } + + /// + /// Mouse moved handler. + /// + /// Canvas. + /// + /// + /// + /// + 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); + } + + /// + /// Handles focus updating and key autorepeats. + /// + /// Unused. + 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); + } + } + } + } + + /// + /// Mouse click handler. + /// + /// Canvas. + /// Mouse button number. + /// Specifies if the button is down. + /// True if handled. + 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; + } + + /// + /// Key handler. + /// + /// Canvas. + /// Key. + /// True if the key is down. + /// True if handled. + 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; + } + } +} diff --git a/Gwen/Input/KeyData.cs b/Gwen/Input/KeyData.cs new file mode 100644 index 0000000..3bd3eaa --- /dev/null +++ b/Gwen/Input/KeyData.cs @@ -0,0 +1,24 @@ +using System; +using Gwen.Control; + +namespace Gwen.Input +{ + /// + /// Keyboard state. + /// + public class KeyData + { + public readonly bool[] KeyState; + public readonly float [] NextRepeat; + public Base Target; + public bool LeftMouseDown; + public bool RightMouseDown; + + public KeyData() + { + KeyState = new bool[(int)Key.Count]; + NextRepeat = new float[(int)Key.Count]; + // everything is initialized to 0 by default + } + } +} diff --git a/Gwen/Key.cs b/Gwen/Key.cs new file mode 100644 index 0000000..654bc49 --- /dev/null +++ b/Gwen/Key.cs @@ -0,0 +1,29 @@ +using System; + +namespace Gwen +{ + /// + /// Key constants. + /// + public enum Key + { + Invalid = 0, + Return = 1, + Backspace = 2, + Delete = 3, + Left = 4, + Right = 5, + Shift = 6, + Tab = 7, + Space = 8, + Home = 9, + End = 10, + Control = 11, + Up = 12, + Down = 13, + Escape = 14, + Alt = 15, + + Count = 16 + } +} diff --git a/Gwen/Margin.cs b/Gwen/Margin.cs new file mode 100644 index 0000000..c837a76 --- /dev/null +++ b/Gwen/Margin.cs @@ -0,0 +1,70 @@ +using System; + +namespace Gwen +{ + /// + /// Represents outer spacing. + /// + public struct Margin : IEquatable + { + public int Top; + public int Bottom; + public int Left; + public int Right; + + // common values + public static Margin Zero = new Margin(0, 0, 0, 0); + public static Margin One = new Margin(1, 1, 1, 1); + public static Margin Two = new Margin(2, 2, 2, 2); + public static Margin Three = new Margin(3, 3, 3, 3); + public static Margin Four = new Margin(4, 4, 4, 4); + public static Margin Five = new Margin(5, 5, 5, 5); + public static Margin Six = new Margin(6, 6, 6, 6); + public static Margin Seven = new Margin(7, 7, 7, 7); + public static Margin Eight = new Margin(8, 8, 8, 8); + public static Margin Nine = new Margin(9, 9, 9, 9); + public static Margin Ten = new Margin(10, 10, 10, 10); + + public Margin(int left, int top, int right, int bottom) + { + Top = top; + Bottom = bottom; + Left = left; + Right = right; + } + + public bool Equals(Margin other) + { + return other.Top == Top && other.Bottom == Bottom && other.Left == Left && other.Right == Right; + } + + public static bool operator==(Margin lhs, Margin rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator!=(Margin lhs, Margin rhs) + { + return !lhs.Equals(rhs); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (obj.GetType() != typeof (Margin)) return false; + return Equals((Margin) obj); + } + + public override int GetHashCode() + { + unchecked + { + int result = Top; + result = (result*397) ^ Bottom; + result = (result*397) ^ Left; + result = (result*397) ^ Right; + return result; + } + } + } +} diff --git a/Gwen/Padding.cs b/Gwen/Padding.cs new file mode 100644 index 0000000..b79f7a0 --- /dev/null +++ b/Gwen/Padding.cs @@ -0,0 +1,65 @@ +using System; + +namespace Gwen +{ + /// + /// Represents inner spacing. + /// + public struct Padding : IEquatable + { + public readonly int Top; + public readonly int Bottom; + public readonly int Left; + public readonly int Right; + + // common values + public static Padding Zero = new Padding(0, 0, 0, 0); + public static Padding One = new Padding(1, 1, 1, 1); + public static Padding Two = new Padding(2, 2, 2, 2); + public static Padding Three = new Padding(3, 3, 3, 3); + public static Padding Four = new Padding(4, 4, 4, 4); + public static Padding Five = new Padding(5, 5, 5, 5); + + public Padding(int left, int top, int right, int bottom) + { + Top = top; + Bottom = bottom; + Left = left; + Right = right; + } + + public bool Equals(Padding other) + { + return other.Top == Top && other.Bottom == Bottom && other.Left == Left && other.Right == Right; + } + + public static bool operator ==(Padding lhs, Padding rhs) + { + return lhs.Equals(rhs); + } + + public static bool operator !=(Padding lhs, Padding rhs) + { + return !lhs.Equals(rhs); + } + + public override bool Equals(object obj) + { + if (ReferenceEquals(null, obj)) return false; + if (obj.GetType() != typeof (Padding)) return false; + return Equals((Padding) obj); + } + + public override int GetHashCode() + { + unchecked + { + int result = Top; + result = (result*397) ^ Bottom; + result = (result*397) ^ Left; + result = (result*397) ^ Right; + return result; + } + } + } +} diff --git a/Gwen/Platform/Neutral.cs b/Gwen/Platform/Neutral.cs new file mode 100644 index 0000000..837c2e9 --- /dev/null +++ b/Gwen/Platform/Neutral.cs @@ -0,0 +1,185 @@ +using System; +using System.Threading; +//using System.Windows.Forms; + +namespace Gwen.Platform +{ + /// + /// Platform-agnostic utility functions. + /// + public static class Neutral + { + private static DateTime m_LastTime; + private static float m_CurrentTime; + + /* + /// + /// Changes the mouse cursor. + /// + /// Cursor type. + public static void SetCursor(Cursor cursor) + { + Cursor.Current = cursor; + } + */ + + /// + /// Gets text from clipboard. + /// + /// Clipboard text. + public static String GetClipboardText() + { + // code from http://forums.getpaint.net/index.php?/topic/13712-trouble-accessing-the-clipboard/page__view__findpost__p__226140 + String ret = String.Empty; + /* + Thread staThread = new Thread( + () => + { + try + { + if (!Clipboard.ContainsText()) + return; + ret = Clipboard.GetText(); + } + catch (Exception) + { + return; + } + }); + staThread.SetApartmentState(ApartmentState.STA); + staThread.Start(); + staThread.Join(); + // at this point either you have clipboard data or an exception + */ + return ret; + } + + /// + /// Sets the clipboard text. + /// + /// Text to set. + /// True if succeeded. + public static bool SetClipboardText(String text) + { + bool ret = false; + /* + Thread staThread = new Thread( + () => + { + try + { + Clipboard.SetText(text); + ret = true; + } + catch (Exception) + { + return; + } + }); + staThread.SetApartmentState(ApartmentState.STA); + staThread.Start(); + staThread.Join(); + // at this point either you have clipboard data or an exception + */ + return ret; + } + + /// + /// Gets time since last measurement. + /// + /// Time interval in seconds. + public static float GetTimeInSeconds() + { + var time = DateTime.UtcNow; + var diff = time - m_LastTime; + var seconds = diff.TotalSeconds; + if (seconds > 0.1) + seconds = 0.1; + m_CurrentTime += (float)seconds; + m_LastTime = time; + return m_CurrentTime; + } + + /// + /// Displays an open file dialog. + /// + /// Dialog title. + /// Initial path. + /// File extension filter. + /// Callback that is executed after the dialog completes. + /// True if succeeded. + public static bool FileOpen(String title, String startPath, String extension, Action callback) + { + /* + var dialog = new OpenFileDialog + { + Title = title, + InitialDirectory = startPath, + DefaultExt = @"*.*", + Filter = extension, + CheckPathExists = true, + Multiselect = false + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + if (callback != null) + { + callback(dialog.FileName); + } + } + else + { + if (callback != null) + { + callback(String.Empty); + } + return false; + } + + return true; + */ + return false; + } + + /// + /// Displays a save file dialog. + /// + /// Dialog title. + /// Initial path. + /// File extension filter. + /// Callback that is executed after the dialog completes. + /// True if succeeded. + public static bool FileSave(String title, String startPath, String extension, Action callback) + { + /* + var dialog = new SaveFileDialog + { + Title = title, + InitialDirectory = startPath, + DefaultExt = @"*.*", + Filter = extension, + CheckPathExists = true, + OverwritePrompt = true + }; + if (dialog.ShowDialog() == DialogResult.OK) + { + if (callback != null) + { + callback(dialog.FileName); + } + } + else + { + if (callback != null) + { + callback(String.Empty); + } + return false; + } + + return true; + */ + return false; + } + } +} diff --git a/Gwen/Platform/Windows.cs b/Gwen/Platform/Windows.cs new file mode 100644 index 0000000..36470ea --- /dev/null +++ b/Gwen/Platform/Windows.cs @@ -0,0 +1,58 @@ +using System; +using System.Collections.Generic; +using System.IO; +using Microsoft.Win32; + +// todo: compile/run only on windows + +namespace Gwen.Platform +{ + /// + /// Windows-specific utility functions. + /// + public static class Windows + { + private const String FontRegKey = @"SOFTWARE\Microsoft\Windows NT\CurrentVersion\Fonts"; + + private static Dictionary m_FontPaths; + + /// + /// Gets a font file path from font name. + /// + /// Font name. + /// Font file path. + public static String GetFontPath(String fontName) + { + // is this reliable? we rely on lazy jitting to not run win32 code on linux + if (Environment.OSVersion.Platform != PlatformID.Win32NT) + return null; + + if (m_FontPaths == null) + InitFontPaths(); + + if (!m_FontPaths.ContainsKey(fontName)) + return null; + + return m_FontPaths[fontName]; + } + + private static void InitFontPaths() + { + // very hacky but better than nothing + m_FontPaths = new Dictionary(); + String fontsDir = Environment.GetFolderPath(Environment.SpecialFolder.Fonts); + + RegistryKey key = RegistryKey.OpenBaseKey(RegistryHive.LocalMachine, RegistryView.Default); + RegistryKey subkey = key.OpenSubKey(FontRegKey); + foreach (String fontName in subkey.GetValueNames()) + { + String fontFile = (String)subkey.GetValue(fontName); + if (!fontName.EndsWith(" (TrueType)")) + continue; + String font = fontName.Replace(" (TrueType)", ""); + m_FontPaths[font] = Path.Combine(fontsDir, fontFile); + } + key.Dispose(); + } + } +} diff --git a/Gwen/Pos.cs b/Gwen/Pos.cs new file mode 100644 index 0000000..3edd801 --- /dev/null +++ b/Gwen/Pos.cs @@ -0,0 +1,21 @@ +using System; + +namespace Gwen +{ + /// + /// Represents relative position. + /// + [Flags] + public enum Pos + { + None = 0, + Left = 1 << 1, + Right = 1 << 2, + Top = 1 << 3, + Bottom = 1 << 4, + CenterV = 1 << 5, + CenterH = 1 << 6, + Fill = 1 << 7, + Center = CenterV | CenterH, + } +} diff --git a/Gwen/Properties/AssemblyInfo.cs b/Gwen/Properties/AssemblyInfo.cs new file mode 100644 index 0000000..6ec482a --- /dev/null +++ b/Gwen/Properties/AssemblyInfo.cs @@ -0,0 +1,37 @@ +using System.Diagnostics.CodeAnalysis; +using System.Reflection; +using System.Runtime.CompilerServices; +using System.Runtime.InteropServices; + +// General Information about an assembly is controlled through the following +// set of attributes. Change these attribute values to modify the information +// associated with an assembly. +[assembly: AssemblyTitle("Gwen.Net")] +[assembly: AssemblyDescription("Gwen.Net")] +[assembly: AssemblyConfiguration("")] +[assembly: AssemblyCompany("")] +[assembly: AssemblyProduct("Gwen.Net")] +[assembly: AssemblyCopyright("Copyleft © 2011 Omega Red")] +[assembly: AssemblyTrademark("")] +[assembly: AssemblyCulture("")] + +// Setting ComVisible to false makes the types in this assembly not visible +// to COM components. If you need to access a type in this assembly from +// COM, set the ComVisible attribute to true on that type. +[assembly: ComVisible(false)] + +// The following GUID is for the ID of the typelib if this project is exposed to COM +[assembly: Guid("89f684eb-caf7-4ff0-80c6-4437040c8b8d")] + +// Version information for an assembly consists of the following four values: +// +// Major Version +// Minor Version +// Build Number +// Revision +// +// You can specify all the values or you can default the Build and Revision Numbers +// by using the '*' as shown below: +// [assembly: AssemblyVersion("1.0.*")] +[assembly: AssemblyVersion("1.0.0.0")] +[assembly: AssemblyFileVersion("1.0.0.0")] diff --git a/Gwen/Renderer/Base.cs b/Gwen/Renderer/Base.cs new file mode 100644 index 0000000..667f185 --- /dev/null +++ b/Gwen/Renderer/Base.cs @@ -0,0 +1,439 @@ +using System; +using System.Drawing; +using System.IO; + +namespace Gwen.Renderer +{ + /// + /// Base renderer. + /// + public class Base : IDisposable + { + //public Random rnd; + private Point m_RenderOffset; + private Rectangle m_ClipRegion; + //protected ICacheToTexture m_RTT; + + public float Scale { get; set; } + + /// + /// Initializes a new instance of the class. + /// + protected Base() + { + //rnd = new Random(); + m_RenderOffset = Point.Empty; + Scale = 1.0f; + if (CTT != null) + CTT.Initialize(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + /// 2 + public virtual void Dispose() + { + if (CTT != null) + CTT.ShutDown(); + GC.SuppressFinalize(this); + } + +#if DEBUG + ~Base() + { + throw new InvalidOperationException(String.Format("IDisposable object finalized: {0}", GetType())); + //Debug.Print(String.Format("IDisposable object finalized: {0}", GetType())); + } +#endif + + /// + /// Starts rendering. + /// + public virtual void Begin() + {} + + /// + /// Stops rendering. + /// + public virtual void End() + {} + + /// + /// Gets or sets the current drawing color. + /// + public virtual Color DrawColor { get; set; } + + /// + /// Rendering offset. No need to touch it usually. + /// + public Point RenderOffset { get { return m_RenderOffset; } set { m_RenderOffset = value; } } + + /// + /// Clipping rectangle. + /// + public Rectangle ClipRegion { get { return m_ClipRegion; } set { m_ClipRegion = value; } } + + /// + /// Indicates whether the clip region is visible. + /// + public bool ClipRegionVisible + { + get + { + if (m_ClipRegion.Width <= 0 || m_ClipRegion.Height <= 0) + return false; + + return true; + } + } + + /// + /// Draws a line. + /// + /// + /// + /// + /// + public virtual void DrawLine(int x, int y, int a, int b) + {} + + /// + /// Draws a solid filled rectangle. + /// + /// + public virtual void DrawFilledRect(Rectangle rect) + {} + + /// + /// Starts clipping to the current clipping rectangle. + /// + public virtual void StartClip() + {} + + /// + /// Stops clipping. + /// + public virtual void EndClip() + {} + + /// + /// Loads the specified texture. + /// + /// + public virtual void LoadTexture(Texture t) + {} + + /// + /// Initializes texture from raw pixel data. + /// + /// Texture to initialize. Dimensions need to be set. + /// Pixel data in RGBA format. + public virtual void LoadTextureRaw(Texture t, byte[] pixelData) + {} + + /// + /// Initializes texture from image file data. + /// + /// Texture to initialize. + /// Image file as stream. + public virtual void LoadTextureStream(Texture t, Stream data) + {} + + /// + /// Frees the specified texture. + /// + /// Texture to free. + public virtual void FreeTexture(Texture t) + {} + + /// + /// Draws textured rectangle. + /// + /// Texture to use. + /// Rectangle bounds. + /// Texture coordinate u1. + /// Texture coordinate v1. + /// Texture coordinate u2. + /// Texture coordinate v2. + public virtual void DrawTexturedRect(Texture t, Rectangle targetRect, float u1=0, float v1=0, float u2=1, float v2=1) + {} + + /// + /// Draws "missing image" default texture. + /// + /// Target rectangle. + public virtual void DrawMissingImage(Rectangle rect) + { + //DrawColor = Color.FromArgb(255, rnd.Next(0,255), rnd.Next(0,255), rnd.Next(0, 255)); + DrawColor = Color.Red; + DrawFilledRect(rect); + } + + /// + /// Cache to texture provider. + /// + public virtual ICacheToTexture CTT { get { return null; } } + + /// + /// Loads the specified font. + /// + /// Font to load. + /// True if succeeded. + public virtual bool LoadFont(Font font) + { + return false; + } + + /// + /// Frees the specified font. + /// + /// Font to free. + public virtual void FreeFont(Font font) + {} + + /// + /// Returns dimensions of the text using specified font. + /// + /// Font to use. + /// Text to measure. + /// Width and height of the rendered text. + public virtual Point MeasureText(Font font, String text) + { + Point p = new Point((int)(font.Size * Scale * text.Length * 0.4f), (int)(font.Size * Scale)); + + return p; + } + + /// + /// Renders text using specified font. + /// + /// Font to use. + /// Top-left corner of the text. + /// Text to render. + public virtual void RenderText(Font font, Point position, String text) + { + float size = font.Size * Scale; + + for ( int i=0; i= 'a' && chr <= 'z' ) + { + r.Y = (int)(r.Y + size * 0.5f); + r.Height = (int)(r.Height - size * 0.4f); + } + else if ( chr == '.' || chr == ',' ) + { + r.X += 2; + r.Y += r.Height - 2; + r.Width = 2; + r.Height = 2; + } + else if ( chr == '\'' || chr == '`' || chr == '"' ) + { + r.X += 3; + r.Width = 2; + r.Height = 2; + } + + if ( chr == 'o' || chr == 'O' || chr == '0' ) + DrawLinedRect( r ); + else + DrawFilledRect( r ); + } + } + + // + // No need to implement these functions in your derived class, but if + // you can do them faster than the default implementation it's a good idea to. + // + + /// + /// Draws a lined rectangle. Used for keyboard focus overlay. + /// + /// Target rectangle. + public virtual void DrawLinedRect(Rectangle rect) + { + DrawFilledRect(new Rectangle(rect.X, rect.Y, rect.Width, 1)); + DrawFilledRect(new Rectangle(rect.X, rect.Y + rect.Height - 1, rect.Width, 1)); + + DrawFilledRect(new Rectangle(rect.X, rect.Y, 1, rect.Height)); + DrawFilledRect(new Rectangle(rect.X + rect.Width - 1, rect.Y, 1, rect.Height)); + } + + /// + /// Draws a single pixel. Very slow, do not use. :P + /// + /// X. + /// Y. + public virtual void DrawPixel(int x, int y) + { + // [omeg] amazing ;) + DrawFilledRect(new Rectangle(x, y, 1, 1)); + } + + /// + /// Gets pixel color of a specified texture. Slow. + /// + /// Texture. + /// X. + /// Y. + /// Pixel color. + public virtual Color PixelColor(Texture texture, uint x, uint y) + { + return PixelColor(texture, x, y, Color.White); + } + + /// + /// Gets pixel color of a specified texture, returning default if otherwise failed. Slow. + /// + /// Texture. + /// X. + /// Y. + /// Color to return on failure. + /// Pixel color. + public virtual Color PixelColor(Texture texture, uint x, uint y, Color defaultColor) + { + return defaultColor; + } + + /// + /// Draws a round-corner rectangle. + /// + /// Target rectangle. + /// + public virtual void DrawShavedCornerRect(Rectangle rect, bool slight = false) + { + // Draw INSIDE the w/h. + rect.Width -= 1; + rect.Height -= 1; + + if (slight) + { + DrawFilledRect(new Rectangle(rect.X + 1, rect.Y, rect.Width - 1, 1)); + DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + rect.Height, rect.Width - 1, 1)); + + DrawFilledRect(new Rectangle(rect.X, rect.Y + 1, 1, rect.Height - 1)); + DrawFilledRect(new Rectangle(rect.X + rect.Width, rect.Y + 1, 1, rect.Height - 1)); + return; + } + + DrawPixel(rect.X + 1, rect.Y + 1); + DrawPixel(rect.X + rect.Width - 1, rect.Y + 1); + + DrawPixel(rect.X + 1, rect.Y + rect.Height - 1); + DrawPixel(rect.X + rect.Width - 1, rect.Y + rect.Height - 1); + + DrawFilledRect(new Rectangle(rect.X + 2, rect.Y, rect.Width - 3, 1)); + DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + rect.Height, rect.Width - 3, 1)); + + DrawFilledRect(new Rectangle(rect.X, rect.Y + 2, 1, rect.Height - 3)); + DrawFilledRect(new Rectangle(rect.X + rect.Width, rect.Y + 2, 1, rect.Height - 3)); + } + + private int TranslateX(int x) + { + int x1 = x + m_RenderOffset.X; + return Util.Ceil(x1 * Scale); + } + + private int TranslateY(int y) + { + int y1 = y + m_RenderOffset.Y; + return Util.Ceil(y1 * Scale); + } + + /// + /// Translates a panel's local drawing coordinate into view space, taking offsets into account. + /// + /// + /// + public void Translate(ref int x, ref int y) + { + x += m_RenderOffset.X; + y += m_RenderOffset.Y; + + x = Util.Ceil(x * Scale); + y = Util.Ceil(y * Scale); + } + + /// + /// Translates a panel's local drawing coordinate into view space, taking offsets into account. + /// + public Point Translate(Point p) + { + int x = p.X; + int y = p.Y; + Translate(ref x, ref y); + return new Point(x, y); + } + + /// + /// Translates a panel's local drawing coordinate into view space, taking offsets into account. + /// + public Rectangle Translate(Rectangle rect) + { + return new Rectangle(TranslateX(rect.X), TranslateY(rect.Y), Util.Ceil(rect.Width * Scale), Util.Ceil(rect.Height * Scale)); + } + + /// + /// Adds a point to the render offset. + /// + /// Point to add. + public void AddRenderOffset(Rectangle offset) + { + m_RenderOffset = new Point(m_RenderOffset.X + offset.X, m_RenderOffset.Y + offset.Y); + } + + /// + /// Adds a rectangle to the clipping region. + /// + /// Rectangle to add. + public void AddClipRegion(Rectangle rect) + { + + rect.X = m_RenderOffset.X; + rect.Y = m_RenderOffset.Y; + + Rectangle r = rect; + if (rect.X < m_ClipRegion.X) + { + r.Width -= (m_ClipRegion.X - r.X); + r.X = m_ClipRegion.X; + } + + if (rect.Y < m_ClipRegion.Y) + { + r.Height -= (m_ClipRegion.Y - r.Y); + r.Y = m_ClipRegion.Y; + } + + if (rect.Right > m_ClipRegion.Right) + { + r.Width = m_ClipRegion.Right - r.X; + } + + if (rect.Bottom > m_ClipRegion.Bottom) + { + r.Height = m_ClipRegion.Bottom - r.Y; + } + + m_ClipRegion = r; + } + } +} diff --git a/Gwen/Renderer/ICacheToTexture.cs b/Gwen/Renderer/ICacheToTexture.cs new file mode 100644 index 0000000..82336ad --- /dev/null +++ b/Gwen/Renderer/ICacheToTexture.cs @@ -0,0 +1,36 @@ + +namespace Gwen.Renderer +{ + public interface ICacheToTexture + { + void Initialize(); + void ShutDown(); + + /// + /// Called to set the target up for rendering. + /// + /// Control to be rendered. + void SetupCacheTexture(Control.Base control); + + /// + /// Called when cached rendering is done. + /// + /// Control to be rendered. + void FinishCacheTexture(Control.Base control); + + /// + /// Called when gwen wants to draw the cached version of the control. + /// + /// Control to be rendered. + void DrawCachedControlTexture(Control.Base control); + + /// + /// Called to actually create a cached texture. + /// + /// Control to be rendered. + void CreateControlCacheTexture(Control.Base control); + + void UpdateControlCacheTexture(Control.Base control); + void SetRenderer(Base renderer); + } +} diff --git a/Gwen/Skin/Base.cs b/Gwen/Skin/Base.cs new file mode 100644 index 0000000..77e9519 --- /dev/null +++ b/Gwen/Skin/Base.cs @@ -0,0 +1,269 @@ +using System; +using System.Drawing; + +namespace Gwen.Skin +{ + /// + /// Base skin. + /// + public class Base : IDisposable + { + protected Font m_DefaultFont; + protected readonly Renderer.Base m_Renderer; + + /// + /// Colors of various UI elements. + /// + public SkinColors Colors; + + /// + /// Default font to use when rendering text if none specified. + /// + public Font DefaultFont + { + get { return m_DefaultFont; } + set + { + m_DefaultFont.Dispose(); + m_DefaultFont = value; + } + } + + /// + /// Renderer used. + /// + public Renderer.Base Renderer { get { return m_Renderer; } } + + /// + /// Initializes a new instance of the class. + /// + /// Renderer to use. + protected Base(Renderer.Base renderer) + { + m_DefaultFont = new Font(renderer); + m_Renderer = renderer; + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public virtual void Dispose() + { + m_DefaultFont.Dispose(); + GC.SuppressFinalize(this); + } + +#if DEBUG + ~Base() + { + throw new InvalidOperationException(String.Format("IDisposable object finalized: {0}", GetType())); + //Debug.Print(String.Format("IDisposable object finalized: {0}", GetType())); + } +#endif + + /// + /// Releases the specified font. + /// + /// Font to release. + protected virtual void ReleaseFont(Font font) + { + if (font == null) + return; + if (m_Renderer == null) + return; + m_Renderer.FreeFont(font); + } + + /// + /// Sets the default text font. + /// + /// Font name. Meaning can vary depending on the renderer. + /// Font size. + public virtual void SetDefaultFont(String faceName, int size = 10) + { + m_DefaultFont.FaceName = faceName; + m_DefaultFont.Size = size; + } + + #region UI elements + public virtual void DrawButton(Control.Base control, bool depressed, bool hovered, bool disabled) { } + public virtual void DrawTabButton(Control.Base control, bool active, Pos dir) { } + public virtual void DrawTabControl(Control.Base control) { } + public virtual void DrawTabTitleBar(Control.Base control) { } + public virtual void DrawMenuItem(Control.Base control, bool submenuOpen, bool isChecked) { } + public virtual void DrawMenuRightArrow(Control.Base control) { } + public virtual void DrawMenuStrip(Control.Base control) { } + public virtual void DrawMenu(Control.Base control, bool paddingDisabled) { } + public virtual void DrawRadioButton(Control.Base control, bool selected, bool depressed) { } + public virtual void DrawCheckBox(Control.Base control, bool selected, bool depressed) { } + public virtual void DrawGroupBox(Control.Base control, int textStart, int textHeight, int textWidth) { } + public virtual void DrawTextBox(Control.Base control) { } + public virtual void DrawWindow(Control.Base control, int topHeight, bool inFocus) { } + public virtual void DrawWindowCloseButton(Control.Base control, bool depressed, bool hovered, bool disabled) { } + public virtual void DrawHighlight(Control.Base control) { } + public virtual void DrawStatusBar(Control.Base control) { } + public virtual void DrawShadow(Control.Base control) { } + public virtual void DrawScrollBarBar(Control.Base control, bool depressed, bool hovered, bool horizontal) { } + public virtual void DrawScrollBar(Control.Base control, bool horizontal, bool depressed) { } + public virtual void DrawScrollButton(Control.Base control, Pos direction, bool depressed, bool hovered, bool disabled) { } + public virtual void DrawProgressBar(Control.Base control, bool horizontal, float progress) { } + public virtual void DrawListBox(Control.Base control) { } + public virtual void DrawListBoxLine(Control.Base control, bool selected, bool even) { } + public virtual void DrawSlider(Control.Base control, bool horizontal, int numNotches, int barSize) { } + public virtual void DrawSliderButton(Control.Base control, bool depressed, bool horizontal) { } + public virtual void DrawComboBox(Control.Base control, bool down, bool isMenuOpen) { } + public virtual void DrawComboBoxArrow(Control.Base control, bool hovered, bool depressed, bool open, bool disabled) { } + public virtual void DrawKeyboardHighlight(Control.Base control, Rectangle rect, int offset) { } + public virtual void DrawToolTip(Control.Base control) { } + public virtual void DrawNumericUpDownButton(Control.Base control, bool depressed, bool up) { } + public virtual void DrawTreeButton(Control.Base control, bool open) { } + public virtual void DrawTreeControl(Control.Base control) { } + + public virtual void DrawDebugOutlines(Control.Base control) + { + m_Renderer.DrawColor = control.PaddingOutlineColor; + Rectangle inner = new Rectangle(control.Bounds.Left + control.Padding.Left, + control.Bounds.Top + control.Padding.Top, + control.Bounds.Width - control.Padding.Right - control.Padding.Left, + control.Bounds.Height - control.Padding.Bottom - control.Padding.Top); + m_Renderer.DrawLinedRect(inner); + + m_Renderer.DrawColor = control.MarginOutlineColor; + Rectangle outer = new Rectangle(control.Bounds.Left - control.Margin.Left, + control.Bounds.Top - control.Margin.Top, + control.Bounds.Width + control.Margin.Right + control.Margin.Left, + control.Bounds.Height + control.Margin.Bottom + control.Margin.Top); + m_Renderer.DrawLinedRect(outer); + + m_Renderer.DrawColor = control.BoundsOutlineColor; + m_Renderer.DrawLinedRect(control.Bounds); + } + + public virtual void DrawTreeNode(Control.Base ctrl, bool open, bool selected, int labelHeight, int labelWidth, int halfWay, int lastBranch, bool isRoot) + { + Renderer.DrawColor = Colors.Tree.Lines; + + if (!isRoot) + Renderer.DrawFilledRect(new Rectangle(8, halfWay, 16 - 9, 1)); + + if (!open) return; + + Renderer.DrawFilledRect(new Rectangle(14 + 7, labelHeight + 1, 1, lastBranch + halfWay - labelHeight)); + } + + public virtual void DrawPropertyRow(Control.Base control, int iWidth, bool bBeingEdited, bool hovered) + { + Rectangle rect = control.RenderBounds; + + if (bBeingEdited) + m_Renderer.DrawColor = Colors.Properties.Column_Selected; + else if (hovered) + m_Renderer.DrawColor = Colors.Properties.Column_Hover; + else + m_Renderer.DrawColor = Colors.Properties.Column_Normal; + + m_Renderer.DrawFilledRect(new Rectangle(0, rect.Y, iWidth, rect.Height)); + + if (bBeingEdited) + m_Renderer.DrawColor = Colors.Properties.Line_Selected; + else if (hovered) + m_Renderer.DrawColor = Colors.Properties.Line_Hover; + else + m_Renderer.DrawColor = Colors.Properties.Line_Normal; + + m_Renderer.DrawFilledRect(new Rectangle(iWidth, rect.Y, 1, rect.Height)); + + rect.Y += rect.Height - 1; + rect.Height = 1; + + m_Renderer.DrawFilledRect(rect); + } + + public virtual void DrawColorDisplay(Control.Base control, Color color) { } + public virtual void DrawModalControl(Control.Base control) { } + public virtual void DrawMenuDivider(Control.Base control) { } + public virtual void DrawCategoryHolder(Control.Base control) { } + public virtual void DrawCategoryInner(Control.Base control, bool collapsed) { } + + public virtual void DrawPropertyTreeNode(Control.Base control, int BorderLeft, int BorderTop) + { + Rectangle rect = control.RenderBounds; + + m_Renderer.DrawColor = Colors.Properties.Border; + + m_Renderer.DrawFilledRect(new Rectangle(rect.X, rect.Y, BorderLeft, rect.Height)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + BorderLeft, rect.Y, rect.Width - BorderLeft, BorderTop)); + } +#endregion + + #region Symbols for Simple skin + /* + Here we're drawing a few symbols such as the directional arrows and the checkbox check + + Texture'd skins don't generally use these - but the Simple skin does. We did originally + use the marlett font to draw these.. but since that's a Windows font it wasn't a very + good cross platform solution. + */ + + public virtual void DrawArrowDown(Rectangle rect) + { + float x = (rect.Width / 5.0f); + float y = (rect.Height / 5.0f); + + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 0.0f, rect.Y + y * 1.0f, x, y * 1.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 1.0f, x, y * 2.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 2.0f, rect.Y + y * 1.0f, x, y * 3.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 3.0f, rect.Y + y * 1.0f, x, y * 2.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 4.0f, rect.Y + y * 1.0f, x, y * 1.0f)); + } + + public virtual void DrawArrowUp(Rectangle rect) + { + float x = (rect.Width / 5.0f); + float y = (rect.Height / 5.0f); + + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 0.0f, rect.Y + y * 3.0f, x, y * 1.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 2.0f, x, y * 2.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 2.0f, rect.Y + y * 1.0f, x, y * 3.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 3.0f, rect.Y + y * 2.0f, x, y * 2.0f)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 4.0f, rect.Y + y * 3.0f, x, y * 1.0f)); + } + + public virtual void DrawArrowLeft(Rectangle rect) + { + float x = (rect.Width / 5.0f); + float y = (rect.Height / 5.0f); + + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 3.0f, rect.Y + y * 0.0f, x * 1.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 2.0f, rect.Y + y * 1.0f, x * 2.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 2.0f, x * 3.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 2.0f, rect.Y + y * 3.0f, x * 2.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 3.0f, rect.Y + y * 4.0f, x * 1.0f, y)); + } + + public virtual void DrawArrowRight(Rectangle rect) + { + float x = (rect.Width / 5.0f); + float y = (rect.Height / 5.0f); + + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 0.0f, x * 1.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 1.0f, x * 2.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 2.0f, x * 3.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 3.0f, x * 2.0f, y)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 4.0f, x * 1.0f, y)); + } + + public virtual void DrawCheck(Rectangle rect) + { + float x = (rect.Width / 5.0f); + float y = (rect.Height / 5.0f); + + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 0.0f, rect.Y + y * 3.0f, x * 2, y * 2)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 1.0f, rect.Y + y * 4.0f, x * 2, y * 2)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 2.0f, rect.Y + y * 3.0f, x * 2, y * 2)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 3.0f, rect.Y + y * 1.0f, x * 2, y * 2)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + x * 4.0f, rect.Y + y * 0.0f, x * 2, y * 2)); + } + #endregion + } +} diff --git a/Gwen/Skin/Simple.cs b/Gwen/Skin/Simple.cs new file mode 100644 index 0000000..21aa7f5 --- /dev/null +++ b/Gwen/Skin/Simple.cs @@ -0,0 +1,705 @@ +using System; +using System.Drawing; + +namespace Gwen.Skin +{ + /// + /// Simple skin (non-textured). Deprecated and incomplete, do not use. + /// + [Obsolete] + public class Simple : Skin.Base + { + private readonly Color m_colBorderColor; + private readonly Color m_colControlOutlineLight; + private readonly Color m_colControlOutlineLighter; + private readonly Color m_colBGDark; + private readonly Color m_colControl; + private readonly Color m_colControlDarker; + private readonly Color m_colControlOutlineNormal; + private readonly Color m_colControlBright; + private readonly Color m_colControlDark; + private readonly Color m_colHighlightBG; + private readonly Color m_colHighlightBorder; + private readonly Color m_colToolTipBackground; + private readonly Color m_colToolTipBorder; + private readonly Color m_colModal; + + public Simple(Renderer.Base renderer) : base(renderer) + { + m_colBorderColor = Color.FromArgb(255, 80, 80, 80); + //m_colBG = Color.FromArgb(255, 248, 248, 248); + m_colBGDark = Color.FromArgb(255, 235, 235, 235); + + m_colControl = Color.FromArgb(255, 240, 240, 240); + m_colControlBright = Color.FromArgb(255, 255, 255, 255); + m_colControlDark = Color.FromArgb(255, 214, 214, 214); + m_colControlDarker = Color.FromArgb(255, 180, 180, 180); + + m_colControlOutlineNormal = Color.FromArgb(255, 112, 112, 112); + m_colControlOutlineLight = Color.FromArgb(255, 144, 144, 144); + m_colControlOutlineLighter = Color.FromArgb(255, 210, 210, 210); + + m_colHighlightBG = Color.FromArgb(255, 192, 221, 252); + m_colHighlightBorder = Color.FromArgb(255, 51, 153, 255); + + m_colToolTipBackground = Color.FromArgb(255, 255, 255, 225); + m_colToolTipBorder = Color.FromArgb(255, 0, 0, 0); + + m_colModal = Color.FromArgb(150, 25, 25, 25); + } + + #region UI elements + public override void DrawButton(Control.Base control, bool depressed, bool hovered, bool disabled) + { + int w = control.Width; + int h = control.Height; + + DrawButton(w, h, depressed, hovered); + } + + public override void DrawMenuItem(Control.Base control, bool submenuOpen, bool isChecked) + { + Rectangle rect = control.RenderBounds; + if (submenuOpen || control.IsHovered) + { + m_Renderer.DrawColor = m_colHighlightBG; + m_Renderer.DrawFilledRect(rect); + + m_Renderer.DrawColor = m_colHighlightBorder; + m_Renderer.DrawLinedRect(rect); + } + + if (isChecked) + { + m_Renderer.DrawColor = Color.FromArgb(255, 0, 0, 0); + + Rectangle r = new Rectangle(control.Width / 2 - 2, control.Height / 2 - 2, 5, 5); + DrawCheck(r); + } + } + + public override void DrawMenuStrip(Control.Base control) + { + int w = control.Width; + int h = control.Height; + + m_Renderer.DrawColor = Color.FromArgb(255, 246, 248, 252); + m_Renderer.DrawFilledRect(new Rectangle(0, 0, w, h)); + + m_Renderer.DrawColor = Color.FromArgb(150, 218, 224, 241); + + m_Renderer.DrawFilledRect(Util.FloatRect(0, h * 0.4f, w, h * 0.6f)); + m_Renderer.DrawFilledRect(Util.FloatRect(0, h * 0.5f, w, h * 0.5f)); + } + + public override void DrawMenu(Control.Base control, bool paddingDisabled) + { + int w = control.Width; + int h = control.Height; + + m_Renderer.DrawColor = m_colControlBright; + m_Renderer.DrawFilledRect(new Rectangle(0, 0, w, h)); + + if (!paddingDisabled) + { + m_Renderer.DrawColor = m_colControl; + m_Renderer.DrawFilledRect(new Rectangle(1, 0, 22, h)); + } + + m_Renderer.DrawColor = m_colControlOutlineNormal; + m_Renderer.DrawLinedRect(new Rectangle(0, 0, w, h)); + } + + public override void DrawShadow(Control.Base control) + { + int w = control.Width; + int h = control.Height; + + int x = 4; + int y = 6; + + m_Renderer.DrawColor = Color.FromArgb(10, 0, 0, 0); + + m_Renderer.DrawFilledRect(new Rectangle(x, y, w, h)); + x += 2; + m_Renderer.DrawFilledRect(new Rectangle(x, y, w, h)); + y += 2; + m_Renderer.DrawFilledRect(new Rectangle(x, y, w, h)); + } + + public virtual void DrawButton(int w, int h, bool depressed, bool bHovered, bool bSquared = false) + { + if (depressed) m_Renderer.DrawColor = m_colControlDark; + else if (bHovered) m_Renderer.DrawColor = m_colControlBright; + else m_Renderer.DrawColor = m_colControl; + + m_Renderer.DrawFilledRect(new Rectangle(1, 1, w - 2, h - 2)); + + if (depressed) m_Renderer.DrawColor = m_colControlDark; + else if (bHovered) m_Renderer.DrawColor = m_colControl; + else m_Renderer.DrawColor = m_colControlDark; + + m_Renderer.DrawFilledRect(Util.FloatRect(1, h * 0.5f, w - 2, h * 0.5f - 2)); + + if (!depressed) + { + m_Renderer.DrawColor = m_colControlBright; + } + else + { + m_Renderer.DrawColor = m_colControlDarker; + } + m_Renderer.DrawShavedCornerRect(new Rectangle(1, 1, w - 2, h - 2), bSquared); + + // Border + m_Renderer.DrawColor = m_colControlOutlineNormal; + m_Renderer.DrawShavedCornerRect(new Rectangle(0, 0, w, h), bSquared); + } + + public override void DrawRadioButton(Control.Base control, bool selected, bool depressed) + { + Rectangle rect = control.RenderBounds; + + // Inside colour + if (control.IsHovered) m_Renderer.DrawColor = Color.FromArgb(255, 220, 242, 254); + else m_Renderer.DrawColor = m_colControlBright; + + m_Renderer.DrawFilledRect(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2)); + + // Border + if (control.IsHovered) m_Renderer.DrawColor = Color.FromArgb(255, 85, 130, 164); + else m_Renderer.DrawColor = m_colControlOutlineLight; + + m_Renderer.DrawShavedCornerRect(rect); + + m_Renderer.DrawColor = Color.FromArgb(15, 0, 50, 60); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + 2, rect.Width - 4, rect.Height - 4)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + 2, rect.Y + 2, rect.Width * 0.3f, rect.Height - 4)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + 2, rect.Y + 2, rect.Width - 4, rect.Height * 0.3f)); + + if (control.IsHovered) m_Renderer.DrawColor = Color.FromArgb(255, 121, 198, 249); + else m_Renderer.DrawColor = Color.FromArgb(50, 0, 50, 60); + + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + 3, 1, rect.Height - 5)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 3, rect.Y + 2, rect.Width - 5, 1)); + + + if (selected) + { + m_Renderer.DrawColor = Color.FromArgb(255, 40, 230, 30); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + 2, rect.Width - 4, rect.Height - 4)); + } + } + + public override void DrawCheckBox(Control.Base control, bool selected, bool depressed) + { + Rectangle rect = control.RenderBounds; + + // Inside colour + if (control.IsHovered) m_Renderer.DrawColor = Color.FromArgb(255, 220, 242, 254); + else m_Renderer.DrawColor = m_colControlBright; + + m_Renderer.DrawFilledRect(rect); + + // Border + if (control.IsHovered) m_Renderer.DrawColor = Color.FromArgb(255, 85, 130, 164); + else m_Renderer.DrawColor = m_colControlOutlineLight; + + m_Renderer.DrawLinedRect(rect); + + m_Renderer.DrawColor = Color.FromArgb(15, 0, 50, 60); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + 2, rect.Width - 4, rect.Height - 4)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + 2, rect.Y + 2, rect.Width * 0.3f, rect.Height - 4)); + m_Renderer.DrawFilledRect(Util.FloatRect(rect.X + 2, rect.Y + 2, rect.Width - 4, rect.Height * 0.3f)); + + if (control.IsHovered) m_Renderer.DrawColor = Color.FromArgb(255, 121, 198, 249); + else m_Renderer.DrawColor = Color.FromArgb(50, 0, 50, 60); + + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + 2, 1, rect.Height - 4)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + 2, rect.Width - 4, 1)); + + if (depressed) + { + m_Renderer.DrawColor = Color.FromArgb(255, 100, 100, 100); + Rectangle r = new Rectangle(control.Width / 2 - 2, control.Height / 2 - 2, 5, 5); + DrawCheck(r); + } + else if (selected) + { + m_Renderer.DrawColor = Color.FromArgb(255, 0, 0, 0); + Rectangle r = new Rectangle(control.Width / 2 - 2, control.Height / 2 - 2, 5, 5); + DrawCheck(r); + } + } + + public override void DrawGroupBox(Control.Base control, int textStart, int textHeight, int textWidth) + { + Rectangle rect = control.RenderBounds; + + rect.Y += (int)(textHeight * 0.5f); + rect.Height -= (int)(textHeight * 0.5f); + + Color m_colDarker = Color.FromArgb(50, 0, 50, 60); + Color m_colLighter = Color.FromArgb(150, 255, 255, 255); + + m_Renderer.DrawColor = m_colLighter; + + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + 1, textStart - 3, 1)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1 + textStart + textWidth, rect.Y + 1, + rect.Width - textStart + textWidth - 2, 1)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, (rect.Y + rect.Height) - 1, rect.Width - 2, 1)); + + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + 1, 1, rect.Height)); + m_Renderer.DrawFilledRect(new Rectangle((rect.X + rect.Width) - 2, rect.Y + 1, 1, rect.Height - 1)); + + m_Renderer.DrawColor = m_colDarker; + + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y, textStart - 3, 1)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1 + textStart + textWidth, rect.Y, + rect.Width - textStart - textWidth - 2, 1)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, (rect.Y + rect.Height) - 1, rect.Width - 2, 1)); + + m_Renderer.DrawFilledRect(new Rectangle(rect.X, rect.Y + 1, 1, rect.Height - 1)); + m_Renderer.DrawFilledRect(new Rectangle((rect.X + rect.Width) - 1, rect.Y + 1, 1, rect.Height - 1)); + } + + public override void DrawTextBox(Control.Base control) + { + Rectangle rect = control.RenderBounds; + bool bHasFocus = control.HasFocus; + + // Box inside + m_Renderer.DrawColor = Color.FromArgb(255, 255, 255, 255); + m_Renderer.DrawFilledRect(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2)); + + m_Renderer.DrawColor = m_colControlOutlineLight; + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y, rect.Width - 2, 1)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X, rect.Y + 1, 1, rect.Height - 2)); + + m_Renderer.DrawColor = m_colControlOutlineLighter; + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, (rect.Y + rect.Height) - 1, rect.Width - 2, 1)); + m_Renderer.DrawFilledRect(new Rectangle((rect.X + rect.Width) - 1, rect.Y + 1, 1, rect.Height - 2)); + + if (bHasFocus) + { + m_Renderer.DrawColor = Color.FromArgb(150, 50, 200, 255); + m_Renderer.DrawLinedRect(rect); + } + } + + public override void DrawTabButton(Control.Base control, bool active, Pos dir) + { + Rectangle rect = control.RenderBounds; + bool bHovered = control.IsHovered; + + if (bHovered) m_Renderer.DrawColor = m_colControlBright; + else m_Renderer.DrawColor = m_colControl; + + m_Renderer.DrawFilledRect(new Rectangle(1, 1, rect.Width - 2, rect.Height - 1)); + + if (bHovered) m_Renderer.DrawColor = m_colControl; + else m_Renderer.DrawColor = m_colControlDark; + + m_Renderer.DrawFilledRect(Util.FloatRect(1, rect.Height*0.5f, rect.Width - 2, rect.Height*0.5f - 1)); + + m_Renderer.DrawColor = m_colControlBright; + m_Renderer.DrawShavedCornerRect(new Rectangle(1, 1, rect.Width - 2, rect.Height)); + + m_Renderer.DrawColor = m_colBorderColor; + + m_Renderer.DrawShavedCornerRect(new Rectangle(0, 0, rect.Width, rect.Height)); + } + + public override void DrawTabControl(Control.Base control) + { + Rectangle rect = control.RenderBounds; + + m_Renderer.DrawColor = m_colControl; + m_Renderer.DrawFilledRect(rect); + + m_Renderer.DrawColor = m_colBorderColor; + m_Renderer.DrawLinedRect(rect); + + //m_Renderer.DrawColor = m_colControl; + //m_Renderer.DrawFilledRect(CurrentButtonRect); + } + + public override void DrawWindow(Control.Base control, int topHeight, bool inFocus) + { + Rectangle rect = control.RenderBounds; + + // Titlebar + if (inFocus) + m_Renderer.DrawColor = Color.FromArgb(230, 87, 164, 232); + else + m_Renderer.DrawColor = Color.FromArgb(230, (int)(87 * 0.70f), (int)(164 * 0.70f), + (int)(232 * 0.70f)); + + int iBorderSize = 5; + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + 1, rect.Width - 2, topHeight - 1)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + topHeight - 1, iBorderSize, + rect.Height - 2 - topHeight)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + rect.Width - iBorderSize, rect.Y + topHeight - 1, iBorderSize, + rect.Height - 2 - topHeight)); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + rect.Height - iBorderSize, rect.Width - 2, + iBorderSize)); + + // Main inner + m_Renderer.DrawColor = Color.FromArgb(230, m_colControlDark.R, m_colControlDark.G, m_colControlDark.B); + m_Renderer.DrawFilledRect(new Rectangle(rect.X + iBorderSize + 1, rect.Y + topHeight, + rect.Width - iBorderSize * 2 - 2, + rect.Height - topHeight - iBorderSize - 1)); + + // Light inner border + m_Renderer.DrawColor = Color.FromArgb(100, 255, 255, 255); + m_Renderer.DrawShavedCornerRect(new Rectangle(rect.X + 1, rect.Y + 1, rect.Width - 2, rect.Height - 2)); + + // Dark line between titlebar and main + m_Renderer.DrawColor = m_colBorderColor; + + // Inside border + m_Renderer.DrawColor = m_colControlOutlineNormal; + m_Renderer.DrawLinedRect(new Rectangle(rect.X + iBorderSize, rect.Y + topHeight - 1, rect.Width - 10, + rect.Height - topHeight - (iBorderSize - 1))); + + // Dark outer border + m_Renderer.DrawColor = m_colBorderColor; + m_Renderer.DrawShavedCornerRect(new Rectangle(rect.X, rect.Y, rect.Width, rect.Height)); + } + + public override void DrawWindowCloseButton(Control.Base control, bool depressed, bool hovered, bool disabled) + { + // TODO + DrawButton(control, depressed, hovered, disabled); + } + + public override void DrawHighlight(Control.Base control) + { + Rectangle rect = control.RenderBounds; + m_Renderer.DrawColor = Color.FromArgb(255, 255, 100, 255); + m_Renderer.DrawFilledRect(rect); + } + + public override void DrawScrollBar(Control.Base control, bool horizontal, bool depressed) + { + Rectangle rect = control.RenderBounds; + if (depressed) + m_Renderer.DrawColor = m_colControlDarker; + else + m_Renderer.DrawColor = m_colControlBright; + m_Renderer.DrawFilledRect(rect); + } + + public override void DrawScrollBarBar(Control.Base control, bool depressed, bool hovered, bool horizontal) + { + //TODO: something specialized + DrawButton(control, depressed, hovered, false); + } + + public override void DrawTabTitleBar(Control.Base control) + { + Rectangle rect = control.RenderBounds; + + m_Renderer.DrawColor = Color.FromArgb(255, 177, 193, 214); + m_Renderer.DrawFilledRect(rect); + + m_Renderer.DrawColor = m_colBorderColor; + rect.Height += 1; + m_Renderer.DrawLinedRect(rect); + } + + public override void DrawProgressBar(Control.Base control, bool horizontal, float progress) + { + Rectangle rect = control.RenderBounds; + Color FillColour = Color.FromArgb(255, 0, 211, 40); + + if (horizontal) + { + //Background + m_Renderer.DrawColor = m_colControlDark; + m_Renderer.DrawFilledRect(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2)); + + //Right half + m_Renderer.DrawColor = FillColour; + m_Renderer.DrawFilledRect(Util.FloatRect(1, 1, rect.Width * progress - 2, rect.Height - 2)); + + m_Renderer.DrawColor = Color.FromArgb(150, 255, 255, 255); + m_Renderer.DrawFilledRect(Util.FloatRect(1, 1, rect.Width - 2, rect.Height * 0.45f)); + } + else + { + //Background + m_Renderer.DrawColor = m_colControlDark; + m_Renderer.DrawFilledRect(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2)); + + //Top half + m_Renderer.DrawColor = FillColour; + m_Renderer.DrawFilledRect(Util.FloatRect(1, 1 + (rect.Height * (1 - progress)), rect.Width - 2, + rect.Height * progress - 2)); + + m_Renderer.DrawColor = Color.FromArgb(150, 255, 255, 255); + m_Renderer.DrawFilledRect(Util.FloatRect(1, 1, rect.Width * 0.45f, rect.Height - 2)); + } + + m_Renderer.DrawColor = Color.FromArgb(150, 255, 255, 255); + m_Renderer.DrawShavedCornerRect(new Rectangle(1, 1, rect.Width - 2, rect.Height - 2)); + + m_Renderer.DrawColor = Color.FromArgb(70, 255, 255, 255); + m_Renderer.DrawShavedCornerRect(new Rectangle(2, 2, rect.Width - 4, rect.Height - 4)); + + m_Renderer.DrawColor = m_colBorderColor; + m_Renderer.DrawShavedCornerRect(rect); + } + + public override void DrawListBox(Control.Base control) + { + Rectangle rect = control.RenderBounds; + + m_Renderer.DrawColor = m_colControlBright; + m_Renderer.DrawFilledRect(rect); + + m_Renderer.DrawColor = m_colBorderColor; + m_Renderer.DrawLinedRect(rect); + } + + public override void DrawListBoxLine(Control.Base control, bool selected, bool even) + { + Rectangle rect = control.RenderBounds; + + if (selected) + { + m_Renderer.DrawColor = m_colHighlightBorder; + m_Renderer.DrawFilledRect(rect); + } + else if (control.IsHovered) + { + m_Renderer.DrawColor = m_colHighlightBG; + m_Renderer.DrawFilledRect(rect); + } + } + + public override void DrawSlider(Control.Base control, bool horizontal, int numNotches, int barSize) + { + Rectangle rect = control.RenderBounds; + Rectangle notchRect = rect; + + if (horizontal) + { + rect.Y += (int)(rect.Height * 0.4f); + rect.Height -= (int)(rect.Height * 0.8f); + } + else + { + rect.X += (int)(rect.Width * 0.4f); + rect.Width -= (int)(rect.Width * 0.8f); + } + + m_Renderer.DrawColor = m_colBGDark; + m_Renderer.DrawFilledRect(rect); + + m_Renderer.DrawColor = m_colControlDarker; + m_Renderer.DrawLinedRect(rect); + } + + public override void DrawComboBox(Control.Base control, bool down, bool open) + { + DrawTextBox(control); + } + + public override void DrawKeyboardHighlight(Control.Base control, Rectangle r, int iOffset) + { + Rectangle rect = r; + + rect.X += iOffset; + rect.Y += iOffset; + rect.Width -= iOffset * 2; + rect.Height -= iOffset * 2; + + //draw the top and bottom + bool skip = true; + for (int i = 0; i < rect.Width * 0.5; i++) + { + m_Renderer.DrawColor = Color.FromArgb(255, 0, 0, 0); + if (!skip) + { + m_Renderer.DrawPixel(rect.X + (i * 2), rect.Y); + m_Renderer.DrawPixel(rect.X + (i * 2), rect.Y + rect.Height - 1); + } + else + skip = false; + } + + for (int i = 0; i < rect.Height * 0.5; i++) + { + m_Renderer.DrawColor = Color.FromArgb(255, 0, 0, 0); + m_Renderer.DrawPixel(rect.X, rect.Y + i * 2); + m_Renderer.DrawPixel(rect.X + rect.Width - 1, rect.Y + i * 2); + } + } + + public override void DrawToolTip(Control.Base control) + { + Rectangle rct = control.RenderBounds; + rct.X -= 3; + rct.Y -= 3; + rct.Width += 6; + rct.Height += 6; + + m_Renderer.DrawColor = m_colToolTipBackground; + m_Renderer.DrawFilledRect(rct); + + m_Renderer.DrawColor = m_colToolTipBorder; + m_Renderer.DrawLinedRect(rct); + } + + public override void DrawScrollButton(Control.Base control, Pos direction, bool depressed, bool hovered, bool disabled) + { + DrawButton(control, depressed, false, false); + + m_Renderer.DrawColor = Color.FromArgb(240, 0, 0, 0); + + Rectangle r = new Rectangle(control.Width / 2 - 2, control.Height / 2 - 2, 5, 5); + + if (direction == Pos.Top) DrawArrowUp(r); + else if (direction == Pos.Bottom) DrawArrowDown(r); + else if (direction == Pos.Left) DrawArrowLeft(r); + else DrawArrowRight(r); + } + + public override void DrawComboBoxArrow(Control.Base control, bool hovered, bool depressed, bool open, bool disabled) + { + //DrawButton( control.Width, control.Height, depressed, false, true ); + + m_Renderer.DrawColor = Color.FromArgb(240, 0, 0, 0); + + Rectangle r = new Rectangle(control.Width / 2 - 2, control.Height / 2 - 2, 5, 5); + DrawArrowDown(r); + } + + public override void DrawNumericUpDownButton(Control.Base control, bool depressed, bool up) + { + //DrawButton( control.Width, control.Height, depressed, false, true ); + + m_Renderer.DrawColor = Color.FromArgb(240, 0, 0, 0); + + Rectangle r = new Rectangle(control.Width / 2 - 2, control.Height / 2 - 2, 5, 5); + + if (up) DrawArrowUp(r); + else DrawArrowDown(r); + } + + public override void DrawTreeButton(Control.Base control, bool open) + { + Rectangle rect = control.RenderBounds; + rect.X += 2; + rect.Y += 2; + rect.Width -= 4; + rect.Height -= 4; + + m_Renderer.DrawColor = m_colControlBright; + m_Renderer.DrawFilledRect(rect); + + m_Renderer.DrawColor = m_colBorderColor; + m_Renderer.DrawLinedRect(rect); + + m_Renderer.DrawColor = m_colBorderColor; + + if (!open) // ! because the button shows intention, not the current state + m_Renderer.DrawFilledRect(new Rectangle(rect.X + rect.Width / 2, rect.Y + 2, 1, rect.Height - 4)); + + m_Renderer.DrawFilledRect(new Rectangle(rect.X + 2, rect.Y + rect.Height / 2, rect.Width - 4, 1)); + } + + public override void DrawTreeControl(Control.Base control) + { + Rectangle rect = control.RenderBounds; + + m_Renderer.DrawColor = m_colControlBright; + m_Renderer.DrawFilledRect(rect); + + m_Renderer.DrawColor = m_colBorderColor; + m_Renderer.DrawLinedRect(rect); + } + + public override void DrawTreeNode(Control.Base ctrl, bool open, bool selected, int labelHeight, int labelWidth, int halfWay, int lastBranch, bool isRoot) + { + if (selected) + { + Renderer.DrawColor = Color.FromArgb(100, 0, 150, 255); + Renderer.DrawFilledRect(new Rectangle(17, 0, labelWidth + 2, labelHeight - 1)); + Renderer.DrawColor = Color.FromArgb(200, 0, 150, 255); + Renderer.DrawLinedRect(new Rectangle(17, 0, labelWidth + 2, labelHeight - 1)); + } + + base.DrawTreeNode(ctrl, open, selected, labelHeight, labelWidth, halfWay, lastBranch, isRoot); + } + + public override void DrawStatusBar(Control.Base control) + { + // todo + } + + public override void DrawColorDisplay(Control.Base control, Color color) + { + Rectangle rect = control.RenderBounds; + + if (color.A != 255) + { + Renderer.DrawColor = Color.FromArgb(255, 255, 255, 255); + Renderer.DrawFilledRect(rect); + + Renderer.DrawColor = Color.FromArgb(128, 128, 128, 128); + + Renderer.DrawFilledRect(Util.FloatRect(0, 0, rect.Width * 0.5f, rect.Height * 0.5f)); + Renderer.DrawFilledRect(Util.FloatRect(rect.Width * 0.5f, rect.Height * 0.5f, rect.Width * 0.5f, + rect.Height * 0.5f)); + } + + Renderer.DrawColor = color; + Renderer.DrawFilledRect(rect); + + Renderer.DrawColor = Color.FromArgb(255, 0, 0, 0); + Renderer.DrawLinedRect(rect); + } + + public override void DrawModalControl(Control.Base control) + { + if (control.ShouldDrawBackground) + { + Rectangle rect = control.RenderBounds; + Renderer.DrawColor = m_colModal; + Renderer.DrawFilledRect(rect); + } + } + + public override void DrawMenuDivider(Control.Base control) + { + Rectangle rect = control.RenderBounds; + Renderer.DrawColor = m_colBGDark; + Renderer.DrawFilledRect(rect); + Renderer.DrawColor = m_colControlDarker; + Renderer.DrawLinedRect(rect); + } + + public override void DrawMenuRightArrow(Control.Base control) + { + DrawArrowRight(control.RenderBounds); + } + + public override void DrawSliderButton(Control.Base control, bool depressed, bool horizontal) + { + DrawButton(control, depressed, control.IsHovered, control.IsDisabled); + } + + public override void DrawCategoryHolder(Control.Base control) + { + // todo + } + + public override void DrawCategoryInner(Control.Base control, bool collapsed) + { + // todo + } + #endregion + } +} diff --git a/Gwen/Skin/SkinColors.cs b/Gwen/Skin/SkinColors.cs new file mode 100644 index 0000000..c14f4d1 --- /dev/null +++ b/Gwen/Skin/SkinColors.cs @@ -0,0 +1,118 @@ +using System; +using System.Drawing; + +namespace Gwen.Skin +{ + /// + /// UI colors used by skins. + /// + public struct SkinColors + { + public struct _Window + { + public Color TitleActive; + public Color TitleInactive; + } + + public struct _Button + { + public Color Normal; + public Color Hover; + public Color Down; + public Color Disabled; + } + + public struct _Tab + { + public struct _Inactive + { + public Color Normal; + public Color Hover; + public Color Down; + public Color Disabled; + } + + public struct _Active + { + public Color Normal; + public Color Hover; + public Color Down; + public Color Disabled; + } + + public _Inactive Inactive; + public _Active Active; + } + + public struct _Label + { + public Color Default; + public Color Bright; + public Color Dark; + public Color Highlight; + } + + public struct _Tree + { + public Color Lines; + public Color Normal; + public Color Hover; + public Color Selected; + } + + public struct _Properties + { + public Color Line_Normal; + public Color Line_Selected; + public Color Line_Hover; + public Color Column_Normal; + public Color Column_Selected; + public Color Column_Hover; + public Color Label_Normal; + public Color Label_Selected; + public Color Label_Hover; + public Color Border; + public Color Title; + } + + public struct _Category + { + public Color Header; + public Color Header_Closed; + + public struct _Line + { + public Color Text; + public Color Text_Hover; + public Color Text_Selected; + public Color Button; + public Color Button_Hover; + public Color Button_Selected; + } + + public struct _LineAlt + { + public Color Text; + public Color Text_Hover; + public Color Text_Selected; + public Color Button; + public Color Button_Hover; + public Color Button_Selected; + } + + public _Line Line; + public _LineAlt LineAlt; + } + + public Color ModalBackground; + public Color TooltipText; + + public _Window Window; + public _Button Button; + public _Tab Tab; + public _Label Label; + public _Tree Tree; + public _Properties Properties; + public _Category Category; + } +} diff --git a/Gwen/Skin/TexturedBase.cs b/Gwen/Skin/TexturedBase.cs new file mode 100644 index 0000000..5bec86d --- /dev/null +++ b/Gwen/Skin/TexturedBase.cs @@ -0,0 +1,1183 @@ +using System; +using System.Drawing; +using System.IO; +using Gwen.Skin.Texturing; +using Single = Gwen.Skin.Texturing.Single; + +namespace Gwen.Skin +{ + #region UI element textures + public struct SkinTextures + { + public Bordered StatusBar; + public Bordered Selection; + public Bordered Shadow; + public Bordered Tooltip; + + public struct _Panel + { + public Bordered Normal; + public Bordered Bright; + public Bordered Dark; + public Bordered Highlight; + } + + public struct _Window + { + public Bordered Normal; + public Bordered Inactive; + public Single Close; + public Single Close_Hover; + public Single Close_Down; + public Single Close_Disabled; + } + + public struct _CheckBox + { + public struct _Active + { + public Single Normal; + public Single Checked; + } + public struct _Disabled + { + public Single Normal; + public Single Checked; + } + + public _Active Active; + public _Disabled Disabled; + } + + public struct _RadioButton + { + public struct _Active + { + public Single Normal; + public Single Checked; + } + public struct _Disabled + { + public Single Normal; + public Single Checked; + } + + public _Active Active; + public _Disabled Disabled; + } + + public struct _TextBox + { + public Bordered Normal; + public Bordered Focus; + public Bordered Disabled; + } + + public struct _Tree + { + public Bordered Background; + public Single Minus; + public Single Plus; + } + + public struct _ProgressBar + { + public Bordered Back; + public Bordered Front; + } + + public struct _Scroller + { + public Bordered TrackV; + public Bordered TrackH; + public Bordered ButtonV_Normal; + public Bordered ButtonV_Hover; + public Bordered ButtonV_Down; + public Bordered ButtonV_Disabled; + public Bordered ButtonH_Normal; + public Bordered ButtonH_Hover; + public Bordered ButtonH_Down; + public Bordered ButtonH_Disabled; + + public struct _Button + { + public Bordered[] Normal; + public Bordered[] Hover; + public Bordered[] Down; + public Bordered[] Disabled; + } + + public _Button Button; + } + + public struct _Menu + { + public Single RightArrow; + public Single Check; + + public Bordered Strip; + public Bordered Background; + public Bordered BackgroundWithMargin; + public Bordered Hover; + } + + public struct _Input + { + public struct _Button + { + public Bordered Normal; + public Bordered Hovered; + public Bordered Disabled; + public Bordered Pressed; + } + + public struct _ComboBox + { + public Bordered Normal; + public Bordered Hover; + public Bordered Down; + public Bordered Disabled; + + public struct _Button + { + public Single Normal; + public Single Hover; + public Single Down; + public Single Disabled; + } + + public _Button Button; + } + + public struct _Slider + { + public struct _H + { + public Single Normal; + public Single Hover; + public Single Down; + public Single Disabled; + } + + public struct _V + { + public Single Normal; + public Single Hover; + public Single Down; + public Single Disabled; + } + + public _H H; + public _V V; + } + + public struct _ListBox + { + public Bordered Background; + public Bordered Hovered; + public Bordered EvenLine; + public Bordered OddLine; + public Bordered EvenLineSelected; + public Bordered OddLineSelected; + } + + public struct _UpDown + { + public struct _Up + { + public Single Normal; + public Single Hover; + public Single Down; + public Single Disabled; + } + + public struct _Down + { + public Single Normal; + public Single Hover; + public Single Down; + public Single Disabled; + } + + public _Up Up; + public _Down Down; + } + + public _Button Button; + public _ComboBox ComboBox; + public _Slider Slider; + public _ListBox ListBox; + public _UpDown UpDown; + } + + public struct _Tab + { + public struct _Bottom + { + public Bordered Inactive; + public Bordered Active; + } + + public struct _Top + { + public Bordered Inactive; + public Bordered Active; + } + + public struct _Left + { + public Bordered Inactive; + public Bordered Active; + } + + public struct _Right + { + public Bordered Inactive; + public Bordered Active; + } + + public _Bottom Bottom; + public _Top Top; + public _Left Left; + public _Right Right; + + public Bordered Control; + public Bordered HeaderBar; + } + + public struct _CategoryList + { + public Bordered Outer; + public Bordered Inner; + public Bordered Header; + } + + public _Panel Panel; + public _Window Window; + public _CheckBox CheckBox; + public _RadioButton RadioButton; + public _TextBox TextBox; + public _Tree Tree; + public _ProgressBar ProgressBar; + public _Scroller Scroller; + public _Menu Menu; + public _Input Input; + public _Tab Tab; + public _CategoryList CategoryList; + } + #endregion + + /// + /// Base textured skin. + /// + public class TexturedBase : Skin.Base + { + protected SkinTextures Textures; + + private readonly Texture m_Texture; + + /// + /// Initializes a new instance of the class. + /// + /// Renderer to use. + /// Name of the skin texture map. + public TexturedBase(Renderer.Base renderer, String textureName) + : base(renderer) + { + m_Texture = new Texture(Renderer); + m_Texture.Load(textureName); + + InitializeColors(); + InitializeTextures(); + } + + public TexturedBase(Renderer.Base renderer, Stream textureData) + : base(renderer) + { + m_Texture = new Texture(Renderer); + m_Texture.LoadStream(textureData); + + InitializeColors(); + InitializeTextures(); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public override void Dispose() + { + m_Texture.Dispose(); + base.Dispose(); + } + + #region Initialization + private void InitializeColors() + { + Colors.Window.TitleActive = Renderer.PixelColor(m_Texture, 4 + 8*0, 508, Color.Red); + Colors.Window.TitleInactive = Renderer.PixelColor(m_Texture, 4 + 8*1, 508, Color.Yellow); + + Colors.Button.Normal = Renderer.PixelColor(m_Texture, 4 + 8*2, 508, Color.Yellow); + Colors.Button.Hover = Renderer.PixelColor(m_Texture, 4 + 8*3, 508, Color.Yellow); + Colors.Button.Down = Renderer.PixelColor(m_Texture, 4 + 8*2, 500, Color.Yellow); + Colors.Button.Disabled = Renderer.PixelColor(m_Texture, 4 + 8*3, 500, Color.Yellow); + + Colors.Tab.Active.Normal = Renderer.PixelColor(m_Texture, 4 + 8*4, 508, Color.Yellow); + Colors.Tab.Active.Hover = Renderer.PixelColor(m_Texture, 4 + 8*5, 508, Color.Yellow); + Colors.Tab.Active.Down = Renderer.PixelColor(m_Texture, 4 + 8*4, 500, Color.Yellow); + Colors.Tab.Active.Disabled = Renderer.PixelColor(m_Texture, 4 + 8*5, 500, Color.Yellow); + Colors.Tab.Inactive.Normal = Renderer.PixelColor(m_Texture, 4 + 8*6, 508, Color.Yellow); + Colors.Tab.Inactive.Hover = Renderer.PixelColor(m_Texture, 4 + 8*7, 508, Color.Yellow); + Colors.Tab.Inactive.Down = Renderer.PixelColor(m_Texture, 4 + 8*6, 500, Color.Yellow); + Colors.Tab.Inactive.Disabled = Renderer.PixelColor(m_Texture, 4 + 8*7, 500, Color.Yellow); + + Colors.Label.Default = Renderer.PixelColor(m_Texture, 4 + 8*8, 508, Color.Yellow); + Colors.Label.Bright = Renderer.PixelColor(m_Texture, 4 + 8*9, 508, Color.Yellow); + Colors.Label.Dark = Renderer.PixelColor(m_Texture, 4 + 8*8, 500, Color.Yellow); + Colors.Label.Highlight = Renderer.PixelColor(m_Texture, 4 + 8*9, 500, Color.Yellow); + + Colors.Tree.Lines = Renderer.PixelColor(m_Texture, 4 + 8*10, 508, Color.Yellow); + Colors.Tree.Normal = Renderer.PixelColor(m_Texture, 4 + 8*11, 508, Color.Yellow); + Colors.Tree.Hover = Renderer.PixelColor(m_Texture, 4 + 8*10, 500, Color.Yellow); + Colors.Tree.Selected = Renderer.PixelColor(m_Texture, 4 + 8*11, 500, Color.Yellow); + + Colors.Properties.Line_Normal = Renderer.PixelColor(m_Texture, 4 + 8*12, 508, Color.Yellow); + Colors.Properties.Line_Selected = Renderer.PixelColor(m_Texture, 4 + 8*13, 508, Color.Yellow); + Colors.Properties.Line_Hover = Renderer.PixelColor(m_Texture, 4 + 8*12, 500, Color.Yellow); + Colors.Properties.Title = Renderer.PixelColor(m_Texture, 4 + 8*13, 500, Color.Yellow); + Colors.Properties.Column_Normal = Renderer.PixelColor(m_Texture, 4 + 8*14, 508, Color.Yellow); + Colors.Properties.Column_Selected = Renderer.PixelColor(m_Texture, 4 + 8*15, 508, Color.Yellow); + Colors.Properties.Column_Hover = Renderer.PixelColor(m_Texture, 4 + 8*14, 500, Color.Yellow); + Colors.Properties.Border = Renderer.PixelColor(m_Texture, 4 + 8*15, 500, Color.Yellow); + Colors.Properties.Label_Normal = Renderer.PixelColor(m_Texture, 4 + 8*16, 508, Color.Yellow); + Colors.Properties.Label_Selected = Renderer.PixelColor(m_Texture, 4 + 8*17, 508, Color.Yellow); + Colors.Properties.Label_Hover = Renderer.PixelColor(m_Texture, 4 + 8*16, 500, Color.Yellow); + + Colors.ModalBackground = Renderer.PixelColor(m_Texture, 4 + 8*18, 508, Color.Yellow); + + Colors.TooltipText = Renderer.PixelColor(m_Texture, 4 + 8*19, 508, Color.Yellow); + + Colors.Category.Header = Renderer.PixelColor(m_Texture, 4 + 8*18, 500, Color.Yellow); + Colors.Category.Header_Closed = Renderer.PixelColor(m_Texture, 4 + 8*19, 500, Color.Yellow); + Colors.Category.Line.Text = Renderer.PixelColor(m_Texture, 4 + 8*20, 508, Color.Yellow); + Colors.Category.Line.Text_Hover = Renderer.PixelColor(m_Texture, 4 + 8*21, 508, Color.Yellow); + Colors.Category.Line.Text_Selected = Renderer.PixelColor(m_Texture, 4 + 8*20, 500, Color.Yellow); + Colors.Category.Line.Button = Renderer.PixelColor(m_Texture, 4 + 8*21, 500, Color.Yellow); + Colors.Category.Line.Button_Hover = Renderer.PixelColor(m_Texture, 4 + 8*22, 508, Color.Yellow); + Colors.Category.Line.Button_Selected = Renderer.PixelColor(m_Texture, 4 + 8*23, 508, Color.Yellow); + Colors.Category.LineAlt.Text = Renderer.PixelColor(m_Texture, 4 + 8*22, 500, Color.Yellow); + Colors.Category.LineAlt.Text_Hover = Renderer.PixelColor(m_Texture, 4 + 8*23, 500, Color.Yellow); + Colors.Category.LineAlt.Text_Selected = Renderer.PixelColor(m_Texture, 4 + 8*24, 508, Color.Yellow); + Colors.Category.LineAlt.Button = Renderer.PixelColor(m_Texture, 4 + 8*25, 508, Color.Yellow); + Colors.Category.LineAlt.Button_Hover = Renderer.PixelColor(m_Texture, 4 + 8*24, 500, Color.Yellow); + Colors.Category.LineAlt.Button_Selected = Renderer.PixelColor(m_Texture, 4 + 8*25, 500, Color.Yellow); + } + + private void InitializeTextures() + { + Textures.Shadow = new Bordered(m_Texture, 448, 0, 31, 31, Margin.Eight); + Textures.Tooltip = new Bordered(m_Texture, 128, 320, 127, 31, Margin.Eight); + Textures.StatusBar = new Bordered(m_Texture, 128, 288, 127, 31, Margin.Eight); + Textures.Selection = new Bordered(m_Texture, 384, 32, 31, 31, Margin.Four); + + Textures.Panel.Normal = new Bordered(m_Texture, 256, 0, 63, 63, new Margin(16, 16, 16, 16)); + Textures.Panel.Bright = new Bordered(m_Texture, 256 + 64, 0, 63, 63, new Margin(16, 16, 16, 16)); + Textures.Panel.Dark = new Bordered(m_Texture, 256, 64, 63, 63, new Margin(16, 16, 16, 16)); + Textures.Panel.Highlight = new Bordered(m_Texture, 256 + 64, 64, 63, 63, new Margin(16, 16, 16, 16)); + + Textures.Window.Normal = new Bordered(m_Texture, 0, 0, 127, 127, new Margin(8, 32, 8, 8)); + Textures.Window.Inactive = new Bordered(m_Texture, 128, 0, 127, 127, new Margin(8, 32, 8, 8)); + + Textures.CheckBox.Active.Checked = new Single(m_Texture, 448, 32, 15, 15); + Textures.CheckBox.Active.Normal = new Single(m_Texture, 464, 32, 15, 15); + Textures.CheckBox.Disabled.Normal = new Single(m_Texture, 448, 48, 15, 15); + Textures.CheckBox.Disabled.Normal = new Single(m_Texture, 464, 48, 15, 15); + + Textures.RadioButton.Active.Checked = new Single(m_Texture, 448, 64, 15, 15); + Textures.RadioButton.Active.Normal = new Single(m_Texture, 464, 64, 15, 15); + Textures.RadioButton.Disabled.Normal = new Single(m_Texture, 448, 80, 15, 15); + Textures.RadioButton.Disabled.Normal = new Single(m_Texture, 464, 80, 15, 15); + + Textures.TextBox.Normal = new Bordered(m_Texture, 0, 150, 127, 21, Margin.Four); + Textures.TextBox.Focus = new Bordered(m_Texture, 0, 172, 127, 21, Margin.Four); + Textures.TextBox.Disabled = new Bordered(m_Texture, 0, 193, 127, 21, Margin.Four); + + Textures.Menu.Strip = new Bordered(m_Texture, 0, 128, 127, 21, Margin.One); + Textures.Menu.BackgroundWithMargin = new Bordered(m_Texture, 128, 128, 127, 63, new Margin(24, 8, 8, 8)); + Textures.Menu.Background = new Bordered(m_Texture, 128, 192, 127, 63, Margin.Eight); + Textures.Menu.Hover = new Bordered(m_Texture, 128, 256, 127, 31, Margin.Eight); + Textures.Menu.RightArrow = new Single(m_Texture, 464, 112, 15, 15); + Textures.Menu.Check = new Single(m_Texture, 448, 112, 15, 15); + + Textures.Tab.Control = new Bordered(m_Texture, 0, 256, 127, 127, Margin.Eight); + Textures.Tab.Bottom.Active = new Bordered(m_Texture, 0, 416, 63, 31, Margin.Eight); + Textures.Tab.Bottom.Inactive = new Bordered(m_Texture, 0 + 128, 416, 63, 31, Margin.Eight); + Textures.Tab.Top.Active = new Bordered(m_Texture, 0, 384, 63, 31, Margin.Eight); + Textures.Tab.Top.Inactive = new Bordered(m_Texture, 0 + 128, 384, 63, 31, Margin.Eight); + Textures.Tab.Left.Active = new Bordered(m_Texture, 64, 384, 31, 63, Margin.Eight); + Textures.Tab.Left.Inactive = new Bordered(m_Texture, 64 + 128, 384, 31, 63, Margin.Eight); + Textures.Tab.Right.Active = new Bordered(m_Texture, 96, 384, 31, 63, Margin.Eight); + Textures.Tab.Right.Inactive = new Bordered(m_Texture, 96 + 128, 384, 31, 63, Margin.Eight); + Textures.Tab.HeaderBar = new Bordered(m_Texture, 128, 352, 127, 31, Margin.Four); + + Textures.Window.Close = new Single(m_Texture, 0, 224, 24, 24); + Textures.Window.Close_Hover = new Single(m_Texture, 32, 224, 24, 24); + Textures.Window.Close_Hover = new Single(m_Texture, 64, 224, 24, 24); + Textures.Window.Close_Hover = new Single(m_Texture, 96, 224, 24, 24); + + Textures.Scroller.TrackV = new Bordered(m_Texture, 384, 208, 15, 127, Margin.Four); + Textures.Scroller.ButtonV_Normal = new Bordered(m_Texture, 384 + 16, 208, 15, 127, Margin.Four); + Textures.Scroller.ButtonV_Hover = new Bordered(m_Texture, 384 + 32, 208, 15, 127, Margin.Four); + Textures.Scroller.ButtonV_Down = new Bordered(m_Texture, 384 + 48, 208, 15, 127, Margin.Four); + Textures.Scroller.ButtonV_Disabled = new Bordered(m_Texture, 384 + 64, 208, 15, 127, Margin.Four); + Textures.Scroller.TrackH = new Bordered(m_Texture, 384, 128, 127, 15, Margin.Four); + Textures.Scroller.ButtonH_Normal = new Bordered(m_Texture, 384, 128 + 16, 127, 15, Margin.Four); + Textures.Scroller.ButtonH_Hover = new Bordered(m_Texture, 384, 128 + 32, 127, 15, Margin.Four); + Textures.Scroller.ButtonH_Down = new Bordered(m_Texture, 384, 128 + 48, 127, 15, Margin.Four); + Textures.Scroller.ButtonH_Disabled = new Bordered(m_Texture, 384, 128 + 64, 127, 15, Margin.Four); + + Textures.Scroller.Button.Normal = new Bordered[4]; + Textures.Scroller.Button.Disabled = new Bordered[4]; + Textures.Scroller.Button.Hover = new Bordered[4]; + Textures.Scroller.Button.Down = new Bordered[4]; + + Textures.Tree.Background = new Bordered(m_Texture, 256, 128, 127, 127, new Margin(16, 16, 16, 16)); + Textures.Tree.Plus = new Single(m_Texture, 448, 96, 15, 15); + Textures.Tree.Minus = new Single(m_Texture, 464, 96, 15, 15); + + Textures.Input.Button.Normal = new Bordered(m_Texture, 480, 0, 31, 31, Margin.Eight); + Textures.Input.Button.Hovered = new Bordered(m_Texture, 480, 32, 31, 31, Margin.Eight); + Textures.Input.Button.Disabled = new Bordered(m_Texture, 480, 64, 31, 31, Margin.Eight); + Textures.Input.Button.Pressed = new Bordered(m_Texture, 480, 96, 31, 31, Margin.Eight); + + for (int i = 0; i < 4; i++) + { + Textures.Scroller.Button.Normal[i] = new Bordered(m_Texture, 464 + 0, 208 + i * 16, 15, 15, Margin.Two); + Textures.Scroller.Button.Hover[i] = new Bordered(m_Texture, 480, 208 + i * 16, 15, 15, Margin.Two); + Textures.Scroller.Button.Down[i] = new Bordered(m_Texture, 464, 272 + i * 16, 15, 15, Margin.Two); + Textures.Scroller.Button.Disabled[i] = new Bordered(m_Texture, 480 + 48, 272 + i * 16, 15, 15, Margin.Two); + } + + Textures.Input.ListBox.Background = new Bordered(m_Texture, 256, 256, 63, 127, Margin.Eight); + Textures.Input.ListBox.Hovered = new Bordered(m_Texture, 320, 320, 31, 31, Margin.Eight); + Textures.Input.ListBox.EvenLine = new Bordered(m_Texture, 352, 256, 31, 31, Margin.Eight); + Textures.Input.ListBox.OddLine = new Bordered(m_Texture, 352, 288, 31, 31, Margin.Eight); + Textures.Input.ListBox.EvenLineSelected = new Bordered(m_Texture, 320, 270, 31, 31, Margin.Eight); + Textures.Input.ListBox.OddLineSelected = new Bordered(m_Texture, 320, 288, 31, 31, Margin.Eight); + + Textures.Input.ComboBox.Normal = new Bordered(m_Texture, 384, 336, 127, 31, new Margin(8, 8, 32, 8)); + Textures.Input.ComboBox.Hover = new Bordered(m_Texture, 384, 336 + 32, 127, 31, new Margin(8, 8, 32, 8)); + Textures.Input.ComboBox.Down = new Bordered(m_Texture, 384, 336 + 64, 127, 31, new Margin(8, 8, 32, 8)); + Textures.Input.ComboBox.Disabled = new Bordered(m_Texture, 384, 336 + 96, 127, 31, new Margin(8, 8, 32, 8)); + + Textures.Input.ComboBox.Button.Normal = new Single(m_Texture, 496, 272, 15, 15); + Textures.Input.ComboBox.Button.Hover = new Single(m_Texture, 496, 272 + 16, 15, 15); + Textures.Input.ComboBox.Button.Down = new Single(m_Texture, 496, 272 + 32, 15, 15); + Textures.Input.ComboBox.Button.Disabled = new Single(m_Texture, 496, 272 + 48, 15, 15); + + Textures.Input.UpDown.Up.Normal = new Single(m_Texture, 384, 112, 7, 7); + Textures.Input.UpDown.Up.Hover = new Single(m_Texture, 384 + 8, 112, 7, 7); + Textures.Input.UpDown.Up.Down = new Single(m_Texture, 384 + 16, 112, 7, 7); + Textures.Input.UpDown.Up.Disabled = new Single(m_Texture, 384 + 24, 112, 7, 7); + Textures.Input.UpDown.Down.Normal = new Single(m_Texture, 384, 120, 7, 7); + Textures.Input.UpDown.Down.Hover = new Single(m_Texture, 384 + 8, 120, 7, 7); + Textures.Input.UpDown.Down.Down = new Single(m_Texture, 384 + 16, 120, 7, 7); + Textures.Input.UpDown.Down.Disabled = new Single(m_Texture, 384 + 24, 120, 7, 7); + + Textures.ProgressBar.Back = new Bordered(m_Texture, 384, 0, 31, 31, Margin.Eight); + Textures.ProgressBar.Front = new Bordered(m_Texture, 384 + 32, 0, 31, 31, Margin.Eight); + + Textures.Input.Slider.H.Normal = new Single(m_Texture, 416, 32, 15, 15); + Textures.Input.Slider.H.Hover = new Single(m_Texture, 416, 32 + 16, 15, 15); + Textures.Input.Slider.H.Down = new Single(m_Texture, 416, 32 + 32, 15, 15); + Textures.Input.Slider.H.Disabled = new Single(m_Texture, 416, 32 + 48, 15, 15); + + Textures.Input.Slider.V.Normal = new Single(m_Texture, 416 + 16, 32, 15, 15); + Textures.Input.Slider.V.Hover = new Single(m_Texture, 416 + 16, 32 + 16, 15, 15); + Textures.Input.Slider.V.Down = new Single(m_Texture, 416 + 16, 32 + 32, 15, 15); + Textures.Input.Slider.V.Disabled = new Single(m_Texture, 416 + 16, 32 + 48, 15, 15); + + Textures.CategoryList.Outer = new Bordered(m_Texture, 256, 384, 63, 63, Margin.Eight); + Textures.CategoryList.Inner = new Bordered(m_Texture, 256 + 64, 384, 63, 63, new Margin(8, 21, 8, 8)); + Textures.CategoryList.Header = new Bordered(m_Texture, 320, 352, 63, 31, Margin.Eight); + } + #endregion + + #region UI elements + public override void DrawButton(Control.Base control, bool depressed, bool hovered, bool disabled) + { + if (disabled) + { + Textures.Input.Button.Disabled.Draw(Renderer, control.RenderBounds); + return; + } + if (depressed) + { + Textures.Input.Button.Pressed.Draw(Renderer, control.RenderBounds); + return; + } + if (hovered) + { + Textures.Input.Button.Hovered.Draw(Renderer, control.RenderBounds); + return; + } + Textures.Input.Button.Normal.Draw(Renderer, control.RenderBounds); + } + + public override void DrawMenuRightArrow(Control.Base control) + { + Textures.Menu.RightArrow.Draw(Renderer, control.RenderBounds); + } + + public override void DrawMenuItem(Control.Base control, bool submenuOpen, bool isChecked) + { + if (submenuOpen || control.IsHovered) + Textures.Menu.Hover.Draw(Renderer, control.RenderBounds); + + if (isChecked) + Textures.Menu.Check.Draw(Renderer, new Rectangle(control.RenderBounds.X + 4, control.RenderBounds.Y + 3, 15, 15)); + } + + public override void DrawMenuStrip(Control.Base control) + { + Textures.Menu.Strip.Draw(Renderer, control.RenderBounds); + } + + public override void DrawMenu(Control.Base control, bool paddingDisabled) + { + if (!paddingDisabled) + { + Textures.Menu.BackgroundWithMargin.Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Menu.Background.Draw(Renderer, control.RenderBounds); + } + + public override void DrawShadow(Control.Base control) + { + Rectangle r = control.RenderBounds; + r.X -= 4; + r.Y -= 4; + r.Width += 10; + r.Height += 10; + Textures.Shadow.Draw(Renderer, r); + } + + public override void DrawRadioButton(Control.Base control, bool selected, bool depressed) + { + if (selected) + { + if (control.IsDisabled) + Textures.RadioButton.Disabled.Checked.Draw(Renderer, control.RenderBounds); + else + Textures.RadioButton.Active.Checked.Draw(Renderer, control.RenderBounds); + } + else + { + if (control.IsDisabled) + Textures.RadioButton.Disabled.Normal.Draw(Renderer, control.RenderBounds); + else + Textures.RadioButton.Active.Normal.Draw(Renderer, control.RenderBounds); + } + } + + public override void DrawCheckBox(Control.Base control, bool selected, bool depressed) + { + if (selected) + { + if (control.IsDisabled) + Textures.CheckBox.Disabled.Checked.Draw(Renderer, control.RenderBounds); + else + Textures.CheckBox.Active.Checked.Draw(Renderer, control.RenderBounds); + } + else + { + if (control.IsDisabled) + Textures.CheckBox.Disabled.Normal.Draw(Renderer, control.RenderBounds); + else + Textures.CheckBox.Active.Normal.Draw(Renderer, control.RenderBounds); + } + } + + public override void DrawGroupBox(Control.Base control, int textStart, int textHeight, int textWidth) + { + Rectangle rect = control.RenderBounds; + + rect.Y += (int)(textHeight * 0.5f); + rect.Height -= (int)(textHeight * 0.5f); + + Color m_colDarker = Color.FromArgb(50, 0, 50, 60); + Color m_colLighter = Color.FromArgb(150, 255, 255, 255); + + Renderer.DrawColor = m_colLighter; + + Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + 1, textStart - 3, 1)); + Renderer.DrawFilledRect(new Rectangle(rect.X + 1 + textStart + textWidth, rect.Y + 1, rect.Width - textStart + textWidth - 2, 1)); + Renderer.DrawFilledRect(new Rectangle(rect.X + 1, (rect.Y + rect.Height) - 1, rect.X + rect.Width - 2, 1)); + + Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y + 1, 1, rect.Height)); + Renderer.DrawFilledRect(new Rectangle((rect.X + rect.Width) - 2, rect.Y + 1, 1, rect.Height - 1)); + + Renderer.DrawColor = m_colDarker; + + Renderer.DrawFilledRect(new Rectangle(rect.X + 1, rect.Y, textStart - 3, 1)); + Renderer.DrawFilledRect(new Rectangle(rect.X + 1 + textStart + textWidth, rect.Y, rect.Width - textStart - textWidth - 2, 1)); + Renderer.DrawFilledRect(new Rectangle(rect.X + 1, (rect.Y + rect.Height) - 1, rect.X + rect.Width - 2, 1)); + + Renderer.DrawFilledRect(new Rectangle(rect.X, rect.Y + 1, 1, rect.Height - 1)); + Renderer.DrawFilledRect(new Rectangle((rect.X + rect.Width) - 1, rect.Y + 1, 1, rect.Height - 1)); + } + + public override void DrawTextBox(Control.Base control) + { + if (control.IsDisabled) + { + Textures.TextBox.Disabled.Draw(Renderer, control.RenderBounds); + return; + } + + if (control.HasFocus) + Textures.TextBox.Focus.Draw(Renderer, control.RenderBounds); + else + Textures.TextBox.Normal.Draw(Renderer, control.RenderBounds); + } + + public override void DrawTabButton(Control.Base control, bool active, Pos dir) + { + if (active) + { + DrawActiveTabButton(control, dir); + return; + } + + if (dir == Pos.Top) + { + Textures.Tab.Top.Inactive.Draw(Renderer, control.RenderBounds); + return; + } + if (dir == Pos.Left) + { + Textures.Tab.Left.Inactive.Draw(Renderer, control.RenderBounds); + return; + } + if (dir == Pos.Bottom) + { + Textures.Tab.Bottom.Inactive.Draw(Renderer, control.RenderBounds); + return; + } + if (dir == Pos.Right) + { + Textures.Tab.Right.Inactive.Draw(Renderer, control.RenderBounds); + return; + } + } + + private void DrawActiveTabButton(Control.Base control, Pos dir) + { + if (dir == Pos.Top) + { + Textures.Tab.Top.Active.Draw(Renderer, control.RenderBounds.Add(new Rectangle(0, 0, 0, 8))); + return; + } + if (dir == Pos.Left) + { + Textures.Tab.Left.Active.Draw(Renderer, control.RenderBounds.Add(new Rectangle(0, 0, 8, 0))); + return; + } + if (dir == Pos.Bottom) + { + Textures.Tab.Bottom.Active.Draw(Renderer, control.RenderBounds.Add(new Rectangle(0, -8, 0, 8))); + return; + } + if (dir == Pos.Right) + { + Textures.Tab.Right.Active.Draw(Renderer, control.RenderBounds.Add(new Rectangle(-8, 0, 8, 0))); + return; + } + } + + public override void DrawTabControl(Control.Base control) + { + Textures.Tab.Control.Draw(Renderer, control.RenderBounds); + } + + public override void DrawTabTitleBar(Control.Base control) + { + Textures.Tab.HeaderBar.Draw(Renderer, control.RenderBounds); + } + + public override void DrawWindow(Control.Base control, int topHeight, bool inFocus) + { + if (inFocus) + Textures.Window.Normal.Draw(Renderer, control.RenderBounds); + else + Textures.Window.Inactive.Draw(Renderer, control.RenderBounds); + } + + public override void DrawHighlight(Control.Base control) + { + Rectangle rect = control.RenderBounds; + Renderer.DrawColor = Color.FromArgb(255, 255, 100, 255); + Renderer.DrawFilledRect(rect); + } + + public override void DrawScrollBar(Control.Base control, bool horizontal, bool depressed) + { + if (horizontal) + Textures.Scroller.TrackH.Draw(Renderer, control.RenderBounds); + else + Textures.Scroller.TrackV.Draw(Renderer, control.RenderBounds); + } + + public override void DrawScrollBarBar(Control.Base control, bool depressed, bool hovered, bool horizontal) + { + if (!horizontal) + { + if (control.IsDisabled) + { + Textures.Scroller.ButtonV_Disabled.Draw(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Scroller.ButtonV_Down.Draw(Renderer, control.RenderBounds); + return; + } + + if (hovered) + { + Textures.Scroller.ButtonV_Hover.Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Scroller.ButtonV_Normal.Draw(Renderer, control.RenderBounds); + return; + } + + if (control.IsDisabled) + { + Textures.Scroller.ButtonH_Disabled.Draw(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Scroller.ButtonH_Down.Draw(Renderer, control.RenderBounds); + return; + } + + if (hovered) + { + Textures.Scroller.ButtonH_Hover.Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Scroller.ButtonH_Normal.Draw(Renderer, control.RenderBounds); + } + + public override void DrawProgressBar(Control.Base control, bool horizontal, float progress) + { + Rectangle rect = control.RenderBounds; + + if (horizontal) + { + Textures.ProgressBar.Back.Draw(Renderer, rect); + rect.Width = (int) (rect.Width*progress); + Textures.ProgressBar.Front.Draw(Renderer, rect); + } + else + { + Textures.ProgressBar.Back.Draw(Renderer, rect); + rect.Y = (int) (rect.Y + rect.Height*(1 - progress)); + rect.Height = (int)(rect.Height * progress); + Textures.ProgressBar.Front.Draw(Renderer, rect); + } + } + + public override void DrawListBox(Control.Base control) + { + Textures.Input.ListBox.Background.Draw(Renderer, control.RenderBounds); + } + + public override void DrawListBoxLine(Control.Base control, bool selected, bool even) + { + if (selected) + { + if (even) + { + Textures.Input.ListBox.EvenLineSelected.Draw(Renderer, control.RenderBounds); + return; + } + Textures.Input.ListBox.OddLineSelected.Draw(Renderer, control.RenderBounds); + return; + } + + if (control.IsHovered) + { + Textures.Input.ListBox.Hovered.Draw(Renderer, control.RenderBounds); + return; + } + + if (even) + { + Textures.Input.ListBox.EvenLine.Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Input.ListBox.OddLine.Draw(Renderer, control.RenderBounds); + } + + public void DrawSliderNotchesH(Rectangle rect, int numNotches, float dist) + { + if (numNotches == 0) return; + + float iSpacing = rect.Width / (float)numNotches; + for (int i = 0; i < numNotches + 1; i++) + Renderer.DrawFilledRect(Util.FloatRect(rect.X + iSpacing * i, rect.Y + dist - 2, 1, 5)); + } + + public void DrawSliderNotchesV(Rectangle rect, int numNotches, float dist) + { + if (numNotches == 0) return; + + float iSpacing = rect.Height / (float)numNotches; + for (int i = 0; i < numNotches + 1; i++) + Renderer.DrawFilledRect(Util.FloatRect(rect.X + dist - 2, rect.Y + iSpacing * i, 5, 1)); + } + + public override void DrawSlider(Control.Base control, bool horizontal, int numNotches, int barSize) + { + Rectangle rect = control.RenderBounds; + Renderer.DrawColor = Color.FromArgb(100, 0, 0, 0); + + if (horizontal) + { + rect.X += (int) (barSize*0.5); + rect.Width -= barSize; + rect.Y += (int)(rect.Height * 0.5 - 1); + rect.Height = 1; + DrawSliderNotchesH(rect, numNotches, barSize*0.5f); + Renderer.DrawFilledRect(rect); + return; + } + + rect.Y += (int)(barSize * 0.5); + rect.Height -= barSize; + rect.X += (int)(rect.Width * 0.5 - 1); + rect.Width = 1; + DrawSliderNotchesV(rect, numNotches, barSize * 0.4f); + Renderer.DrawFilledRect(rect); + } + + public override void DrawComboBox(Control.Base control, bool down, bool open) + { + if (control.IsDisabled) + { + Textures.Input.ComboBox.Disabled.Draw(Renderer, control.RenderBounds); + return; + } + + if (down || open) + { + Textures.Input.ComboBox.Down.Draw(Renderer, control.RenderBounds); + return; + } + + if (control.IsHovered) + { + Textures.Input.ComboBox.Down.Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Input.ComboBox.Normal.Draw(Renderer, control.RenderBounds); + } + + public override void DrawKeyboardHighlight(Control.Base control, Rectangle r, int offset) + { + Rectangle rect = r; + + rect.X += offset; + rect.Y += offset; + rect.Width -= offset * 2; + rect.Height -= offset * 2; + + //draw the top and bottom + bool skip = true; + for (int i = 0; i < rect.Width * 0.5; i++) + { + m_Renderer.DrawColor = Color.Black; + if (!skip) + { + Renderer.DrawPixel(rect.X + (i * 2), rect.Y); + Renderer.DrawPixel(rect.X + (i * 2), rect.Y + rect.Height - 1); + } + else + skip = false; + } + + for (int i = 0; i < rect.Height * 0.5; i++) + { + Renderer.DrawColor = Color.Black; + Renderer.DrawPixel(rect.X, rect.Y + i * 2); + Renderer.DrawPixel(rect.X + rect.Width - 1, rect.Y + i * 2); + } + } + + public override void DrawToolTip(Control.Base control) + { + Textures.Tooltip.Draw(Renderer, control.RenderBounds); + } + + public override void DrawScrollButton(Control.Base control, Pos direction, bool depressed, bool hovered, bool disabled) + { + int i = 0; + if (direction == Pos.Top) i = 1; + if (direction == Pos.Right) i = 2; + if (direction == Pos.Bottom) i = 3; + + if (disabled) + { + Textures.Scroller.Button.Disabled[i].Draw(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Scroller.Button.Down[i].Draw(Renderer, control.RenderBounds); + return; + } + + if (hovered) + { + Textures.Scroller.Button.Hover[i].Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Scroller.Button.Normal[i].Draw(Renderer, control.RenderBounds); + } + + public override void DrawComboBoxArrow(Control.Base control, bool hovered, bool down, bool open, bool disabled) + { + if (disabled) + { + Textures.Input.ComboBox.Button.Disabled.Draw(Renderer, control.RenderBounds); + return; + } + + if (down || open) + { + Textures.Input.ComboBox.Button.Down.Draw(Renderer, control.RenderBounds); + return; + } + + if (hovered) + { + Textures.Input.ComboBox.Button.Hover.Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Input.ComboBox.Button.Normal.Draw(Renderer, control.RenderBounds); + } + + public override void DrawNumericUpDownButton(Control.Base control, bool depressed, bool up) + { + if (up) + { + if (control.IsDisabled) + { + Textures.Input.UpDown.Up.Disabled.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Input.UpDown.Up.Down.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (control.IsHovered) + { + Textures.Input.UpDown.Up.Hover.DrawCenter(Renderer, control.RenderBounds); + return; + } + + Textures.Input.UpDown.Up.Normal.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (control.IsDisabled) + { + Textures.Input.UpDown.Down.Disabled.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Input.UpDown.Down.Down.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (control.IsHovered) + { + Textures.Input.UpDown.Down.Hover.DrawCenter(Renderer, control.RenderBounds); + return; + } + + Textures.Input.UpDown.Down.Normal.DrawCenter(Renderer, control.RenderBounds); + } + + public override void DrawStatusBar(Control.Base control) + { + Textures.StatusBar.Draw(Renderer, control.RenderBounds); + } + + public override void DrawTreeButton(Control.Base control, bool open) + { + Rectangle rect = control.RenderBounds; + + if (open) + Textures.Tree.Minus.Draw(Renderer, rect); + else + Textures.Tree.Plus.Draw(Renderer, rect); + } + + public override void DrawTreeControl(Control.Base control) + { + Textures.Tree.Background.Draw(Renderer, control.RenderBounds); + } + + public override void DrawTreeNode(Control.Base ctrl, bool open, bool selected, int labelHeight, int labelWidth, int halfWay, int lastBranch, bool isRoot) + { + if (selected) + { + Textures.Selection.Draw(Renderer, new Rectangle(17, 0, labelWidth + 2, labelHeight - 1)); + } + + base.DrawTreeNode(ctrl, open, selected, labelHeight, labelWidth, halfWay, lastBranch, isRoot); + } + + public override void DrawColorDisplay(Control.Base control, Color color) + { + Rectangle rect = control.RenderBounds; + + if (color.A != 255) + { + Renderer.DrawColor = Color.FromArgb(255, 255, 255, 255); + Renderer.DrawFilledRect(rect); + + Renderer.DrawColor = Color.FromArgb(128, 128, 128, 128); + + Renderer.DrawFilledRect(Util.FloatRect(0, 0, rect.Width * 0.5f, rect.Height * 0.5f)); + Renderer.DrawFilledRect(Util.FloatRect(rect.Width * 0.5f, rect.Height * 0.5f, rect.Width * 0.5f, rect.Height * 0.5f)); + } + + Renderer.DrawColor = color; + Renderer.DrawFilledRect(rect); + + Renderer.DrawColor = Color.Black; + Renderer.DrawLinedRect(rect); + } + + public override void DrawModalControl(Control.Base control) + { + if (!control.ShouldDrawBackground) + return; + Rectangle rect = control.RenderBounds; + Renderer.DrawColor = Colors.ModalBackground; + Renderer.DrawFilledRect(rect); + } + + public override void DrawMenuDivider(Control.Base control) + { + Rectangle rect = control.RenderBounds; + Renderer.DrawColor = Color.FromArgb(100, 0, 0, 0); + Renderer.DrawFilledRect(rect); + } + + public override void DrawWindowCloseButton(Control.Base control, bool depressed, bool hovered, bool disabled) + { + + if (disabled) + { + Textures.Window.Close_Disabled.Draw(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Window.Close_Down.Draw(Renderer, control.RenderBounds); + return; + } + + if (hovered) + { + Textures.Window.Close_Hover.Draw(Renderer, control.RenderBounds); + return; + } + + Textures.Window.Close.Draw(Renderer, control.RenderBounds); + } + + public override void DrawSliderButton(Control.Base control, bool depressed, bool horizontal) + { + if (!horizontal) + { + if (control.IsDisabled) + { + Textures.Input.Slider.V.Disabled.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Input.Slider.V.Down.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (control.IsHovered) + { + Textures.Input.Slider.V.Hover.DrawCenter(Renderer, control.RenderBounds); + return; + } + + Textures.Input.Slider.V.Normal.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (control.IsDisabled) + { + Textures.Input.Slider.H.Disabled.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (depressed) + { + Textures.Input.Slider.H.Down.DrawCenter(Renderer, control.RenderBounds); + return; + } + + if (control.IsHovered) + { + Textures.Input.Slider.H.Hover.DrawCenter(Renderer, control.RenderBounds); + return; + } + + Textures.Input.Slider.H.Normal.DrawCenter(Renderer, control.RenderBounds); + } + + public override void DrawCategoryHolder(Control.Base control) + { + Textures.CategoryList.Outer.Draw(Renderer, control.RenderBounds); + } + + public override void DrawCategoryInner(Control.Base control, bool collapsed) + { + if (collapsed) + Textures.CategoryList.Header.Draw(Renderer, control.RenderBounds); + else + Textures.CategoryList.Inner.Draw(Renderer, control.RenderBounds); + } + #endregion + } +} diff --git a/Gwen/Skin/Texturing/Bordered.cs b/Gwen/Skin/Texturing/Bordered.cs new file mode 100644 index 0000000..5cd5708 --- /dev/null +++ b/Gwen/Skin/Texturing/Bordered.cs @@ -0,0 +1,125 @@ +using System.Drawing; + +namespace Gwen.Skin.Texturing +{ + public struct SubRect + { + public float[] uv; + } + + /// + /// 3x3 texture grid. + /// + public struct Bordered + { + private Texture m_Texture; + + private readonly SubRect[] m_Rects; + + private Margin m_Margin; + + private float m_Width; + private float m_Height; + + public Bordered(Texture texture, float x, float y, float w, float h, Margin inMargin, float drawMarginScale = 1.0f) + : this() + { + m_Rects = new SubRect[9]; + for (int i = 0; i < m_Rects.Length; i++) + { + m_Rects[i].uv = new float[4]; + } + + Init(texture, x, y, w, h, inMargin, drawMarginScale); + } + + void DrawRect(Renderer.Base render, int i, int x, int y, int w, int h) + { + render.DrawTexturedRect(m_Texture, + new Rectangle(x, y, w, h), + m_Rects[i].uv[0], m_Rects[i].uv[1], m_Rects[i].uv[2], m_Rects[i].uv[3]); + } + + void SetRect(int num, float x, float y, float w, float h) + { + float texw = m_Texture.Width; + float texh = m_Texture.Height; + + //x -= 1.0f; + //y -= 1.0f; + + m_Rects[num].uv[0] = x / texw; + m_Rects[num].uv[1] = y / texh; + + m_Rects[num].uv[2] = (x + w) / texw; + m_Rects[num].uv[3] = (y + h) / texh; + + // rects[num].uv[0] += 1.0f / m_Texture->width; + // rects[num].uv[1] += 1.0f / m_Texture->width; + } + + private void Init(Texture texture, float x, float y, float w, float h, Margin inMargin, float drawMarginScale = 1.0f) + { + m_Texture = texture; + + m_Margin = inMargin; + + SetRect(0, x, y, m_Margin.Left, m_Margin.Top); + SetRect(1, x + m_Margin.Left, y, w - m_Margin.Left - m_Margin.Right, m_Margin.Top); + SetRect(2, (x + w) - m_Margin.Right, y, m_Margin.Right, m_Margin.Top); + + SetRect(3, x, y + m_Margin.Top, m_Margin.Left, h - m_Margin.Top - m_Margin.Bottom); + SetRect(4, x + m_Margin.Left, y + m_Margin.Top, w - m_Margin.Left - m_Margin.Right, + h - m_Margin.Top - m_Margin.Bottom); + SetRect(5, (x + w) - m_Margin.Right, y + m_Margin.Top, m_Margin.Right, h - m_Margin.Top - m_Margin.Bottom - 1); + + SetRect(6, x, (y + h) - m_Margin.Bottom, m_Margin.Left, m_Margin.Bottom); + SetRect(7, x + m_Margin.Left, (y + h) - m_Margin.Bottom, w - m_Margin.Left - m_Margin.Right, m_Margin.Bottom); + SetRect(8, (x + w) - m_Margin.Right, (y + h) - m_Margin.Bottom, m_Margin.Right, m_Margin.Bottom); + + m_Margin.Left = (int)(m_Margin.Left*drawMarginScale); + m_Margin.Right = (int)(m_Margin.Right*drawMarginScale); + m_Margin.Top = (int)(m_Margin.Top*drawMarginScale); + m_Margin.Bottom = (int)(m_Margin.Bottom*drawMarginScale); + + m_Width = w - x; + m_Height = h - y; + } + + // can't have this as default param + public void Draw(Renderer.Base render, Rectangle r) + { + Draw(render, r, Color.White); + } + + public void Draw(Renderer.Base render, Rectangle r, Color col) + { + if (m_Texture == null) + return; + + render.DrawColor = col; + + if (r.Width < m_Width && r.Height < m_Height) + { + render.DrawTexturedRect(m_Texture, r, m_Rects[0].uv[0], m_Rects[0].uv[1], m_Rects[8].uv[2], m_Rects[8].uv[3]); + return; + } + + DrawRect(render, 0, r.X, r.Y, m_Margin.Left, m_Margin.Top); + DrawRect(render, 1, r.X + m_Margin.Left, r.Y, r.Width - m_Margin.Left - m_Margin.Right, m_Margin.Top); + DrawRect(render, 2, (r.X + r.Width) - m_Margin.Right, r.Y, m_Margin.Right, m_Margin.Top); + + DrawRect(render, 3, r.X, r.Y + m_Margin.Top, m_Margin.Left, r.Height - m_Margin.Top - m_Margin.Bottom); + DrawRect(render, 4, r.X + m_Margin.Left, r.Y + m_Margin.Top, r.Width - m_Margin.Left - m_Margin.Right, + r.Height - m_Margin.Top - m_Margin.Bottom); + DrawRect(render, 5, (r.X + r.Width) - m_Margin.Right, r.Y + m_Margin.Top, m_Margin.Right, + r.Height - m_Margin.Top - m_Margin.Bottom); + + DrawRect(render, 6, r.X, (r.Y + r.Height) - m_Margin.Bottom, m_Margin.Left, m_Margin.Bottom); + DrawRect(render, 7, r.X + m_Margin.Left, (r.Y + r.Height) - m_Margin.Bottom, + r.Width - m_Margin.Left - m_Margin.Right, m_Margin.Bottom); + DrawRect(render, 8, (r.X + r.Width) - m_Margin.Right, (r.Y + r.Height) - m_Margin.Bottom, m_Margin.Right, + m_Margin.Bottom); + } + } +} diff --git a/Gwen/Skin/Texturing/Single.cs b/Gwen/Skin/Texturing/Single.cs new file mode 100644 index 0000000..e42fbcf --- /dev/null +++ b/Gwen/Skin/Texturing/Single.cs @@ -0,0 +1,66 @@ +using System; +using System.Drawing; + +namespace Gwen.Skin.Texturing +{ + /// + /// Single textured element. + /// + public struct Single + { + private readonly Texture m_Texture; + private readonly float[] m_uv; + private readonly int m_Width; + private readonly int m_Height; + + public Single(Texture texture, float x, float y, float w, float h ) + { + m_Texture = texture; + + float texw = m_Texture.Width; + float texh = m_Texture.Height; + + m_uv = new float[4]; + m_uv[0] = x / texw; + m_uv[1] = y / texh; + m_uv[2] = (x + w) / texw; + m_uv[3] = (y + h) / texh; + + m_Width = (int) w; + m_Height = (int) h; + } + + // can't have this as default param + public void Draw(Renderer.Base render, Rectangle r) + { + Draw(render, r, Color.White); + } + + public void Draw(Renderer.Base render, Rectangle r, Color col) + { + if (m_Texture == null) + return; + + render.DrawColor = col; + render.DrawTexturedRect(m_Texture, r, m_uv[0], m_uv[1], m_uv[2], m_uv[3]); + } + + public void DrawCenter(Renderer.Base render, Rectangle r) + { + if (m_Texture == null) + return; + + DrawCenter(render, r, Color.White); + } + + public void DrawCenter(Renderer.Base render, Rectangle r, Color col) + { + r.X += (int)((r.Width - m_Width) * 0.5); + r.Y += (int)((r.Height - m_Height) * 0.5); + r.Width = m_Width; + r.Height = m_Height; + + Draw(render, r, col); + } + } +} diff --git a/Gwen/Texture.cs b/Gwen/Texture.cs new file mode 100644 index 0000000..5c2c4a4 --- /dev/null +++ b/Gwen/Texture.cs @@ -0,0 +1,96 @@ +using System; +using System.IO; + +namespace Gwen +{ + /// + /// Represents a texture. + /// + public class Texture : IDisposable + { + /// + /// Texture name. Usually file name, but exact meaning depends on renderer. + /// + public String Name { get; set; } + + /// + /// Renderer data. + /// + public object RendererData { get; set; } + + /// + /// Indicates that the texture failed to load. + /// + public bool Failed { get; set; } + + /// + /// Texture width. + /// + public int Width { get; set; } + + /// + /// Texture height. + /// + public int Height { get; set; } + + private readonly Renderer.Base m_Renderer; + + /// + /// Initializes a new instance of the class. + /// + /// Renderer to use. + public Texture(Renderer.Base renderer) + { + m_Renderer = renderer; + Width = 4; + Height = 4; + Failed = false; + } + + /// + /// Loads the specified texture. + /// + /// Texture name. + public void Load(String name) + { + Name = name; + m_Renderer.LoadTexture(this); + } + + /// + /// Initializes the texture from raw pixel data. + /// + /// Texture width. + /// Texture height. + /// Color array in RGBA format. + public void LoadRaw(int width, int height, byte[] pixelData) + { + Width = width; + Height = height; + m_Renderer.LoadTextureRaw(this, pixelData); + } + + public void LoadStream(Stream data) + { + m_Renderer.LoadTextureStream(this, data); + } + + /// + /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources. + /// + public void Dispose() + { + m_Renderer.FreeTexture(this); + GC.SuppressFinalize(this); + } + +#if DEBUG + ~Texture() + { + throw new InvalidOperationException(String.Format("IDisposable object finalized: {0}", GetType())); + //Debug.Print(String.Format("IDisposable object finalized: {0}", GetType())); + } +#endif + + } +} diff --git a/Gwen/ToolTip.cs b/Gwen/ToolTip.cs new file mode 100644 index 0000000..ac24539 --- /dev/null +++ b/Gwen/ToolTip.cs @@ -0,0 +1,74 @@ +using System.Drawing; +using Gwen.Control; + +namespace Gwen +{ + /// + /// Tooltip handling. + /// + public static class ToolTip + { + private static Base g_ToolTip; + + /// + /// Enables tooltip display for the specified control. + /// + /// Target control. + public static void Enable(Base control) + { + if (null == control.ToolTip) + return; + + g_ToolTip = control; + } + + /// + /// Disables tooltip display for the specified control. + /// + /// Target control. + public static void Disable(Base control) + { + if (g_ToolTip == control) + { + g_ToolTip = null; + } + } + + /// + /// Disables tooltip display for the specified control. + /// + /// Target control. + public static void ControlDeleted(Base control) + { + Disable(control); + } + + /// + /// Renders the currently visible tooltip. + /// + /// + public static void RenderToolTip(Skin.Base skin) + { + if (null == g_ToolTip) return; + + Renderer.Base render = skin.Renderer; + + Point oldRenderOffset = render.RenderOffset; + Point mousePos = Input.InputHandler.MousePosition; + Rectangle bounds = g_ToolTip.ToolTip.Bounds; + + Rectangle offset = Util.FloatRect(mousePos.X - bounds.Width*0.5f, mousePos.Y - bounds.Height - 10, + bounds.Width, bounds.Height); + offset = Util.ClampRectToRect(offset, g_ToolTip.GetCanvas().Bounds); + + //Calculate offset on screen bounds + render.AddRenderOffset(offset); + render.EndClip(); + + skin.DrawToolTip(g_ToolTip.ToolTip); + g_ToolTip.ToolTip.DoRender(skin); + + render.RenderOffset = oldRenderOffset; + } + } +} diff --git a/Gwen/Util.cs b/Gwen/Util.cs new file mode 100644 index 0000000..aea7ada --- /dev/null +++ b/Gwen/Util.cs @@ -0,0 +1,146 @@ +using System; +using System.Drawing; +using System.Text.RegularExpressions; + +namespace Gwen +{ + /// + /// Misc utility functions. + /// + public static class Util + { + public static int Round(float x) + { + return (int)Math.Round(x, MidpointRounding.AwayFromZero); + } + /* + public static int Trunc(float x) + { + return (int)Math.Truncate(x); + } + */ + public static int Ceil(float x) + { + return (int)Math.Ceiling(x); + } + + public static Rectangle FloatRect(float x, float y, float w, float h) + { + return new Rectangle((int)x, (int)y, (int)w, (int)h); + } + + public static int Clamp(int x, int min, int max) + { + if (x < min) + return min; + if (x > max) + return max; + return x; + } + + public static float Clamp(float x, float min, float max) + { + if (x < min) + return min; + if (x > max) + return max; + return x; + } + + public static Rectangle ClampRectToRect(Rectangle inside, Rectangle outside, bool clampSize = false) + { + if (inside.X < outside.X) + inside.X = outside.X; + + if (inside.Y < outside.Y) + inside.Y = outside.Y; + + if (inside.Right > outside.Right) + { + if (clampSize) + inside.Width = outside.Width; + else + inside.X = outside.Right - inside.Width; + } + if (inside.Bottom > outside.Bottom) + { + if (clampSize) + inside.Height = outside.Height; + else + inside.Y = outside.Bottom - inside.Height; + } + + return inside; + } + + // from http://stackoverflow.com/questions/359612/how-to-change-rgb-color-to-hsv + public static HSV ToHSV(this Color color) + { + HSV hsv = new HSV(); + int max = Math.Max(color.R, Math.Max(color.G, color.B)); + int min = Math.Min(color.R, Math.Min(color.G, color.B)); + + hsv.h = color.GetHue(); + hsv.s = (max == 0) ? 0 : 1f - (1f * min / max); + hsv.v = max / 255f; + + return hsv; + } + + public static Color HSVToColor(float h, float s, float v) + { + int hi = Convert.ToInt32(Math.Floor(h / 60)) % 6; + float f = h / 60 - (float)Math.Floor(h / 60); + + v = v * 255; + int va = Convert.ToInt32(v); + int p = Convert.ToInt32(v * (1 - s)); + int q = Convert.ToInt32(v * (1 - f * s)); + int t = Convert.ToInt32(v * (1 - (1 - f) * s)); + + if (hi == 0) + return Color.FromArgb(255, va, t, p); + if (hi == 1) + return Color.FromArgb(255, q, va, p); + if (hi == 2) + return Color.FromArgb(255, p, va, t); + if (hi == 3) + return Color.FromArgb(255, p, q, va); + if (hi == 4) + return Color.FromArgb(255, t, p, va); + return Color.FromArgb(255, va, p, q); + } + + // can't create extension operators + public static Color Subtract(this Color color, Color other) + { + return Color.FromArgb(color.A - other.A, color.R - other.R, color.G - other.G, color.B - other.B); + } + + public static Color Add(this Color color, Color other) + { + return Color.FromArgb(color.A + other.A, color.R + other.R, color.G + other.G, color.B + other.B); + } + + public static Color Multiply(this Color color, float amount) + { + return Color.FromArgb(color.A, (int)(color.R * amount), (int)(color.G * amount), (int)(color.B * amount)); + } + + public static Rectangle Add(this Rectangle r, Rectangle other) + { + return new Rectangle(r.X + other.X, r.Y + other.Y, r.Width + other.Width, r.Height + other.Height); + } + + /// + /// Splits a string but keeps the separators intact (at the end of split parts). + /// + /// String to split. + /// Separator characters. + /// Split strings. + public static String[] SplitAndKeep(String text, String separators) + { + return Regex.Split(text, @"(?=[" + separators + "])"); + } + } +} diff --git a/Gwen/readme.txt b/Gwen/readme.txt new file mode 100644 index 0000000..17055ed --- /dev/null +++ b/Gwen/readme.txt @@ -0,0 +1 @@ +Too many changes from original GWEN to list here :P diff --git a/GwenCS.sln b/GwenCS.sln new file mode 100644 index 0000000..525606f --- /dev/null +++ b/GwenCS.sln @@ -0,0 +1,20 @@ + +Microsoft Visual Studio Solution File, Format Version 11.00 +# Visual Studio 2010 +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Gwen", "Gwen\Gwen.csproj", "{D3F5E624-3AF2-418F-A180-8A4172928065}" +EndProject +Global + GlobalSection(SolutionConfigurationPlatforms) = preSolution + Debug|Any CPU = Debug|Any CPU + Release|Any CPU = Release|Any CPU + EndGlobalSection + GlobalSection(ProjectConfigurationPlatforms) = postSolution + {D3F5E624-3AF2-418F-A180-8A4172928065}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {D3F5E624-3AF2-418F-A180-8A4172928065}.Debug|Any CPU.Build.0 = Debug|Any CPU + {D3F5E624-3AF2-418F-A180-8A4172928065}.Release|Any CPU.ActiveCfg = Release|Any CPU + {D3F5E624-3AF2-418F-A180-8A4172928065}.Release|Any CPU.Build.0 = Release|Any CPU + EndGlobalSection + GlobalSection(MonoDevelopProperties) = preSolution + StartupItem = Gwen\Gwen.csproj + EndGlobalSection +EndGlobal