Created
February 28, 2017 10:59
-
-
Save craigmjohnston/5f2c83b139eff6192733693ae6fc9d7c to your computer and use it in GitHub Desktop.
Modified Entity.cs from GeonBit.UI with LimitPositionToParentBoundaries added (doesn't work properly)
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#region File Description | |
//----------------------------------------------------------------------------- | |
// Base UI entity. Every widget inherit from this class. | |
// The base entity implement the following key functionality: | |
// 1. Drawing basic stuff like tiled textures with frames etc. | |
// 2. Positioning and calculating destination rect. | |
// 3. Basic events. | |
// 4. Visibility / Disabled / Locked modes. | |
// 5. Managing child entities. | |
// | |
// Author: Ronen Ness. | |
// Since: 2016. | |
//----------------------------------------------------------------------------- | |
#endregion | |
using System.Collections.Generic; | |
using Microsoft.Xna.Framework; | |
using Microsoft.Xna.Framework.Graphics; | |
using GeonBit.UI.DataTypes; | |
namespace GeonBit.UI.Entities | |
{ | |
/// <summary> | |
/// GeonBit.UI.Entities contains all the UI elements you can create and use in your layouts. | |
/// </summary> | |
[System.Runtime.CompilerServices.CompilerGenerated] | |
class NamespaceDoc | |
{ | |
} | |
/// <summary> | |
/// An Anchor is a pre-defined position in parent entity that we use to position a child. | |
/// For eample, we can use anchors to position an entity at the bottom-center point of its parent. | |
/// Note: anchor affect both the position relative to parent and also the offset origin point of the entity. | |
/// </summary> | |
public enum Anchor | |
{ | |
/// <summary>Center of parent element.</summary> | |
Center, | |
/// <summary>Top-Left corner of parent element.</summary> | |
TopLeft, | |
/// <summary>Top-Right corner of parent element.</summary> | |
TopRight, | |
/// <summary>Top-Center of parent element.</summary> | |
TopCenter, | |
/// <summary>Bottom-Left corner of parent element.</summary> | |
BottomLeft, | |
/// <summary>Bottom-Right corner of parent element.</summary> | |
BottomRight, | |
/// <summary>Bottom-Center of parent element.</summary> | |
BottomCenter, | |
/// <summary>Center-Left of parent element.</summary> | |
CenterLeft, | |
/// <summary>Center-Right of parent element.</summary> | |
CenterRight, | |
/// <summary>Position of the older sibling bottom, eg align this entity based on its older sibling. | |
/// Use this property to place entities one after another.</summary> | |
Auto, | |
/// <summary>Position of the older sibling right side, or below it if not enough room in parent. | |
/// In other words, this will try to put together entities on the same row until overflow parent width, in which case will | |
/// go row down. Use this property to place entities one after another in the same row.</summary> | |
AutoInline, | |
/// <summary>Position of the older sibling bottom, eg align this entity based on its older sibling, but center on X axis. | |
/// Use this property to place entities one after another but keep them aligned to center (especially paragraphs).</summary> | |
AutoCenter, | |
}; | |
/// <summary> | |
/// Possible entity states and interactions with user. | |
/// </summary> | |
public enum EntityState | |
{ | |
/// <summary>Default state, eg currently not interacting.</summary> | |
Default = 0, | |
/// <summary>Mouse is hovering over this entity.</summary> | |
MouseHover = 1, | |
/// <summary>Mouse button is pressed down over this entity.</summary> | |
MouseDown = 2, | |
}; | |
/// <summary> | |
/// Basic UI entity. | |
/// All entities inherit from this class and share this API. | |
/// </summary> | |
public class Entity | |
{ | |
// list of child elements | |
private List<Entity> _children = new List<Entity>(); | |
/// <summary> | |
/// A special size used value to use when you want to get the entity default size. | |
/// </summary> | |
public static readonly Vector2 USE_DEFAULT_SIZE = new Vector2(-1, -1); | |
/// <summary>The direct parent of this entity.</summary> | |
protected Entity _parent = null; | |
/// <summary>Index inside parent.</summary> | |
protected int _indexInParent; | |
/// <summary>Is the entity currently interactable.</summary> | |
protected bool _isInteractable = false; | |
/// <summary>Optional identifier you can attach to entities so you can later search and retrieve by.</summary> | |
public string Identifier = ""; | |
/// <summary>If in promiscuous mode, mouse button is pressed *outside* the entity and then released on the entity, click event will be fired. | |
/// If false, in order to fire click event the mouse button must be pressed AND released over this entity (but can travel outside while being | |
/// held down, as long as its released inside). | |
/// Note: Windows default behavior is non promiscuous mode.</summary> | |
public bool PromiscuousClicksMode = false; | |
/// <summary> | |
/// If this set to true, this entity will still react to events if its direct parent is locked. | |
/// This setting is mostly for scrollbars etc, that even if parent is locked should still be scrollable. | |
/// </summary> | |
protected bool DoEventsIfDirectParentIsLocked = false; | |
/// <summary> | |
/// If true, this entity will always inherit its parent state. | |
/// This is useful for stuff like a paragraph that's attached to a button etc. | |
/// NOTE!!! entities that inherit parent state will not trigger any events either. | |
/// </summary> | |
protected bool InheritParentState = false; | |
// optional background object for this entity. | |
// the background will be rendered on the full size of this entity, behind it, and will not respond to events etc. | |
private Entity _background = null; | |
// mark the first update call on this entity. | |
private bool _isFirstUpdate = true; | |
// entity current style properties | |
private StyleSheet _style = new StyleSheet(); | |
/// <summary>Optional data you can attach to this entity and retrieve later (for example when handling events).</summary> | |
public object AttachedData = null; | |
/// <summary> | |
/// If true (default), will use the actual object size for collision detection. If false, will use the size property. | |
/// This is useful for paragraphs, for example, where the actual width is based on text content and can vary and be totally | |
/// different than the size set in the constructor. | |
/// </summary> | |
public bool UseActualSizeForCollision = true; | |
/// <summary>Entity size (in pixels). Value of 0 will take parent's full size.</summary> | |
protected Vector2 _size; | |
/// <summary>Offset, in pixels, from the anchor position.</summary> | |
protected Vector2 _offset; | |
/// <summary>Anchor to position this entity based on (see Anchor enum for more info).</summary> | |
protected Anchor _anchor; | |
/// <summary>Basic default style that all entities share. Note: loaded from UI theme xml file.</summary> | |
public static StyleSheet DefaultStyle = new StyleSheet(); | |
/// <summary>Callback to execute when mouse button is pressed over this entity (called once when button is pressed).</summary> | |
public EventCallback OnMouseDown = null; | |
/// <summary>Callback to execute when mouse button is released over this entity (called once when button is released).</summary> | |
public EventCallback OnMouseReleased = null; | |
/// <summary>Callback to execute every frame while mouse button is pressed over the entity.</summary> | |
public EventCallback WhileMouseDown = null; | |
/// <summary>Callback to execute every frame while mouse is hovering over the entity.</summary> | |
public EventCallback WhileMouseHover = null; | |
/// <summary>Callback to execute when user clicks on this entity (eg release mouse over it).</summary> | |
public EventCallback OnClick = null; | |
/// <summary>Callback to execute when entity value changes (relevant only for entities with value).</summary> | |
public EventCallback OnValueChange = null; | |
/// <summary>Callback to execute when mouse start hovering over this entity (eg enters its region).</summary> | |
public EventCallback OnMouseEnter = null; | |
/// <summary>Callback to execute when mouse stop hovering over this entity (eg leaves its region).</summary> | |
public EventCallback OnMouseLeave = null; | |
/// <summary>Callback to execute when mouse wheel scrolls and this entity is the active entity.</summary> | |
public EventCallback OnMouseWheelScroll = null; | |
/// <summary>Called when entity starts getting dragged (only if draggable).</summary> | |
public EventCallback OnStartDrag = null; | |
/// <summary>Called when entity stop getting dragged (only if draggable).</summary> | |
public EventCallback OnStopDrag = null; | |
/// <summary>Called every frame while the entity is being dragged.</summary> | |
public EventCallback WhileDragging = null; | |
/// <summary>Callback to execute every frame before this entity is rendered.</summary> | |
public EventCallback BeforeDraw = null; | |
/// <summary>Callback to execute every frame after this entity is rendered.</summary> | |
public EventCallback AfterDraw = null; | |
/// <summary>Callback to execute every frame before this entity updates.</summary> | |
public EventCallback BeforeUpdate = null; | |
/// <summary>Callback to execute every frame after this entity updates.</summary> | |
public EventCallback AfterUpdate = null; | |
/// <summary>Callback to execute every time the visibility of this entity changes (also invokes when parent becomes invisible / visible again).</summary> | |
public EventCallback OnVisiblityChange = null; | |
/// <summary>Is mouse currently pointing on this entity.</summary> | |
protected bool _isMouseOver = false; | |
/// <summary>If true, this entity and its children will be drawn in greyscale effect and will not respond to events.</summary> | |
public bool Disabled = false; | |
/// <summary>If true, this entity and its children will not respond to events (but will be drawn normally, unlike when disabled).</summary> | |
public bool Locked = false; | |
/// <summary>Is the entity currently visible.</summary> | |
public bool _visible = true; | |
/// <summary>Is this entity currently disabled?</summary> | |
private bool _isCurrentlyDisabled = false; | |
/// <summary>Current entity state.</summary> | |
protected EntityState _entityState = EntityState.Default; | |
/// <summary>Does this entity or one of its children currently focused?</summary> | |
protected bool IsFocused = false; | |
/// <summary>Currently calculated destination rect (eg the region this entity is drawn on).</summary> | |
protected Rectangle _destRect; | |
/// <summary>Currently calculated internal destination rect (eg the region this entity children are positioned in).</summary> | |
protected Rectangle _destRectInternal; | |
// is this entity draggable? | |
private bool _draggable = false; | |
// do we need to init drag offset from current position? | |
private bool _needToSetDragOffset = false; | |
// current dragging offset. | |
private Vector2 _dragOffset = Vector2.Zero; | |
// true if this entity is currently being dragged. | |
private bool _isBeingDragged = false; | |
/// <summary>Default size this entity will have when no size is provided or when -1 is set for either width or height.</summary> | |
virtual public Vector2 DefaultSize { get { return Vector2.Zero; } } | |
/// <summary>If true, users will not be able to drag this entity outside its parent boundaries.</summary> | |
public bool LimitDraggingToParentBoundaries = true; | |
/// <summary>If true, this entity will always be positioned within its parent's boundaries.</summary> | |
public bool LimitPositionToParentBoundaries = true; | |
/// <summary> | |
/// Create the entity. | |
/// </summary> | |
/// <param name="size">Entity size, in pixels.</param> | |
/// <param name="anchor">Poisition anchor.</param> | |
/// <param name="offset">Offset from anchor position.</param> | |
public Entity(Vector2? size = null, Anchor anchor = Anchor.Auto, Vector2? offset = null) | |
{ | |
// store size, anchor and offset | |
_size = size ?? DefaultSize; | |
_offset = offset ?? Vector2.Zero; | |
_anchor = anchor; | |
// set basic default style | |
UpdateStyle(DefaultStyle); | |
// check default size on specific axises | |
if (_size.X == -1) { _size.X = DefaultSize.X; } | |
if (_size.Y == -1) { _size.Y = DefaultSize.Y; } | |
} | |
/// <summary> | |
/// Call this function when the first update occures. | |
/// </summary> | |
protected virtual void DoOnFirstUpdate() | |
{ | |
// call the spawn event | |
UserInterface.OnEntitySpawn?.Invoke(this); | |
} | |
/// <summary> | |
/// Return stylesheet property for a given state. | |
/// </summary> | |
/// <param name="property">Property identifier.</param> | |
/// <param name="state">State to get property for (if undefined will fallback to default state).</param> | |
/// <param name="fallbackToDefault">If true and property not found for given state, will fallback to default state.</param> | |
/// <returns>Style property value for given state or default, or null if undefined.</returns> | |
public StyleProperty GetStyleProperty(string property, EntityState state = EntityState.Default, bool fallbackToDefault = true) | |
{ | |
return _style.GetStyleProperty(property, state, fallbackToDefault); | |
} | |
/// <summary> | |
/// Set a stylesheet property. | |
/// </summary> | |
/// <param name="property">Property identifier.</param> | |
/// <param name="value">Property value.</param> | |
/// <param name="state">State to set property for.</param> | |
public void SetStyleProperty(string property, StyleProperty value, EntityState state = EntityState.Default) | |
{ | |
_style.SetStyleProperty(property, value, state); | |
} | |
/// <summary> | |
/// Return stylesheet property for current entity state (or default if undefined for state). | |
/// </summary> | |
/// <param name="property">Property identifier.</param> | |
/// <returns>Stylesheet property value for current entity state, or default if not defined.</returns> | |
public StyleProperty GetActiveStyle(string property) | |
{ | |
return GetStyleProperty(property, _entityState); | |
} | |
/// <summary> | |
/// Update the entire stylesheet from a different stylesheet. | |
/// </summary> | |
/// <param name="updates">Stylesheet to update from.</param> | |
public void UpdateStyle(StyleSheet updates) | |
{ | |
_style.UpdateFrom(updates); | |
} | |
/// <summary>Get extra space after with current UI scale applied. </summary> | |
protected Vector2 _scaledSpaceAfter { get { return SpaceAfter * UserInterface.GlobalScale; } } | |
/// <summary>Get extra space before with current UI scale applied. </summary> | |
protected Vector2 _scaledSpaceBefore { get { return SpaceBefore * UserInterface.GlobalScale; } } | |
/// <summary>Get size with current UI scale applied. </summary> | |
protected Vector2 _scaledSize { get { return _size * UserInterface.GlobalScale; } } | |
/// <summary>Get offset with current UI scale applied. </summary> | |
protected Vector2 _scaledOffset { get { return _offset * UserInterface.GlobalScale; } } | |
/// <summary>Get offset with current UI scale applied. </summary> | |
protected Vector2 _scaledPadding { get { return Padding * UserInterface.GlobalScale; } } | |
/// <summary> | |
/// Set / get visibility. | |
/// </summary> | |
public bool Visible | |
{ | |
get { return _visible; } | |
set { _visible = value; DoOnVisibilityChange(); } | |
} | |
/// <summary> | |
/// Return entity priority in drawing order and event handling. | |
/// </summary> | |
public virtual int Priority | |
{ | |
get { return _indexInParent; } | |
} | |
/// <summary> | |
/// Is the entity draggable (eg can a user grab it and drag it around). | |
/// </summary> | |
public bool Draggable | |
{ | |
get { return _draggable; } | |
set { _needToSetDragOffset = _draggable != value; _draggable = value; } | |
} | |
/// <summary> | |
/// Optional background entity that will not respond to events and will always be rendered right behind this entity. | |
/// </summary> | |
public Entity Background | |
{ | |
get { return _background; } | |
set { | |
if (value != null && value._parent != null) { throw new System.Exception("Cannot set background entity that have a parent!"); } | |
_background = value; | |
} | |
} | |
/// <summary> | |
/// Current entity state (default / mouse hover / mouse down..). | |
/// </summary> | |
public EntityState State | |
{ | |
get { return _entityState; } | |
set { _entityState = value; } | |
} | |
/// <summary> | |
/// Return all children of this entity. | |
/// </summary> | |
/// <returns>List with all children in entity.</returns> | |
public List<Entity> GetChildren() | |
{ | |
return _children; | |
} | |
/// <summary> | |
/// Find and return first occurance of a child entity with a given identifier and specific type. | |
/// </summary> | |
/// <typeparam name="T">Entity type to get.</typeparam> | |
/// <param name="identifier">Identifier to find.</param> | |
/// <param name="recursive">If true, will search recursively in children of children. If false, will search only in direct children.</param> | |
/// <returns>First found entity with given identifier and type, or null if nothing found.</returns> | |
public T Find<T> (string identifier, bool recursive = false) where T : Entity | |
{ | |
// iterate children | |
foreach (Entity child in _children) | |
{ | |
// check if identifier and type matches - if so, return it | |
if (child.Identifier == identifier && child.GetType() == typeof(T)) | |
{ | |
return (T)child; | |
} | |
// if recursive, search in child | |
if (recursive) | |
{ | |
// search in child | |
T ret = child.Find<T>(identifier, recursive); | |
// if found return it | |
if (ret != null) | |
{ | |
return ret; | |
} | |
} | |
} | |
// not found? | |
return null; | |
} | |
/// <summary> | |
/// Find and return first occurance of a child entity with a given identifier. | |
/// </summary> | |
/// <param name="identifier">Identifier to find.</param> | |
/// <param name="recursive">If true, will search recursively in children of children. If false, will search only in direct children.</param> | |
/// <returns>First found entity with given identifier, or null if nothing found.</returns> | |
public Entity Find(string identifier, bool recursive = false) | |
{ | |
return Find<Entity>(identifier, recursive); | |
} | |
/// <summary> | |
/// Iterate over children and call 'callback' for every direct child of this entity. | |
/// </summary> | |
/// <param name="callback">Callback function to call with every child of this entity.</param> | |
public void IterateChildren(EventCallback callback) | |
{ | |
foreach (Entity child in _children) | |
{ | |
callback(child); | |
} | |
} | |
/// <summary> | |
/// Entity current size property. | |
/// </summary> | |
public Vector2 Size | |
{ | |
get { return _size; } | |
set { _size = value; } | |
} | |
/// <summary> | |
/// Extra space (in pixels) to reserve *after* this entity when using Auto Anchors. | |
/// </summary> | |
public Vector2 SpaceAfter | |
{ | |
set { SetStyleProperty("SpaceAfter", new StyleProperty(value)); } | |
get { return GetActiveStyle("SpaceAfter").asVector; } | |
} | |
/// <summary> | |
/// Extra space (in pixels) to reserve *before* this entity when using Auto Anchors. | |
/// </summary> | |
public Vector2 SpaceBefore | |
{ | |
set { SetStyleProperty("SpaceBefore", new StyleProperty(value)); } | |
get { return GetActiveStyle("SpaceBefore").asVector; } | |
} | |
/// <summary> | |
/// Entity fill color - this is just a sugarcoat to access the default fill color style property. | |
/// </summary> | |
public Color FillColor | |
{ | |
set { SetStyleProperty("FillColor", new StyleProperty(value)); } | |
get { return GetActiveStyle("FillColor").asColor; } | |
} | |
/// <summary> | |
/// Entity padding - this is just a sugarcoat to access the default padding style property. | |
/// </summary> | |
public Vector2 Padding | |
{ | |
set { SetStyleProperty("Padding", new StyleProperty(value)); } | |
get { return GetActiveStyle("Padding").asVector; } | |
} | |
/// <summary> | |
/// Entity shadow color - this is just a sugarcoat to access the default shadow color style property. | |
/// </summary> | |
public Color ShadowColor | |
{ | |
set { SetStyleProperty("ShadowColor", new StyleProperty(value)); } | |
get { return GetActiveStyle("ShadowColor").asColor; } | |
} | |
/// <summary> | |
/// Entity shadow scale - this is just a sugarcoat to access the default shadow scale style property. | |
/// </summary> | |
public float ShadowScale | |
{ | |
set { SetStyleProperty("ShadowScale", new StyleProperty(value)); } | |
get { return GetActiveStyle("ShadowScale").asFloat; } | |
} | |
/// <summary> | |
/// Entity shadow offset - this is just a sugarcoat to access the default shadow offset style property. | |
/// </summary> | |
public Vector2 ShadowOffset | |
{ | |
set { SetStyleProperty("ShadowOffset", new StyleProperty(value)); } | |
get { return GetActiveStyle("ShadowOffset").asVector; } | |
} | |
/// <summary> | |
/// Entity scale - this is just a sugarcoat to access the default scale style property. | |
/// </summary> | |
public float Scale | |
{ | |
set { SetStyleProperty("Scale", new StyleProperty(value)); } | |
get { return GetActiveStyle("Scale").asFloat; } | |
} | |
/// <summary> | |
/// Entity outline color - this is just a sugarcoat to access the default outline color style property. | |
/// </summary> | |
public Color OutlineColor | |
{ | |
set { SetStyleProperty("OutlineColor", new StyleProperty(value)); } | |
get { return GetActiveStyle("OutlineColor").asColor; } | |
} | |
/// <summary> | |
/// Entity outline width - this is just a sugarcoat to access the default outline color style property. | |
/// </summary> | |
public int OutlineWidth | |
{ | |
set { SetStyleProperty("OutlineWidth", new StyleProperty(value)); } | |
get { return GetActiveStyle("OutlineWidth").asInt; } | |
} | |
/// <summary> | |
/// Return if this entity is currently disabled, due to self or one of the parents / grandparents being disabled. | |
/// </summary> | |
/// <returns>True if entity is disabled.</returns> | |
public bool IsDisabled() | |
{ | |
// iterate over parents until root, starting with self. | |
// if any entity along the way is disabled we return true. | |
Entity parent = this; | |
while (parent != null) | |
{ | |
if (parent.Disabled) { return true; } | |
parent = parent._parent; | |
} | |
// not disabled | |
return false; | |
} | |
/// <summary> | |
/// Check if this entity is a descendant of another entity. | |
/// This goes up all the way to root. | |
/// </summary> | |
/// <param name="other">Entity to check if this entity is descendant of.</param> | |
/// <returns>True if this entity is descendant of the other entity.</returns> | |
public bool IsDeepChildOf(Entity other) | |
{ | |
// iterate over parents until root, starting with self. | |
// if any entity along the way is child of 'other', we return true. | |
Entity parent = this; | |
while (parent != null) | |
{ | |
if (parent._parent == other) { return true; } | |
parent = parent._parent; | |
} | |
// not child of | |
return false; | |
} | |
/// <summary> | |
/// Return if this entity is currently locked, due to self or one of the parents / grandparents being locked. | |
/// </summary> | |
/// <returns>True if entity is disabled.</returns> | |
public bool IsLocked() | |
{ | |
// iterate over parents until root, starting with self. | |
// if any entity along the way is locked we return true. | |
Entity parent = this; | |
while (parent != null) | |
{ | |
if (parent.Locked) | |
{ | |
// special case - if should do events even when parent is locked and direct parent, skip | |
if (DoEventsIfDirectParentIsLocked) | |
{ | |
if (parent == _parent) { | |
parent = parent._parent; | |
continue; | |
} | |
} | |
// if parent locked return true | |
return true; | |
} | |
// advance to next parent | |
parent = parent._parent; | |
} | |
// not disabled | |
return false; | |
} | |
/// <summary> | |
/// Return if this entity is currently visible, eg this and all its parents and grandparents are visible. | |
/// </summary> | |
/// <returns>True if entity is really visible.</returns> | |
public bool IsVisible() | |
{ | |
// iterate over parents until root, starting with self. | |
// if any entity along the way is not visible we return false. | |
Entity parent = this; | |
while (parent != null) | |
{ | |
if (!parent.Visible) { return false; } | |
parent = parent._parent; | |
} | |
// visible! | |
return true; | |
} | |
/// <summary> | |
/// Set the position and anchor of this entity. | |
/// </summary> | |
/// <param name="anchor">New anchor to set.</param> | |
/// <param name="offset">Offset from new anchor position.</param> | |
public void SetPosition(Anchor anchor, Vector2 offset) | |
{ | |
_anchor = anchor; | |
_offset = offset; | |
} | |
/// <summary> | |
/// Set the anchor of this entity. | |
/// </summary> | |
/// <param name="anchor">New anchor to set.</param> | |
public void SetAnchor(Anchor anchor) | |
{ | |
_anchor = anchor; | |
} | |
/// <summary> | |
/// Set the offset of this entity. | |
/// </summary> | |
/// <param name="offset">New offset to set.</param> | |
public void SetOffset(Vector2 offset) | |
{ | |
_offset = offset; | |
} | |
/// <summary> | |
/// Return children in a sorted list by priority. | |
/// </summary> | |
/// <returns>List of children sorted by priority.</returns> | |
protected List<Entity> GetSortedChildren() | |
{ | |
// create list to sort and return | |
List<Entity> ret = new List<Entity>(_children); | |
// get children in a sorted list | |
ret.Sort((x, y) => | |
x.Priority.CompareTo(y.Priority)); | |
// return the sorted list | |
return ret; | |
} | |
/// <summary> | |
/// Draw this entity and its children. | |
/// </summary> | |
/// <param name="spriteBatch">SpriteBatch to use for drawing.</param> | |
virtual public void Draw(SpriteBatch spriteBatch) | |
{ | |
// if not visible skip | |
if (!Visible) | |
{ | |
return; | |
} | |
// update if disabled | |
_isCurrentlyDisabled = IsDisabled(); | |
// draw background | |
if (Background != null) | |
{ | |
_background._parent = this; | |
_background._indexInParent = 0; | |
_background.Draw(spriteBatch); | |
_background._parent = null; | |
} | |
// do before draw event | |
OnBeforeDraw(spriteBatch); | |
// calc desination rect | |
_destRect = CalcDestRect(); | |
_destRectInternal = CalcInternalRect(); | |
// draw shadow | |
DrawEntityShadow(spriteBatch); | |
// draw entity outline | |
DrawEntityOutline(spriteBatch); | |
// draw the entity itself | |
UserInterface.DrawUtils.StartDraw(spriteBatch, _isCurrentlyDisabled); | |
DrawEntity(spriteBatch); | |
UserInterface.DrawUtils.EndDraw(spriteBatch); | |
// get sorted children list | |
List<Entity> childrenSorted = GetSortedChildren(); | |
// draw all children | |
foreach (Entity child in childrenSorted) | |
{ | |
child.Draw(spriteBatch); | |
} | |
// do after draw event | |
OnAfterDraw(spriteBatch); | |
} | |
/// <summary> | |
/// Draw entity shadow (if defined shadow). | |
/// </summary> | |
/// <param name="spriteBatch">Sprite batch to draw on.</param> | |
virtual protected void DrawEntityShadow(SpriteBatch spriteBatch) | |
{ | |
// get current shadow color and if transparent skip | |
Color shadowColor = ShadowColor; | |
if (shadowColor.A == 0) { return; } | |
// get shadow scale | |
float shadowScale = ShadowScale; | |
// update position to draw shadow | |
_destRect.X += (int)ShadowOffset.X; | |
_destRect.Y += (int)ShadowOffset.Y; | |
// store previous state and colors | |
Color oldFill = FillColor; | |
Color oldOutline = OutlineColor; | |
float oldScale = Scale; | |
int oldOutlineWidth = OutlineWidth; | |
EntityState oldState = _entityState; | |
// set default colors and state for shadow pass | |
FillColor = shadowColor; | |
OutlineColor = Color.Transparent; | |
OutlineWidth = 0; | |
Scale = shadowScale; | |
_entityState = EntityState.Default; | |
// if disabled, turn color into greyscale | |
if (_isCurrentlyDisabled) | |
{ | |
FillColor = new Color(Color.White * (((shadowColor.R + shadowColor.G + shadowColor.B) / 3f) / 255f), shadowColor.A); | |
} | |
// draw with shadow effect | |
UserInterface.DrawUtils.StartDrawSilhouette(spriteBatch); | |
DrawEntity(spriteBatch); | |
UserInterface.DrawUtils.EndDraw(spriteBatch); | |
// return position and colors back to what they were | |
_destRect.X -= (int)ShadowOffset.X; | |
_destRect.Y -= (int)ShadowOffset.Y; | |
FillColor = oldFill; | |
Scale = oldScale; | |
OutlineColor = oldOutline; | |
OutlineWidth = oldOutlineWidth; | |
_entityState = oldState; | |
} | |
/// <summary> | |
/// Draw entity outline. | |
/// </summary> | |
/// <param name="spriteBatch">Sprite batch to draw on.</param> | |
virtual protected void DrawEntityOutline(SpriteBatch spriteBatch) | |
{ | |
// get outline width and if 0 return | |
int outlineWidth = OutlineWidth; | |
if (OutlineWidth == 0) { return; } | |
// get outline color | |
Color outlineColor = OutlineColor; | |
// if disabled, turn outline to grey | |
if (_isCurrentlyDisabled) | |
{ | |
outlineColor = new Color(Color.White * (((outlineColor.R + outlineColor.G + outlineColor.B) / 3f) / 255f), outlineColor.A); | |
} | |
// store previous fill color | |
Color oldFill = FillColor; | |
// store original destination rect | |
Rectangle originalDest = _destRect; | |
Rectangle originalIntDest = _destRectInternal; | |
// store entity previous state | |
EntityState oldState = _entityState; | |
// set fill color | |
SetStyleProperty("FillColor", new StyleProperty(outlineColor), oldState); | |
// draw the entity outline | |
UserInterface.DrawUtils.StartDrawSilhouette(spriteBatch); | |
_destRect.Location = originalDest.Location + new Point(-outlineWidth, 0); | |
DrawEntity(spriteBatch); | |
_destRect.Location = originalDest.Location + new Point(0, -outlineWidth); | |
DrawEntity(spriteBatch); | |
_destRect.Location = originalDest.Location + new Point(outlineWidth, 0); | |
DrawEntity(spriteBatch); | |
_destRect.Location = originalDest.Location + new Point(0, outlineWidth); | |
DrawEntity(spriteBatch); | |
UserInterface.DrawUtils.EndDraw(spriteBatch); | |
// turn back to previous fill color | |
SetStyleProperty("FillColor", new StyleProperty(oldFill), oldState); | |
// return to the original destination rect | |
_destRect = originalDest; | |
_destRectInternal = originalIntDest; | |
} | |
/// <summary> | |
/// The internal function to draw the entity itself. | |
/// Implemented by inheriting entity types. | |
/// </summary> | |
/// <param name="spriteBatch">SpriteBatch to draw on.</param> | |
virtual protected void DrawEntity(SpriteBatch spriteBatch) | |
{ | |
} | |
/// <summary> | |
/// Called every frame after drawing is done. | |
/// </summary> | |
/// <param name="spriteBatch">SpriteBatch to draw on.</param> | |
virtual protected void OnAfterDraw(SpriteBatch spriteBatch) | |
{ | |
AfterDraw?.Invoke(this); | |
UserInterface.AfterDraw?.Invoke(this); | |
} | |
/// <summary> | |
/// Called every frame before drawing is done. | |
/// </summary> | |
/// <param name="spriteBatch">SpriteBatch to draw on.</param> | |
virtual protected void OnBeforeDraw(SpriteBatch spriteBatch) | |
{ | |
BeforeDraw?.Invoke(this); | |
UserInterface.BeforeDraw?.Invoke(this); | |
} | |
/// <summary> | |
/// Get the direct parent of this entity. | |
/// </summary> | |
public Entity Parent | |
{ | |
get { return _parent; } | |
} | |
/// <summary> | |
/// Add a child entity. | |
/// </summary> | |
/// <param name="child">Entity to add as child.</param> | |
/// <param name="inheritParentState">If true, this entity will inherit the parent's state (set InheritParentState property).</param> | |
public void AddChild(Entity child, bool inheritParentState = false) | |
{ | |
// make sure don't already have a parent | |
if (child._parent != null) | |
{ | |
throw new System.Exception("Child element to add already got a parent!"); | |
} | |
// set inherit parent mode | |
child.InheritParentState = inheritParentState; | |
// set parent and add | |
child._parent = this; | |
child._indexInParent = _children.Count; | |
_children.Add(child); | |
} | |
/// <summary> | |
/// Bring this entity to be on front (inside its parent). | |
/// </summary> | |
public void BringToFront() | |
{ | |
Entity parent = _parent; | |
parent.RemoveChild(this); | |
parent.AddChild(this); | |
} | |
/// <summary> | |
/// Remove child entity. | |
/// </summary> | |
/// <param name="child">Entity to remove.</param> | |
public void RemoveChild(Entity child) | |
{ | |
// make sure don't already have a parent | |
if (child._parent != this) | |
{ | |
throw new System.Exception("Child element to remove does not belong to this entity!"); | |
} | |
// set parent to null and remove | |
child._parent = null; | |
child._indexInParent = -1; | |
_children.Remove(child); | |
// reset index for all children | |
int index = 0; | |
foreach (Entity itrChild in _children) | |
{ | |
itrChild._indexInParent = index++; | |
} | |
} | |
/// <summary> | |
/// Remove all children entities. | |
/// </summary> | |
public void ClearChildren() | |
{ | |
foreach (Entity child in _children) | |
{ | |
child._parent = null; | |
child._indexInParent = -1; | |
} | |
_children.Clear(); | |
} | |
/// <summary> | |
/// Calculate and return the internal destination rectangle (note: this relay on the dest rect having a valid value first). | |
/// </summary> | |
/// <returns>Internal destination rectangle.</returns> | |
public Rectangle CalcInternalRect() | |
{ | |
// calculate the internal destination rect, eg after padding | |
Vector2 padding = _scaledPadding; | |
_destRectInternal = new Rectangle(_destRect.Location, _destRect.Size); | |
_destRectInternal.X += (int)padding.X; | |
_destRectInternal.Y += (int)padding.Y; | |
_destRectInternal.Width -= (int)padding.X * 2; | |
_destRectInternal.Height -= (int)padding.Y * 2; | |
return _destRectInternal; | |
} | |
/// <summary> | |
/// Calculate and return the destination rectangle, eg the space this entity is rendered on. | |
/// </summary> | |
/// <returns>Destination rectangle.</returns> | |
virtual public Rectangle CalcDestRect() | |
{ | |
// create new rectangle | |
Rectangle ret = new Rectangle(); | |
// get parent internal destination rectangle | |
Rectangle parentDest = _parent._destRectInternal; | |
// set size: | |
// 0: takes whole parent size. | |
// 0.0 - 1.0: takes percent of parent size. | |
// > 1.0: size in pixels. | |
Vector2 size = _scaledSize; | |
ret.Width = (size.X == 0f ? parentDest.Width : (size.X > 0f && size.X < 1f ? (int)(parentDest.Width * _size.X) : (int)size.X)); | |
ret.Height = (size.Y == 0f ? parentDest.Height : (size.Y > 0f && size.Y < 1f ? (int)(parentDest.Height * _size.Y) : (int)size.Y)); | |
// make sure valid size | |
if (ret.Width < 1) { ret.Width = 1; } | |
if (ret.Height < 1) { ret.Height = 1; } | |
// first calc some helpers | |
int parent_left = parentDest.X; | |
int parent_top = parentDest.Y; | |
int parent_right = parent_left + parentDest.Width; | |
int parent_bottom = parent_top + parentDest.Height; | |
int parent_center_x = parent_left + parentDest.Width / 2; | |
int parent_center_y = parent_top + parentDest.Height / 2; | |
// get anchor and offset | |
Anchor anchor = _anchor; | |
Vector2 offset = _scaledOffset; | |
// if we are in dragging mode we do a little hack to use top-left anchor with the dragged offset | |
// note: but only if drag offset was previously set. | |
if (_draggable && !_needToSetDragOffset) | |
{ | |
anchor = Anchor.TopLeft; | |
offset = _dragOffset; | |
} | |
// calculate position based on anchor, parent and offset | |
switch (anchor) | |
{ | |
case Anchor.Auto: | |
case Anchor.AutoInline: | |
case Anchor.TopLeft: | |
ret.X = parent_left + (int)offset.X; | |
ret.Y = parent_top + (int)offset.Y; | |
break; | |
case Anchor.TopRight: | |
ret.X = parent_right - ret.Width - (int)offset.X; | |
ret.Y = parent_top + (int)offset.Y; | |
break; | |
case Anchor.TopCenter: | |
case Anchor.AutoCenter: | |
ret.X = parent_center_x - ret.Width / 2 + (int)offset.X; | |
ret.Y = parent_top + (int)offset.Y; | |
break; | |
case Anchor.BottomLeft: | |
ret.X = parent_left + (int)offset.X; | |
ret.Y = parent_bottom - ret.Height - (int)offset.Y; | |
break; | |
case Anchor.BottomRight: | |
ret.X = parent_right - ret.Width - (int)offset.X; | |
ret.Y = parent_bottom - ret.Height - (int)offset.Y; | |
break; | |
case Anchor.BottomCenter: | |
ret.X = parent_center_x - ret.Width / 2 + (int)offset.X; | |
ret.Y = parent_bottom - ret.Height - (int)offset.Y; | |
break; | |
case Anchor.CenterLeft: | |
ret.X = parent_left + (int)offset.X; | |
ret.Y = parent_center_y - ret.Height / 2 + (int)offset.Y; | |
break; | |
case Anchor.CenterRight: | |
ret.X = parent_right - ret.Width - (int)offset.X; | |
ret.Y = parent_center_y - ret.Height / 2 + (int)offset.Y; | |
break; | |
case Anchor.Center: | |
ret.X = parent_center_x - ret.Width / 2 + (int)offset.X; | |
ret.Y = parent_center_y - ret.Height / 2 + (int)offset.Y; | |
break; | |
} | |
// special case for auto anchors | |
if ((anchor == Anchor.Auto || anchor == Anchor.AutoInline || anchor == Anchor.AutoCenter) && _parent != null) | |
{ | |
// get previous entity before this | |
Entity prevEntity = GetPreviousEntity(true); | |
// only if found align based on it | |
if (prevEntity != null) | |
{ | |
// handle inline align | |
if (anchor == Anchor.AutoInline) | |
{ | |
ret.X = prevEntity._destRect.Right + (int)(offset.X + prevEntity._scaledSpaceAfter.X + _scaledSpaceBefore.X); | |
ret.Y = prevEntity._destRect.Y; | |
} | |
// handle inline align that ran out of width / or auto anchor not inline | |
if ((anchor == Anchor.AutoInline && ret.Right > _parent._destRectInternal.Right) || | |
(anchor == Anchor.Auto || anchor == Anchor.AutoCenter)) | |
{ | |
// align x | |
if (anchor != Anchor.AutoCenter) | |
{ | |
ret.X = parent_left + (int)offset.X; | |
} | |
// align y | |
ret.Y = prevEntity.GetActualDestRect().Bottom + (int)(offset.Y + | |
prevEntity._scaledSpaceAfter.Y + | |
_scaledSpaceBefore.Y); | |
} | |
} | |
} | |
// some extra logic for draggables | |
if (_draggable) | |
{ | |
// if need to init dragged offset, set it | |
// this trick is used so if an object is draggable, we first evaluate its position based on anchor etc, and we use that | |
// position as starting point for the dragging | |
if (_needToSetDragOffset) | |
{ | |
_dragOffset.X = ret.X - parent_left; | |
_dragOffset.Y = ret.Y - parent_top; | |
_needToSetDragOffset = false; | |
} | |
} | |
// clamp position to parent container | |
if (LimitPositionToParentBoundaries || LimitDraggingToParentBoundaries && _draggable) | |
{ | |
if (ret.X < parent_left) | |
{ | |
ret.X = parent_left; | |
if (_draggable) { _dragOffset.X = 0; } | |
} | |
if (ret.Y < parent_top) | |
{ | |
ret.Y = parent_top; | |
if (_draggable) { _dragOffset.Y = 0; } | |
} | |
if (ret.Right > parent_right) | |
{ | |
ret.X -= ret.Right - parent_right; | |
if (_draggable) { _dragOffset.X -= ret.Right - parent_right; } | |
} | |
if (ret.Bottom > parent_bottom) | |
{ | |
ret.Y -= ret.Bottom - parent_bottom; | |
if (_draggable) { _dragOffset.Y -= ret.Bottom - parent_bottom; } | |
} | |
} | |
// return the newly created rectangle | |
_destRect = ret; | |
return ret; | |
} | |
/// <summary> | |
/// Return actual destination rectangle. | |
/// This can be override and implemented by things like Paragraph, where the actual destination rect is based on | |
/// text content, font and word-wrap. | |
/// </summary> | |
/// <returns>The actual destination rectangle.</returns> | |
virtual public Rectangle GetActualDestRect() | |
{ | |
return _destRect; | |
} | |
/// <summary> | |
/// Remove this entity from its parent. | |
/// </summary> | |
public void RemoveFromParent() | |
{ | |
if (_parent != null) | |
{ | |
_parent.RemoveChild(this); | |
} | |
} | |
/// <summary> | |
/// Return the relative offset, in pixels, from parent top-left corner. | |
/// </summary> | |
/// <remarks> | |
/// This return the offset between the top left corner of this entity regardless of anchor type. | |
/// </remarks> | |
/// <returns>Calculated offset from parent top-left corner.</returns> | |
public Vector2 GetRelativeOffset() | |
{ | |
Rectangle dest = GetActualDestRect(); | |
Rectangle parentDest = _parent.GetActualDestRect(); | |
return new Vector2(dest.X - parentDest.X, dest.Y - parentDest.Y); | |
} | |
/// <summary> | |
/// Return the entity before this one in parent container, aka the next older sibling. | |
/// </summary> | |
/// <returns>Entity before this in parent, or null if first in parent or if orphan entity.</returns> | |
/// <param name="skipInvisibles">If true, will skip invisible entities, eg will return the first visible older sibling.</param> | |
protected Entity GetPreviousEntity(bool skipInvisibles = false) | |
{ | |
// no parent? skip | |
if (_parent == null) { return null; } | |
// get siblings and iterate them | |
List<Entity> siblings = _parent.GetChildren(); | |
Entity prev = null; | |
foreach (Entity sibling in siblings) | |
{ | |
// when getting to self, break the loop | |
if (sibling == this) | |
{ | |
break; | |
} | |
// if older sibling is invisible, skip it | |
if (skipInvisibles && !sibling.Visible) | |
{ | |
continue; | |
} | |
// set prev | |
prev = sibling; | |
} | |
// return the previous entity (or null if wasn't found) | |
return prev; | |
} | |
/// <summary> | |
/// Handle mouse down event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnMouseDown(InputHelper input) | |
{ | |
OnMouseDown?.Invoke(this); | |
UserInterface.OnMouseDown?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle mouse up event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnMouseReleased(InputHelper input) | |
{ | |
OnMouseReleased?.Invoke(this); | |
UserInterface.OnMouseReleased?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle mouse click event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnClick(InputHelper input) | |
{ | |
OnClick?.Invoke(this); | |
UserInterface.OnClick?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle mouse down event, called every frame while down. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoWhileMouseDown(InputHelper input) | |
{ | |
WhileMouseDown?.Invoke(this); | |
UserInterface.WhileMouseDown?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle mouse hover event, called every frame while hover. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoWhileMouseHover(InputHelper input) | |
{ | |
WhileMouseHover?.Invoke(this); | |
UserInterface.WhileMouseHover?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle value change event (for entities with value). | |
/// </summary> | |
virtual protected void DoOnValueChange() | |
{ | |
OnValueChange?.Invoke(this); | |
UserInterface.OnValueChange?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle mouse enter event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnMouseEnter(InputHelper input) | |
{ | |
OnMouseEnter?.Invoke(this); | |
UserInterface.OnMouseEnter?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle mouse leave event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnMouseLeave(InputHelper input) | |
{ | |
OnMouseLeave?.Invoke(this); | |
UserInterface.OnMouseLeave?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle start dragging event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnStartDrag(InputHelper input) | |
{ | |
OnStartDrag?.Invoke(this); | |
UserInterface.OnStartDrag?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle end dragging event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnStopDrag(InputHelper input) | |
{ | |
OnStopDrag?.Invoke(this); | |
UserInterface.OnStopDrag?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle the while-dragging event. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoWhileDragging(InputHelper input) | |
{ | |
WhileDragging?.Invoke(this); | |
UserInterface.WhileDragging?.Invoke(this); | |
} | |
/// <summary> | |
/// Handle when mouse wheel scroll and this entity is the active entity. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoOnMouseWheelScroll(InputHelper input) | |
{ | |
OnMouseWheelScroll?.Invoke(this); | |
UserInterface.OnMouseWheelScroll?.Invoke(this); | |
} | |
/// <summary> | |
/// Called every frame after update. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoAfterUpdate(InputHelper input) | |
{ | |
AfterUpdate?.Invoke(this); | |
UserInterface.AfterUpdate?.Invoke(this); | |
} | |
/// <summary> | |
/// Called every time the visibility property of this entity changes. | |
/// </summary> | |
virtual protected void DoOnVisibilityChange() | |
{ | |
OnVisiblityChange?.Invoke(this); | |
UserInterface.OnVisiblityChange?.Invoke(this); | |
} | |
/// <summary> | |
/// Called every frame before update. | |
/// </summary> | |
/// <param name="input">Input helper instance.</param> | |
virtual protected void DoBeforeUpdate(InputHelper input) | |
{ | |
BeforeUpdate?.Invoke(this); | |
UserInterface.BeforeUpdate?.Invoke(this); | |
} | |
/// <summary> | |
/// Test if a given point is inside entity's boundaries. | |
/// </summary> | |
/// <remarks>This function result is affected by the 'UseActualSizeForCollision' flag.</remarks> | |
/// <param name="point">Point to test.</param> | |
/// <returns>True if point is in entity's boundaries (destination rectangle)</returns> | |
virtual public bool IsInsideEntity(Vector2 point) | |
{ | |
// get rectangle for the test | |
Rectangle rect = UseActualSizeForCollision ? GetActualDestRect() : _destRect; | |
// now test detection | |
return (point.X >= rect.Left && point.X <= rect.Right && | |
point.Y >= rect.Top && point.Y <= rect.Bottom); | |
} | |
/// <summary> | |
/// Return true if this entity is naturally interactable, like buttons, lists, etc. | |
/// Entities that are not naturally interactable are things like paragraph, colored rectangle, icon, etc. | |
/// </summary> | |
/// <remarks>This function should be overrided and implemented by different entities, and either return constant True or False.</remarks> | |
/// <returns>True if entity is naturally interactable.</returns> | |
virtual public bool IsNaturallyInteractable() | |
{ | |
return false; | |
} | |
/// <summary> | |
/// Return if the mouse is currently pressing on this entity (eg over it and left mouse button is down). | |
/// </summary> | |
public bool IsMouseDown { get { return _entityState == EntityState.MouseDown; } } | |
/// <summary> | |
/// Return if the mouse is currently over this entity (regardless of weather or not mouse button is down). | |
/// </summary> | |
public bool IsMouseOver { get { return _isMouseOver; } } | |
/// <summary> | |
/// Called every frame to update entity state and call events. | |
/// </summary> | |
/// <param name="input">Input helper.</param> | |
/// <param name="targetEntity">The deepest child entity with highest priority that we point on and can be interacted with.</param> | |
/// <param name="dragTargetEntity">The deepest child dragable entity with highest priority that we point on and can be drag if mouse down.</param> | |
/// <param name="wasEventHandled">Set to true if current event was already handled by a deeper child.</param> | |
virtual public void Update(InputHelper input, ref Entity targetEntity, ref Entity dragTargetEntity, ref bool wasEventHandled) | |
{ | |
// check if should invoke the spawn effect | |
if (_isFirstUpdate) | |
{ | |
DoOnFirstUpdate(); | |
_isFirstUpdate = false; | |
} | |
// if inherit parent state just copy it and stop | |
if (InheritParentState) | |
{ | |
_entityState = _parent._entityState; | |
_isMouseOver = _parent._isMouseOver; | |
IsFocused = _parent.IsFocused; | |
_isCurrentlyDisabled = _parent._isCurrentlyDisabled; | |
return; | |
} | |
// get if disabled | |
_isCurrentlyDisabled = IsDisabled(); | |
// if disabled, invisible, or locked - skip | |
if (_isCurrentlyDisabled || IsLocked() || !IsVisible()) | |
{ | |
// if this very entity is locked (eg not locked due to parent being locked), | |
// iterate children and invoke those with DoEventsIfDirectParentIsLocked setting | |
if (Locked) | |
{ | |
for (int i = _children.Count - 1; i >= 0; i--) | |
{ | |
if (_children[i].DoEventsIfDirectParentIsLocked) | |
{ | |
_children[i].Update(input, ref targetEntity, ref dragTargetEntity, ref wasEventHandled); | |
} | |
} | |
} | |
// set to default and return | |
_isInteractable = false; | |
_entityState = EntityState.Default; | |
return; | |
} | |
// set if interactable | |
_isInteractable = true; | |
// do before update event | |
DoBeforeUpdate(input); | |
// get mouse position | |
Vector2 mousePos = input.MousePosition; | |
// store previous state | |
EntityState prevState = _entityState; | |
// store previous mouse-over state | |
bool prevMouseOver = _isMouseOver; | |
// STEP 1: FIRST WE CALCULATE ENTITY STATE (EG MOUST HOVER / MOUSE DOWN / ..) | |
// only if event was not already catched by another entity, check for events | |
if (!wasEventHandled) | |
{ | |
// if need to calculate state locally: | |
if (!InheritParentState) | |
{ | |
// reset the mouse-over flag | |
_isMouseOver = false; | |
_entityState = EntityState.Default; | |
// set mouse state | |
if (IsInsideEntity(mousePos)) | |
{ | |
// set self as the current target, unless a sibling got the event first | |
if (targetEntity == null || targetEntity._parent != _parent) | |
{ | |
targetEntity = this; | |
} | |
// set self as the dragging target, but only if draggable or interactive | |
if (_draggable || IsNaturallyInteractable()) | |
{ | |
dragTargetEntity = this; | |
} | |
// mouse is over entity | |
_isMouseOver = true; | |
// update mouse state | |
_entityState = (IsFocused || PromiscuousClicksMode) && input.MouseButtonDown() ? EntityState.MouseDown : EntityState.MouseHover; | |
} | |
} | |
// set if focused | |
if (input.MouseButtonPressed()) | |
{ | |
IsFocused = _isMouseOver; | |
} | |
} | |
// if currently other entity is targeted and mouse clicked, set focused to false | |
else if (input.MouseButtonClick()) | |
{ | |
IsFocused = false; | |
} | |
// STEP 2: NOW WE CALL ALL CHILDREN'S UPDATE | |
// update all children (note: we go in reverse order so that entities on front will receive events before entites on back. | |
List<Entity> childrenSorted = GetSortedChildren(); | |
for (int i = childrenSorted.Count - 1; i >= 0; i--) | |
{ | |
childrenSorted[i].Update(input, ref targetEntity, ref dragTargetEntity, ref wasEventHandled); | |
} | |
// STEP 3: CALL EVENTS | |
// if selected target is this | |
if (targetEntity == this) | |
{ | |
// handled events | |
wasEventHandled = true; | |
// call the while-mouse-down handler | |
if (_entityState == EntityState.MouseDown) | |
{ | |
DoWhileMouseDown(input); | |
} | |
else | |
{ | |
DoWhileMouseHover(input); | |
} | |
// set mouse enter / mouse leave | |
if (_isMouseOver && !prevMouseOver) | |
{ | |
DoOnMouseEnter(input); | |
} | |
// generate events | |
if (prevState != _entityState) | |
{ | |
// mouse down | |
if (input.MouseButtonDown()) | |
{ | |
DoOnMouseDown(input); | |
} | |
// mouse up | |
if (input.MouseButtonReleased()) | |
{ | |
DoOnMouseReleased(input); | |
} | |
// mouse click | |
if (input.MouseButtonClick()) | |
{ | |
DoOnClick(input); | |
} | |
} | |
} | |
// if not current target, clear entity state | |
else | |
{ | |
_entityState = EntityState.Default; | |
} | |
// mouse leave events | |
if (!_isMouseOver && prevMouseOver) | |
{ | |
DoOnMouseLeave(input); | |
} | |
// handle mouse wheel scroll over this entity | |
if (targetEntity == this || UserInterface.ActiveEntity == this) | |
{ | |
if (input.MouseWheelChange != 0) | |
{ | |
DoOnMouseWheelScroll(input); | |
} | |
} | |
// STEP 4: HANDLE DRAGGING FOR DRAGABLES | |
// if draggable, and after calling all the children target is still self, it means we are being dragged! | |
if (_draggable && (dragTargetEntity == this) && input.MouseButtonDown(MouseButton.Left) && IsFocused) | |
{ | |
// check if we need to start dragging the entity that was not dragged before | |
if (!_isBeingDragged && input.MousePositionDiff.Length() != 0) | |
{ | |
// remove self from parent and add again. this trick is to keep the dragged entity always on-top | |
Entity parent = _parent; | |
RemoveFromParent(); | |
parent.AddChild(this); | |
// set dragging mode = true, and call the do-start-dragging event | |
_isBeingDragged = true; | |
DoOnStartDrag(input); | |
} | |
// if being dragged.. | |
if (_isBeingDragged) | |
{ | |
// update drag offset and call the dragging event | |
_dragOffset += input.MousePositionDiff; | |
DoWhileDragging(input); | |
} | |
} | |
// if not currently dragged but was dragged last frane, call the end dragging event | |
else if (_isBeingDragged) | |
{ | |
_isBeingDragged = false; | |
DoOnStopDrag(input); | |
} | |
// do after-update events | |
DoAfterUpdate(input); | |
} | |
} | |
} |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment