// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
using System.Collections.Generic;
namespace FlaxEngine.GUI
{
///
/// Base class for all GUI controls
///
public partial class Control : IComparable, IDrawable
{
private struct AnchorPresetData
{
public AnchorPresets Preset;
public Vector2 Min;
public Vector2 Max;
public AnchorPresetData(AnchorPresets preset, Vector2 min, Vector2 max)
{
Preset = preset;
Min = min;
Max = max;
}
}
private static readonly AnchorPresetData[] AnchorPresetsData =
{
new AnchorPresetData(AnchorPresets.TopLeft, new Vector2(0, 0), new Vector2(0, 0)),
new AnchorPresetData(AnchorPresets.TopCenter, new Vector2(0.5f, 0), new Vector2(0.5f, 0)),
new AnchorPresetData(AnchorPresets.TopRight, new Vector2(1, 0), new Vector2(1, 0)),
new AnchorPresetData(AnchorPresets.MiddleLeft, new Vector2(0, 0.5f), new Vector2(0, 0.5f)),
new AnchorPresetData(AnchorPresets.MiddleCenter, new Vector2(0.5f, 0.5f), new Vector2(0.5f, 0.5f)),
new AnchorPresetData(AnchorPresets.MiddleRight, new Vector2(1, 0.5f), new Vector2(1, 0.5f)),
new AnchorPresetData(AnchorPresets.BottomLeft, new Vector2(0, 1), new Vector2(0, 1)),
new AnchorPresetData(AnchorPresets.BottomCenter, new Vector2(0.5f, 1), new Vector2(0.5f, 1)),
new AnchorPresetData(AnchorPresets.BottomRight, new Vector2(1, 1), new Vector2(1, 1)),
new AnchorPresetData(AnchorPresets.HorizontalStretchTop, new Vector2(0, 0), new Vector2(1, 0)),
new AnchorPresetData(AnchorPresets.HorizontalStretchMiddle, new Vector2(0, 0.5f), new Vector2(1, 0.5f)),
new AnchorPresetData(AnchorPresets.HorizontalStretchBottom, new Vector2(0, 1), new Vector2(1, 1)),
new AnchorPresetData(AnchorPresets.VerticalStretchLeft, new Vector2(0, 0), new Vector2(0, 1)),
new AnchorPresetData(AnchorPresets.VerticalStretchCenter, new Vector2(0.5f, 0), new Vector2(0.5f, 1)),
new AnchorPresetData(AnchorPresets.VerticalStretchRight, new Vector2(1, 0), new Vector2(1, 1)),
new AnchorPresetData(AnchorPresets.StretchAll, new Vector2(0, 0), new Vector2(1, 1)),
};
private ContainerControl _parent;
private RootControl _root;
private bool _isDisposing, _isFocused, _isNavFocused;
// State
// TODO: convert to flags
private bool _isMouseOver, _isDragOver;
private bool _isVisible = true;
private bool _isEnabled = true;
private bool _autoFocus = true;
private List _touchOvers;
private RootControl.UpdateDelegate _tooltipUpdate;
// Transform
private Rectangle _bounds;
private Margin _offsets = new Margin(0.0f, 100.0f, 0.0f, 30.0f);
private Vector2 _anchorMin;
private Vector2 _anchorMax;
private Vector2 _scale = new Vector2(1.0f);
private Vector2 _pivot = new Vector2(0.5f);
private Vector2 _shear;
private float _rotation;
internal Matrix3x3 _cachedTransform;
internal Matrix3x3 _cachedTransformInv;
// Style
private Color _backgroundColor = Color.Transparent;
// Tooltip
private string _tooltipText;
private Tooltip _tooltip;
///
/// Action is invoked, when location is changed
///
public event Action LocationChanged;
///
/// Action is invoked, when size is changed
///
public event Action SizeChanged;
///
/// Action is invoked, when parent is changed
///
public event Action ParentChanged;
///
/// Action is invoked, when visibility is changed
///
public event Action VisibleChanged;
#region Public Properties
///
/// Parent control (the one above in the tree hierarchy)
///
[HideInEditor, NoSerialize]
public ContainerControl Parent
{
get => _parent;
set
{
if (_parent == value)
return;
Defocus();
Vector2 oldParentSize;
if (_parent != null)
{
oldParentSize = _parent.Size;
_parent.RemoveChildInternal(this);
}
else
{
oldParentSize = Vector2.Zero;
}
_parent = value;
_parent?.AddChildInternal(this);
CacheRootHandle();
OnParentChangedInternal();
// Check if parent size has been changed
if (_parent != null && !_parent.Size.Equals(ref oldParentSize))
{
OnParentResized();
}
}
}
///
/// Checks if control has parent container control
///
public bool HasParent => _parent != null;
///
/// Gets or sets zero-based index of the control inside the parent container list.
///
[HideInEditor, NoSerialize]
public int IndexInParent
{
get => _parent?.GetChildIndex(this) ?? -1;
set => _parent?.ChangeChildIndex(this, value);
}
///
/// Gets or sets control background color (transparent color (alpha=0) means no background rendering)
///
[ExpandGroups, EditorDisplay("Style"), EditorOrder(2000), Tooltip("The control background color. Use transparent color (alpha=0) to hide background.")]
public Color BackgroundColor
{
get => _backgroundColor;
set => _backgroundColor = value;
}
///
/// Gets or sets the anchor preset used by the control anchors (based on and ).
///
/// To change anchor preset with current control bounds preservation use .
[NoSerialize, EditorDisplay("Transform"), HideInEditor, EditorOrder(980), Tooltip("The anchor preset used by the control anchors.")]
public AnchorPresets AnchorPreset
{
get
{
var result = AnchorPresets.Custom;
for (int i = 0; i < AnchorPresetsData.Length; i++)
{
if (Vector2.NearEqual(ref _anchorMin, ref AnchorPresetsData[i].Min) &&
Vector2.NearEqual(ref _anchorMax, ref AnchorPresetsData[i].Max))
{
result = AnchorPresetsData[i].Preset;
break;
}
}
return result;
}
set => SetAnchorPreset(value, false);
}
///
/// Gets or sets a value indicating whether this control is scrollable (scroll bars affect it).
///
[HideInEditor, NoSerialize]
public bool IsScrollable { get; set; } = true;
///
/// Gets or sets a value indicating whether the control can respond to user interaction
///
[EditorOrder(520), Tooltip("If checked, control will receive input events of the user interaction.")]
public bool Enabled
{
get => _isEnabled;
set
{
if (_isEnabled != value)
{
_isEnabled = value;
// Check if control has been disabled
if (!_isEnabled)
{
Defocus();
// Clear flags
if (_isMouseOver)
OnMouseLeave();
if (_isDragOver)
OnDragLeave();
while (_touchOvers != null && _touchOvers.Count != 0)
OnTouchLeave(_touchOvers[0]);
}
}
}
}
///
/// Gets a value indicating whether the control is enabled in the hierarchy (it's enabled and all it's parents are enabled as well).
///
public bool EnabledInHierarchy
{
get
{
if (!_isEnabled)
return false;
if (_parent != null)
return _parent.EnabledInHierarchy;
return true;
}
}
///
/// Gets or sets a value indicating whether the control is visible
///
[EditorOrder(510), Tooltip("If checked, control will be visible.")]
public bool Visible
{
get => _isVisible;
set
{
if (_isVisible != value)
{
_isVisible = value;
// Check on control hide event
if (!_isVisible)
{
Defocus();
// Clear flags
if (_isMouseOver)
OnMouseLeave();
if (_isDragOver)
OnDragLeave();
while (_touchOvers != null && _touchOvers.Count != 0)
OnTouchLeave(_touchOvers[0]);
}
OnVisibleChanged();
_parent?.PerformLayout();
}
}
}
///
/// Gets a value indicating whether the control is visible in the hierarchy (it's visible and all it's parents are visible as well).
///
public bool VisibleInHierarchy
{
get
{
if (!_isVisible)
return false;
if (_parent != null)
return _parent.VisibleInHierarchy;
return true;
}
}
///
/// Returns true if control is during disposing state (on destroy)
///
public bool IsDisposing => _isDisposing;
///
/// Gets the GUI tree root control which contains that control (or null if not linked to any)
///
public virtual RootControl Root => _root;
///
/// Gets the GUI window root control which contains that control (or null if not linked to any).
///
public virtual WindowRootControl RootWindow => _root?.RootWindow;
///
/// Gets the control DPI scale factor (1 is default). Includes custom DPI scale.
///
public float DpiScale => RootWindow?.Window.DpiScale ?? Platform.DpiScale;
///
/// Gets screen position of the control (upper left corner).
///
public Vector2 ScreenPos
{
get
{
var parentWin = Root;
if (parentWin == null)
throw new InvalidOperationException("Missing parent window.");
var clientPos = PointToWindow(Vector2.Zero);
return parentWin.PointToScreen(clientPos);
}
}
#endregion
///
/// Gets or sets the cursor (per window). Control should restore cursor to the default value eg. when mouse leaves it's area.
///
[HideInEditor, NoSerialize]
public virtual CursorType Cursor
{
get => _parent?.Cursor ?? CursorType.Default;
set
{
if (_parent != null)
_parent.Cursor = value;
}
}
///
/// The custom tag object value linked to the control.
///
[HideInEditor, NoSerialize]
public object Tag;
///
/// Initializes a new instance of the class.
///
public Control()
{
_bounds = new Rectangle(_offsets.Left, _offsets.Top, _offsets.Right, _offsets.Bottom);
UpdateTransform();
}
///
/// Initializes a new instance of the class.
///
/// X coordinate
/// Y coordinate
/// Width
/// Height
public Control(float x, float y, float width, float height)
: this(new Rectangle(x, y, width, height))
{
}
///
/// Initializes a new instance of the class.
///
/// Upper left corner location.
/// Bounds size.
public Control(Vector2 location, Vector2 size)
: this(new Rectangle(location, size))
{
}
///
/// Init
///
/// Window bounds
public Control(Rectangle bounds)
{
_bounds = bounds;
_offsets = new Margin(bounds.X, bounds.Width, bounds.Y, bounds.Height);
UpdateTransform();
}
///
/// Performs control logic update.
///
/// The delta time in seconds (since last update).
public delegate void UpdateDelegate(float deltaTime);
///
/// Delete control (will unlink from the parent and start to dispose)
///
public void Dispose()
{
if (_isDisposing)
return;
// Call event
OnDestroy();
// Unlink
Parent = null;
}
///
/// Perform control update and all its children
///
/// Delta time in seconds
[NoAnimate]
public virtual void Update(float deltaTime)
{
// TODO: move all controls to use UpdateDelegate and remove this generic Update
}
private void OnUpdateTooltip(float deltaTime)
{
Tooltip.OnMouseOverControl(this, deltaTime);
}
///
/// Draw control
///
[NoAnimate]
public virtual void Draw()
{
// Paint Background
if (_backgroundColor.A > 0.0f)
{
Render2D.FillRectangle(new Rectangle(Vector2.Zero, Size), _backgroundColor);
}
}
///
/// Update control layout
///
/// True if perform layout by force even if cached state wants to skip it due to optimization.
[NoAnimate]
public virtual void PerformLayout(bool force = false)
{
}
#region Focus
///
/// If checked, the control can receive automatic focus (eg. on user click or UI navigation).
///
[EditorOrder(512), Tooltip("If checked, the control can receive automatic focus (eg. on user click or UI navigation).")]
public bool AutoFocus
{
get => _autoFocus;
set => _autoFocus = value;
}
///
/// Gets a value indicating whether the control, currently has the input focus
///
public virtual bool ContainsFocus => _isFocused;
///
/// Gets a value indicating whether the control has input focus
///
public bool IsFocused => _isFocused;
///
/// Gets a value indicating whether the control has UI navigation focus.
///
public bool IsNavFocused => _isNavFocused;
///
/// Sets input focus to the control
///
public virtual void Focus()
{
if (!IsFocused)
{
Focus(this);
}
}
///
/// Removes input focus from the control
///
public virtual void Defocus()
{
if (ContainsFocus)
{
Focus(null);
}
}
///
/// When control gets input focus
///
[NoAnimate]
public virtual void OnGotFocus()
{
// Cache flag
_isFocused = true;
_isNavFocused = false;
}
///
/// When control losts input focus
///
[NoAnimate]
public virtual void OnLostFocus()
{
// Clear flag
_isFocused = false;
_isNavFocused = false;
}
///
/// Action fired when control gets 'Contains Focus' state
///
[NoAnimate]
public virtual void OnStartContainsFocus()
{
}
///
/// Action fired when control lost 'Contains Focus' state
///
[NoAnimate]
public virtual void OnEndContainsFocus()
{
}
///
/// Focus that control
///
/// Control to focus
/// True if control got a focus
protected virtual bool Focus(Control c)
{
return _parent != null && _parent.Focus(c);
}
///
/// Starts the mouse tracking. Used by the scrollbars, splitters, etc.
///
/// If set to true will use mouse screen offset.
[NoAnimate]
public void StartMouseCapture(bool useMouseScreenOffset = false)
{
var parent = Root;
parent.StartTrackingMouse(this, useMouseScreenOffset);
}
///
/// Ends the mouse tracking.
///
[NoAnimate]
public void EndMouseCapture()
{
var parent = Root;
parent.EndTrackingMouse();
}
///
/// When mouse goes up/down not over the control but it has user focus so remove that focus from it (used by scroll
/// bars, sliders etc.)
///
[NoAnimate]
public virtual void OnEndMouseCapture()
{
}
#endregion
#region Navigation
///
/// The explicitly specified target navigation control for direction.
///
[HideInEditor, NoSerialize]
public Control NavTargetUp;
///
/// The explicitly specified target navigation control for direction.
///
[HideInEditor, NoSerialize]
public Control NavTargetDown;
///
/// The explicitly specified target navigation control for direction.
///
[HideInEditor, NoSerialize]
public Control NavTargetLeft;
///
/// The explicitly specified target navigation control for direction.
///
[HideInEditor, NoSerialize]
public Control NavTargetRight;
///
/// Gets the next navigation control to focus for the given direction. Returns null for automated direction resolving.
///
/// The navigation direction.
/// The target navigation control or null to use automatic navigation.
[NoAnimate]
public virtual Control GetNavTarget(NavDirection direction)
{
switch (direction)
{
case NavDirection.Up: return NavTargetUp;
case NavDirection.Down: return NavTargetDown;
case NavDirection.Left: return NavTargetLeft;
case NavDirection.Right: return NavTargetRight;
default: return null;
}
}
///
/// Gets the navigation origin location for this control. It's the starting anchor point for searching navigable controls in the nearby area. By default the origin points are located on the control bounds edges.
///
/// The navigation direction.
/// The navigation origin for the automatic navigation.
[NoAnimate]
public virtual Vector2 GetNavOrigin(NavDirection direction)
{
var size = Size;
switch (direction)
{
case NavDirection.Up: return new Vector2(size.X * 0.5f, 0);
case NavDirection.Down: return new Vector2(size.X * 0.5f, size.Y);
case NavDirection.Left: return new Vector2(0, size.Y * 0.5f);
case NavDirection.Right: return new Vector2(size.X, size.Y * 0.5f);
case NavDirection.Next: return Vector2.Zero;
default: return size * 0.5f;
}
}
///
/// Performs the UI navigation for this control.
///
/// The navigation direction.
/// The navigation start location (in the control-space).
/// The control that calls the event.
/// The list with visited controls. Used to skip recursive navigation calls when doing traversal across the UI hierarchy.
/// The target navigation control or null if didn't performed any navigation.
public virtual Control OnNavigate(NavDirection direction, Vector2 location, Control caller, List visited)
{
if (caller == _parent && AutoFocus && Visible)
return this;
return _parent?.OnNavigate(direction, PointToParent(GetNavOrigin(direction)), caller, visited);
}
///
/// Focuses the control by the UI navigation system. Called during navigating around UI with gamepad/keyboard navigation. Focuses the control and sets the flag.
///
public virtual void NavigationFocus()
{
Focus();
if (IsFocused)
{
_isNavFocused = true;
// Ensure to be in a view
var parent = Parent;
while (parent != null)
{
if (parent is Panel panel && ((panel.VScrollBar != null && panel.VScrollBar.Enabled) || (panel.HScrollBar != null && panel.HScrollBar.Enabled)))
{
panel.ScrollViewTo(this);
break;
}
parent = parent.Parent;
}
}
}
///
/// Generic user interaction event for a control used by UI navigation (eg. user submits on the currently focused control).
///
public virtual void OnSubmit()
{
}
#endregion
#region Mouse
///
/// Check if mouse is over that item or its child items
///
public virtual bool IsMouseOver => _isMouseOver;
///
/// When mouse enters control's area
///
/// Mouse location in Control Space
[NoAnimate]
public virtual void OnMouseEnter(Vector2 location)
{
// Set flag
_isMouseOver = true;
// Update tooltip
if (ShowTooltip && OnTestTooltipOverControl(ref location))
{
Tooltip.OnMouseEnterControl(this);
SetUpdate(ref _tooltipUpdate, OnUpdateTooltip);
}
}
///
/// When mouse moves over control's area
///
/// Mouse location in Control Space
[NoAnimate]
public virtual void OnMouseMove(Vector2 location)
{
// Update tooltip
if (ShowTooltip && OnTestTooltipOverControl(ref location))
{
if (_tooltipUpdate == null)
{
Tooltip.OnMouseEnterControl(this);
SetUpdate(ref _tooltipUpdate, OnUpdateTooltip);
}
}
else if (_tooltipUpdate != null)
{
SetUpdate(ref _tooltipUpdate, null);
Tooltip.OnMouseLeaveControl(this);
}
}
///
/// When mouse leaves control's area
///
[NoAnimate]
public virtual void OnMouseLeave()
{
// Clear flag
_isMouseOver = false;
// Update tooltip
if (_tooltipUpdate != null)
{
SetUpdate(ref _tooltipUpdate, null);
Tooltip.OnMouseLeaveControl(this);
}
}
///
/// When mouse wheel moves
///
/// Mouse location in Control Space
/// Mouse wheel move delta. A positive value indicates that the wheel was rotated forward, away from the user; a negative value indicates that the wheel was rotated backward, toward the user. Normalized to [-1;1] range.
/// True if event has been handled
[NoAnimate]
public virtual bool OnMouseWheel(Vector2 location, float delta)
{
return false;
}
///
/// When mouse goes down over control's area
///
/// Mouse location in Control Space
/// Mouse buttons state (flags)
/// True if event has been handled, otherwise false
[NoAnimate]
public virtual bool OnMouseDown(Vector2 location, MouseButton button)
{
return false;
}
///
/// When mouse goes up over control's area
///
/// Mouse location in Control Space
/// Mouse buttons state (flags)
/// True if event has been handled, otherwise false
[NoAnimate]
public virtual bool OnMouseUp(Vector2 location, MouseButton button)
{
return false;
}
///
/// When mouse double clicks over control's area
///
/// Mouse location in Control Space
/// Mouse buttons state (flags)
/// True if event has been handled, otherwise false
[NoAnimate]
public virtual bool OnMouseDoubleClick(Vector2 location, MouseButton button)
{
return false;
}
#endregion
#region Keyboard
///
/// On input character
///
/// Input character
/// True if event has been handled, otherwise false
[NoAnimate]
public virtual bool OnCharInput(char c)
{
return false;
}
///
/// When key goes down
///
/// Key value
/// True if event has been handled, otherwise false
[NoAnimate]
public virtual bool OnKeyDown(KeyboardKeys key)
{
return false;
}
///
/// When key goes up
///
/// Key value
[NoAnimate]
public virtual void OnKeyUp(KeyboardKeys key)
{
}
#endregion
#region Touch
///
/// Check if touch is over that item or its child items
///
public virtual bool IsTouchOver => _touchOvers != null && _touchOvers.Count != 0;
///
/// Determines whether the given touch pointer is over the control.
///
/// The touch pointer identifier. Stable for the whole touch gesture/interaction.
/// True if given touch pointer is over the control, otherwise false.
public virtual bool IsTouchPointerOver(int pointerId)
{
return _touchOvers != null && _touchOvers.Contains(pointerId);
}
///
/// When touch enters control's area
///
/// Touch location in Control Space
/// The touch pointer identifier. Stable for the whole touch gesture/interaction.
[NoAnimate]
public virtual void OnTouchEnter(Vector2 location, int pointerId)
{
if (_touchOvers == null)
_touchOvers = new List();
_touchOvers.Add(pointerId);
}
///
/// When touch enters control's area.
///
/// Touch location in Control Space.
/// The touch pointer identifier. Stable for the whole touch gesture/interaction.
/// True if event has been handled, otherwise false.
[NoAnimate]
public virtual bool OnTouchDown(Vector2 location, int pointerId)
{
return false;
}
///
/// When touch moves over control's area.
///
/// Touch location in Control Space.
/// The touch pointer identifier. Stable for the whole touch gesture/interaction.
[NoAnimate]
public virtual void OnTouchMove(Vector2 location, int pointerId)
{
}
///
/// When touch goes up over control's area.
///
/// Touch location in Control Space
/// The touch pointer identifier. Stable for the whole touch gesture/interaction.
/// True if event has been handled, otherwise false.
[NoAnimate]
public virtual bool OnTouchUp(Vector2 location, int pointerId)
{
return false;
}
///
/// When touch leaves control's area
///
/// The touch pointer identifier. Stable for the whole touch gesture/interaction.
[NoAnimate]
public virtual void OnTouchLeave(int pointerId)
{
_touchOvers.Remove(pointerId);
if (_touchOvers.Count == 0)
OnTouchLeave();
}
///
/// When all touch leaves control's area
///
[NoAnimate]
public virtual void OnTouchLeave()
{
}
#endregion
#region Drag&Drop
///
/// Check if mouse dragging is over that item or its child items.
///
public virtual bool IsDragOver => _isDragOver;
///
/// When mouse dragging enters control's area
///
/// Mouse location in Control Space
/// The data. See and .
/// The drag event result effect.
[NoAnimate]
public virtual DragDropEffect OnDragEnter(ref Vector2 location, DragData data)
{
// Set flag
_isDragOver = true;
return DragDropEffect.None;
}
///
/// When mouse dragging moves over control's area
///
/// Mouse location in Control Space
/// The data. See and .
/// The drag event result effect.
[NoAnimate]
public virtual DragDropEffect OnDragMove(ref Vector2 location, DragData data)
{
return DragDropEffect.None;
}
///
/// When mouse dragging drops on control's area
///
/// Mouse location in Control Space
/// The data. See and .
/// The drag event result effect.
[NoAnimate]
public virtual DragDropEffect OnDragDrop(ref Vector2 location, DragData data)
{
// Clear flag
_isDragOver = false;
return DragDropEffect.None;
}
///
/// When mouse dragging leaves control's area
///
[NoAnimate]
public virtual void OnDragLeave()
{
// Clear flag
_isDragOver = false;
}
///
/// Starts the drag and drop operation.
///
/// The data.
[NoAnimate]
public virtual void DoDragDrop(DragData data)
{
Root.DoDragDrop(data);
}
#endregion
#region Tooltip
///
/// Gets or sets the tooltip text.
///
[HideInEditor, NoSerialize]
public string TooltipText
{
get => _tooltipText;
set => _tooltipText = value;
}
///
/// Gets or sets the custom tooltip control linked. Use null to show default shared tooltip from the current .
///
[HideInEditor, NoSerialize]
public Tooltip CustomTooltip
{
get => _tooltip;
set => _tooltip = value;
}
///
/// Gets the tooltip used by this control (custom or shared one).
///
public Tooltip Tooltip => _tooltip ?? Style.Current.SharedTooltip;
///
/// Gets a value indicating whether show control tooltip (control is in a proper state, tooltip text is valid, etc.). Can be used to implement custom conditions for showing tooltips (eg. based on current mouse location within the control bounds).
///
/// Tooltip can be only visible if mouse is over the control area (see ).
protected virtual bool ShowTooltip => !string.IsNullOrEmpty(_tooltipText);
///
/// Links the tooltip.
///
/// The text.
/// The custom tooltip.
/// This control pointer. Useful for creating controls in code.
[NoAnimate]
public Control LinkTooltip(string text, Tooltip customTooltip = null)
{
_tooltipText = text;
_tooltip = customTooltip;
return this;
}
///
/// Unlinks the tooltip.
///
[NoAnimate]
public void UnlinkTooltip()
{
_tooltipText = null;
_tooltip = null;
}
///
/// Called when tooltip wants to be shown. Allows modifying its appearance.
///
/// The tooltip text to show.
/// The popup start location (in this control local space).
/// The allowed area of mouse movement to show tooltip (in this control local space).
/// True if can show tooltip, otherwise false to skip.
public virtual bool OnShowTooltip(out string text, out Vector2 location, out Rectangle area)
{
text = _tooltipText;
location = Size * new Vector2(0.5f, 1.0f);
area = new Rectangle(Vector2.Zero, Size);
return ShowTooltip;
}
///
/// Called when tooltip gets created and shown for this control. Can be used to customize tooltip UI.
///
/// The tooltip.
public virtual void OnTooltipShown(Tooltip tooltip)
{
}
///
/// Called when tooltip is visible and tests if the given mouse location (in control space) is valid (is over the content).
///
/// The location.
/// True if tooltip can be still visible, otherwise false.
public virtual bool OnTestTooltipOverControl(ref Vector2 location)
{
return ContainsPoint(ref location) && ShowTooltip;
}
#endregion
#region Helper Functions
///
/// Checks if given location point in Parent Space intersects with the control content and calculates local position.
///
/// The location in Parent Space.
/// The location of intersection in Control Space.
/// True if given point in Parent Space intersects with this control content, otherwise false.
public virtual bool IntersectsContent(ref Vector2 locationParent, out Vector2 location)
{
location = PointFromParent(ref locationParent);
return ContainsPoint(ref location);
}
///
/// Checks if control contains given point in local Control Space.
///
/// Point location in Control Space to check
/// True if point is inside control's area, otherwise false.
public virtual bool ContainsPoint(ref Vector2 location)
{
return location.X >= 0 &&
location.Y >= 0 &&
location.X <= _bounds.Size.X &&
location.Y <= _bounds.Size.Y;
}
///
/// Converts point in local control's space into one of the parent control coordinates
///
/// This control parent of any other parent.
/// Input location of the point to convert
/// Converted point location in parent control coordinates
public Vector2 PointToParent(ContainerControl parent, Vector2 location)
{
if (parent == null)
throw new ArgumentNullException();
Control c = this;
while (c != null)
{
location = c.PointToParent(ref location);
c = c.Parent;
if (c == parent)
break;
}
return location;
}
///
/// Converts point in local control's space into parent control coordinates.
///
/// The input location of the point to convert.
/// The converted point location in parent control coordinates.
public Vector2 PointToParent(Vector2 location)
{
return PointToParent(ref location);
}
///
/// Converts point in local control's space into parent control coordinates.
///
/// The input location of the point to convert.
/// The converted point location in parent control coordinates.
public virtual Vector2 PointToParent(ref Vector2 location)
{
Matrix3x3.Transform2D(ref location, ref _cachedTransform, out var result);
return result;
}
///
/// Converts point in parent control coordinates into local control's space.
///
/// The input location of the point to convert.
/// The converted point location in control's space.
public Vector2 PointFromParent(Vector2 locationParent)
{
return PointFromParent(ref locationParent);
}
///
/// Converts point in parent control coordinates into local control's space.
///
/// The input location of the point to convert.
/// The converted point location in control's space.
public virtual Vector2 PointFromParent(ref Vector2 locationParent)
{
Matrix3x3.Transform2D(ref locationParent, ref _cachedTransformInv, out var result);
return result;
}
///
/// Converts point in one of the parent control coordinates into local control's space.
///
/// This control parent of any other parent.
/// Input location of the point to convert
/// The converted point location in control's space.
public Vector2 PointFromParent(ContainerControl parent, Vector2 location)
{
if (parent == null)
throw new ArgumentNullException();
var path = new List();
Control c = this;
while (c != null && c != parent)
{
path.Add(c);
c = c.Parent;
}
for (int i = path.Count - 1; i >= 0; i--)
{
location = path[i].PointFromParent(ref location);
}
return location;
}
///
/// Converts point in local control's space into window coordinates
///
/// Input location of the point to convert
/// Converted point location in window coordinates
public Vector2 PointToWindow(Vector2 location)
{
location = PointToParent(ref location);
if (_parent != null)
{
location = _parent.PointToWindow(location);
}
return location;
}
///
/// Converts point in the window coordinates into control's space
///
/// Input location of the point to convert
/// Converted point location in control's space
public Vector2 PointFromWindow(Vector2 location)
{
if (_parent != null)
{
location = _parent.PointFromWindow(location);
}
return PointFromParent(ref location);
}
///
/// Converts point in the local control's space into screen coordinates
///
/// Input location of the point to convert
/// Converted point location in screen coordinates
public virtual Vector2 PointToScreen(Vector2 location)
{
location = PointToParent(ref location);
if (_parent != null)
{
location = _parent.PointToScreen(location);
}
return location;
}
///
/// Converts point in screen coordinates into the local control's space
///
/// Input location of the point to convert
/// Converted point location in local control's space
public virtual Vector2 PointFromScreen(Vector2 location)
{
if (_parent != null)
{
location = _parent.PointFromScreen(location);
}
return PointFromParent(ref location);
}
#endregion
#region Control Action
///
/// Called when control location gets changed.
///
protected virtual void OnLocationChanged()
{
LocationChanged?.Invoke(this);
}
///
/// Called when control size gets changed.
///
protected virtual void OnSizeChanged()
{
SizeChanged?.Invoke(this);
_parent?.OnChildResized(this);
}
///
/// Sets the scale and updates the transform.
///
/// The scale.
protected virtual void SetScaleInternal(ref Vector2 scale)
{
_scale = scale;
UpdateTransform();
_parent?.OnChildResized(this);
}
///
/// Sets the pivot and updates the transform.
///
/// The pivot.
protected virtual void SetPivotInternal(ref Vector2 pivot)
{
_pivot = pivot;
UpdateTransform();
_parent?.OnChildResized(this);
}
///
/// Sets the shear and updates the transform.
///
/// The shear.
protected virtual void SetShearInternal(ref Vector2 shear)
{
_shear = shear;
UpdateTransform();
_parent?.OnChildResized(this);
}
///
/// Sets the rotation angle and updates the transform.
///
/// The rotation (in degrees).
protected virtual void SetRotationInternal(float rotation)
{
_rotation = rotation;
UpdateTransform();
_parent?.OnChildResized(this);
}
///
/// Called when visible state gets changed.
///
protected virtual void OnVisibleChanged()
{
VisibleChanged?.Invoke(this);
}
///
/// Action fired when parent control gets changed.
///
protected virtual void OnParentChangedInternal()
{
ParentChanged?.Invoke(this);
}
///
/// Caches the root control handle.
///
internal virtual void CacheRootHandle()
{
if (_root != null)
RemoveUpdateCallbacks(_root);
_root = _parent?.Root;
if (_root != null)
AddUpdateCallbacks(_root);
}
///
/// Adds the custom control logic update callbacks to the root.
///
/// The root.
protected virtual void AddUpdateCallbacks(RootControl root)
{
if (_tooltipUpdate != null)
root.UpdateCallbacksToAdd.Add(_tooltipUpdate);
}
///
/// Removes the custom control logic update callbacks from the root.
///
/// The root.
protected virtual void RemoveUpdateCallbacks(RootControl root)
{
if (_tooltipUpdate != null)
root.UpdateCallbacksToRemove.Add(_tooltipUpdate);
}
///
/// Helper utility function to sets the update callback to the root. Does nothing if value has not been modified. Handles if control has no root or parent.
///
/// The cached update callback delegate (field in the custom control implementation).
/// The value to assign.
protected void SetUpdate(ref UpdateDelegate onUpdate, UpdateDelegate value)
{
if (onUpdate == value)
return;
if (_root != null && onUpdate != null)
_root.UpdateCallbacksToRemove.Add(onUpdate);
onUpdate = value;
if (_root != null && onUpdate != null)
_root.UpdateCallbacksToAdd.Add(onUpdate);
}
///
/// Action fired when parent control gets resized (also when control gets non-null parent).
///
public virtual void OnParentResized()
{
if (!_anchorMin.IsZero || !_anchorMax.IsZero)
{
UpdateBounds();
}
}
///
/// Method called when managed instance should be destroyed
///
[NoAnimate]
public virtual void OnDestroy()
{
// Set disposing flag
_isDisposing = true;
Defocus();
UnlinkTooltip();
Tag = null;
}
#endregion
///
public int CompareTo(object obj)
{
if (obj is Control c)
return Compare(c);
return 0;
}
///
/// Compares this control with the other control.
///
/// The other.
/// Comparision result.
public virtual int Compare(Control other)
{
return (int)(Y - other.Y);
}
}
}