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; using Gwen.Extensions; 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)); } } }