2019-08-09 18:26:28 +02:00
using System ;
using System.Collections.Generic ;
2021-03-24 01:39:41 +01:00
using System.Collections.ObjectModel ;
2022-03-26 20:06:59 +01:00
using System.Diagnostics ;
2021-03-24 01:39:41 +01:00
using System.Linq ;
2019-08-09 18:26:28 +02:00
using Microsoft.Xna.Framework ;
using Microsoft.Xna.Framework.Graphics ;
2019-08-10 18:41:56 +02:00
using Microsoft.Xna.Framework.Input ;
2019-08-09 18:26:28 +02:00
using MLEM.Extensions ;
2022-04-25 15:25:58 +02:00
using MLEM.Graphics ;
2019-08-09 22:04:26 +02:00
using MLEM.Input ;
2019-09-09 17:12:36 +02:00
using MLEM.Misc ;
2022-08-20 11:39:28 +02:00
using MLEM.Sound ;
2019-08-28 18:27:17 +02:00
using MLEM.Textures ;
2019-08-10 21:37:10 +02:00
using MLEM.Ui.Style ;
2019-08-09 18:26:28 +02:00
namespace MLEM.Ui.Elements {
2020-05-22 17:02:24 +02:00
/// <summary>
/// This class represents a generic base class for ui elements of a <see cref="UiSystem"/>.
/// </summary>
2024-07-17 18:21:13 +02:00
public abstract class Element : GenericDataHolder {
2019-08-09 18:26:28 +02:00
2021-10-28 23:26:42 +02:00
/// <summary>
/// This field holds an epsilon value used in element <see cref="Size"/>, position and resulting <see cref="Area"/> calculations to mitigate floating point rounding inaccuracies.
/// If ui elements used are extremely small or extremely large, this value can be reduced or increased.
/// </summary>
public static float Epsilon = 0.01F ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The ui system that this element is currently a part of
/// </summary>
2020-02-06 01:59:33 +01:00
public UiSystem System {
get = > this . system ;
2022-11-24 19:46:20 +01:00
private set {
2020-02-06 01:59:33 +01:00
this . system = value ;
2020-03-17 20:02:23 +01:00
this . Controls = value ? . Controls ;
2023-06-14 09:33:08 +02:00
this . AndChildren ( e = > e . Style = e . Style . OrStyle ( value ? . Style ) ) ;
2020-02-06 01:59:33 +01:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// The controls that this element's <see cref="System"/> uses
/// </summary>
2020-03-17 20:04:10 +01:00
public UiControls Controls ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// This element's parent element.
/// If this element has no parent (it is the <see cref="RootElement"/> of a ui system), this value is <c>null</c>.
/// </summary>
2020-02-06 01:59:33 +01:00
public Element Parent { get ; private set ; }
2020-05-22 17:02:24 +02:00
/// <summary>
/// This element's <see cref="RootElement"/>.
/// Note that this value is set even if this element has a <see cref="Parent"/>. To get the element that represents the root element, use <see cref="RootElement.Element"/>.
/// </summary>
2022-11-24 19:46:20 +01:00
public RootElement Root { get ; private set ; }
2020-05-22 17:02:24 +02:00
/// <summary>
/// The scale that this ui element renders with
/// </summary>
2020-02-06 01:59:33 +01:00
public float Scale = > this . Root . ActualScale ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="Anchor"/> that this element uses for positioning within its parent
/// </summary>
2019-08-09 18:26:28 +02:00
public Anchor Anchor {
get = > this . anchor ;
set {
2019-08-10 15:12:27 +02:00
if ( this . anchor = = value )
return ;
2019-08-09 18:26:28 +02:00
this . anchor = value ;
2019-08-12 14:44:42 +02:00
this . SetAreaDirty ( ) ;
2019-08-09 18:26:28 +02:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
2020-06-02 23:00:40 +02:00
/// The size of this element, where X represents the width and Y represents the height.
/// If the x or y value of the size is between 0 and 1, the size will be seen as a percentage of its parent's size rather than as an absolute value.
2022-06-24 14:01:26 +02:00
/// If the x (or y) value of the size is negative, the width (or height) is seen as a percentage of the element's resulting height (or width).
2022-08-16 14:20:32 +02:00
/// Additionally, if auto-sizing is used, <see cref="AutoSizeAddedAbsolute"/> can be set to add or subtract an absolute value from the automatically calculated size.
2020-06-02 23:00:40 +02:00
/// </summary>
/// <example>
2022-08-16 14:20:32 +02:00
/// The following example, ignoring <see cref="Scale"/>, combines both types of percentage-based sizing.
2020-06-02 23:00:40 +02:00
/// If this element is inside of a <see cref="Parent"/> whose width is 20, this element's width will be set to <c>0.5 * 20 = 10</c>, and its height will be set to <c>2.5 * 10 = 25</c>.
/// <code>
/// element.Size = new Vector2(0.5F, -2.5F);
/// </code>
/// </example>
2019-08-09 19:28:48 +02:00
public Vector2 Size {
2019-08-09 18:26:28 +02:00
get = > this . size ;
set {
2019-08-10 15:12:27 +02:00
if ( this . size = = value )
return ;
2019-08-09 18:26:28 +02:00
this . size = value ;
2019-08-12 14:44:42 +02:00
this . SetAreaDirty ( ) ;
2019-08-09 18:26:28 +02:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="Size"/>, but with <see cref="Scale"/> applied.
/// </summary>
2019-08-11 21:24:09 +02:00
public Vector2 ScaledSize = > this . size * this . Scale ;
2020-05-22 17:02:24 +02:00
/// <summary>
2022-08-16 14:20:32 +02:00
/// If auto-sizing is used by setting <see cref="Size"/> less than or equal to 1, this property allows adding or subtracting an additional, absolute value from the automatically calculated size.
/// If this element is not using auto-sizing, this property is ignored.
/// </summary>
/// <example>
/// Ignoring <see cref="Scale"/>, if this element's <see cref="Size"/> is set to <c>0.5, 0.75</c> and its <see cref="Parent"/> has a size of <c>200, 100</c>, then an added absolute size of <c>-50, 25</c> will result in a final <see cref="Area"/> size of <c>0.5 * 200 - 50, 0.75 * 100 + 25</c>, or <c>50, 100</c>.
/// </example>
public Vector2 AutoSizeAddedAbsolute {
get = > this . autoSizeAddedAbsolute ;
set {
if ( this . autoSizeAddedAbsolute = = value )
return ;
this . autoSizeAddedAbsolute = value ;
this . SetAreaDirty ( ) ;
}
}
/// <summary>
/// The <see cref="AutoSizeAddedAbsolute"/>, but with <see cref="Scale"/> applied.
/// </summary>
public Vector2 ScaledAutoSizeAddedAbsolute = > this . autoSizeAddedAbsolute * this . Scale ;
/// <summary>
2020-05-22 17:02:24 +02:00
/// This element's offset from its default position, which is dictated by its <see cref="Anchor"/>.
/// Note that, depending on the side that the element is anchored to, this offset moves it in a different direction.
/// </summary>
2019-08-13 23:54:29 +02:00
public Vector2 PositionOffset {
2019-08-09 18:26:28 +02:00
get = > this . offset ;
set {
2019-08-10 15:12:27 +02:00
if ( this . offset = = value )
return ;
2019-08-09 18:26:28 +02:00
this . offset = value ;
2019-08-12 14:44:42 +02:00
this . SetAreaDirty ( ) ;
2019-08-09 18:26:28 +02:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="PositionOffset"/>, but with <see cref="Scale"/> applied.
/// </summary>
2019-12-14 14:00:12 +01:00
public Vector2 ScaledOffset = > this . offset * this . Scale ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="Padding"/>, but with <see cref="Scale"/> applied.
/// </summary>
2021-10-30 15:33:38 +02:00
public Padding ScaledPadding = > this . Padding . Value * this . Scale ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="ChildPadding"/>, but with <see cref="Scale"/> applied.
/// </summary>
2021-10-30 15:33:38 +02:00
public Padding ScaledChildPadding = > this . ChildPadding . Value * this . Scale ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// This element's current <see cref="Area"/>, but with <see cref="ScaledChildPadding"/> applied.
/// </summary>
2020-02-06 01:59:33 +01:00
public RectangleF ChildPaddedArea = > this . UnscrolledArea . Shrink ( this . ScaledChildPadding ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// This element's area, without respecting its <see cref="ScrollOffset"/>.
/// This area is updated automatically to fit this element's sizing and positioning properties.
/// </summary>
2020-02-06 01:59:33 +01:00
public RectangleF UnscrolledArea {
get {
this . UpdateAreaIfDirty ( ) ;
return this . area ;
2019-08-10 21:37:10 +02:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="UnscrolledArea"/> of this element, but with <see cref="ScaledScrollOffset"/> applied.
/// </summary>
2020-02-06 01:59:33 +01:00
public RectangleF Area = > this . UnscrolledArea . OffsetCopy ( this . ScaledScrollOffset ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The area that this element is displayed in, which is <see cref="Area"/> shrunk by this element's <see cref="ScaledPadding"/>.
/// This is the property that should be used for drawing this element, as well as mouse input handling and culling.
/// </summary>
2020-02-06 01:59:33 +01:00
public RectangleF DisplayArea = > this . Area . Shrink ( this . ScaledPadding ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The offset that this element has as a result of <see cref="Panel"/> scrolling.
/// </summary>
2020-02-06 01:59:33 +01:00
public Vector2 ScrollOffset ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// The <see cref="ScrollOffset"/>, but with <see cref="Scale"/> applied.
/// </summary>
2020-02-06 01:59:33 +01:00
public Vector2 ScaledScrollOffset = > this . ScrollOffset * this . Scale ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Set this property to <c>true</c> to cause this element to be hidden.
/// Hidden elements don't receive input events, aren't rendered and don't factor into auto-anchoring.
/// </summary>
2022-04-05 14:17:12 +02:00
public virtual bool IsHidden {
2022-12-21 21:02:10 +01:00
get = > this . isHidden ;
2019-08-09 22:23:16 +02:00
set {
2019-08-10 15:12:27 +02:00
if ( this . isHidden = = value )
return ;
2019-08-09 22:23:16 +02:00
this . isHidden = value ;
2019-08-12 14:44:42 +02:00
this . SetAreaDirty ( ) ;
2019-08-09 22:23:16 +02:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// The priority of this element as part of its <see cref="Parent"/> element.
2021-12-11 17:26:55 +01:00
/// A higher priority means the element will be drawn first, but not anchored higher up if auto-anchoring is used.
2020-05-22 17:02:24 +02:00
/// </summary>
2019-08-12 14:44:42 +02:00
public int Priority {
get = > this . priority ;
set {
2020-06-02 13:40:05 +02:00
if ( this . priority = = value )
return ;
2019-08-12 14:44:42 +02:00
this . priority = value ;
if ( this . Parent ! = null )
this . Parent . SetSortedChildrenDirty ( ) ;
}
}
2020-07-15 23:21:52 +02:00
/// <summary>
/// This element's transform matrix.
/// Can easily be scaled using <see cref="ScaleTransform"/>.
2022-06-24 14:01:26 +02:00
/// Note that, when this is non-null, a new <c>SpriteBatch.Begin</c> call is used for this element.
2020-07-15 23:21:52 +02:00
/// </summary>
2020-07-20 14:18:26 +02:00
public Matrix Transform = Matrix . Identity ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Set this field to false to disallow the element from being selected.
/// An unselectable element is skipped by automatic navigation and its <see cref="OnSelected"/> callback will never be called.
/// </summary>
2022-05-21 20:42:54 +02:00
public virtual bool CanBeSelected {
get = > this . canBeSelected ;
set {
this . canBeSelected = value ;
if ( ! this . canBeSelected & & this . Root ? . SelectedElement = = this )
this . Root . SelectElement ( null ) ;
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Set this field to false to disallow the element from reacting to being moused over.
/// </summary>
2022-03-11 13:25:18 +01:00
public virtual bool CanBeMoused { get ; set ; } = true ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Set this field to false to disallow this element's <see cref="OnPressed"/> and <see cref="OnSecondaryPressed"/> events to be called.
/// </summary>
2022-03-11 13:25:18 +01:00
public virtual bool CanBePressed { get ; set ; } = true ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Set this field to false to cause auto-anchored siblings to ignore this element as a possible anchor point.
/// </summary>
2022-12-23 13:25:56 +01:00
public virtual bool CanAutoAnchorsAttach {
get = > this . canAutoAnchorsAttach ;
set {
if ( this . canAutoAnchorsAttach ! = value ) {
this . canAutoAnchorsAttach = value ;
this . SetAreaDirty ( ) ;
}
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
2021-03-24 01:25:39 +01:00
/// Set this field to true to cause this element's width to be automatically calculated based on the area that its <see cref="Children"/> take up.
2021-06-25 16:48:41 +02:00
/// To use this element's <see cref="Size"/>'s X coordinate as a minimum or maximum width rather than ignoring it, set <see cref="TreatSizeAsMinimum"/> or <see cref="TreatSizeAsMaximum"/> to true.
2021-03-24 01:25:39 +01:00
/// </summary>
2022-12-23 13:25:56 +01:00
public virtual bool SetWidthBasedOnChildren {
get = > this . setWidthBasedOnChildren ;
set {
if ( this . setWidthBasedOnChildren ! = value ) {
this . setWidthBasedOnChildren = value ;
this . SetAreaDirty ( ) ;
}
}
}
2021-03-24 01:25:39 +01:00
/// <summary>
2020-05-22 17:02:24 +02:00
/// Set this field to true to cause this element's height to be automatically calculated based on the area that its <see cref="Children"/> take up.
2021-06-25 16:48:41 +02:00
/// To use this element's <see cref="Size"/>'s Y coordinate as a minimum or maximum height rather than ignoring it, set <see cref="TreatSizeAsMinimum"/> or <see cref="TreatSizeAsMaximum"/> to true.
2020-05-22 17:02:24 +02:00
/// </summary>
2022-12-23 13:25:56 +01:00
public virtual bool SetHeightBasedOnChildren {
get = > this . setHeightBasedOnChildren ;
set {
if ( this . setHeightBasedOnChildren ! = value ) {
this . setHeightBasedOnChildren = value ;
this . SetAreaDirty ( ) ;
}
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
2021-03-24 01:25:39 +01:00
/// If this field is set to true, and <see cref="SetWidthBasedOnChildren"/> or <see cref="SetHeightBasedOnChildren"/> are enabled, the resulting width or height will always be greather than or equal to this element's <see cref="Size"/>.
/// For example, if an element's <see cref="Size"/>'s Y coordinate is set to 20, but there is only one child with a height of 10 in it, the element's height would be shrunk to 10 if this value was false, but would remain at 20 if it was true.
/// Note that this value only has an effect if <see cref="SetWidthBasedOnChildren"/> or <see cref="SetHeightBasedOnChildren"/> are enabled.
2020-05-22 17:02:24 +02:00
/// </summary>
2022-12-23 13:25:56 +01:00
public virtual bool TreatSizeAsMinimum {
get = > this . treatSizeAsMinimum ;
set {
if ( this . treatSizeAsMinimum ! = value ) {
this . treatSizeAsMinimum = value ;
this . SetAreaDirty ( ) ;
}
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
2021-06-25 16:48:41 +02:00
/// If this field is set to true, and <see cref="SetWidthBasedOnChildren"/> or <see cref="SetHeightBasedOnChildren"/>are enabled, the resulting width or height weill always be less than or equal to this element's <see cref="Size"/>.
/// Note that this value only has an effect if <see cref="SetWidthBasedOnChildren"/> or <see cref="SetHeightBasedOnChildren"/> are enabled.
/// </summary>
2022-12-23 13:25:56 +01:00
public virtual bool TreatSizeAsMaximum {
get = > this . treatSizeAsMaximum ;
set {
if ( this . treatSizeAsMaximum ! = value ) {
this . treatSizeAsMaximum = value ;
this . SetAreaDirty ( ) ;
}
}
}
2021-06-25 16:48:41 +02:00
/// <summary>
2020-06-12 02:04:01 +02:00
/// Set this field to true to cause this element's final display area to never exceed that of its <see cref="Parent"/>.
/// If the resulting area is too large, the size of this element is shrunk to fit the target area.
/// This can be useful if an element should fill the remaining area of a parent exactly.
/// </summary>
2022-12-23 13:25:56 +01:00
public virtual bool PreventParentSpill {
get = > this . preventParentSpill ;
set {
if ( this . preventParentSpill ! = value ) {
this . preventParentSpill = value ;
this . SetAreaDirty ( ) ;
}
}
}
2020-06-12 02:04:01 +02:00
/// <summary>
2020-05-22 17:02:24 +02:00
/// The transparency (alpha value) that this element is rendered with.
2022-04-25 15:25:58 +02:00
/// Note that, when <see cref="Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> is called, this alpha value is multiplied with the <see cref="Parent"/>'s alpha value and passed down to this element's <see cref="Children"/>.
2020-05-22 17:02:24 +02:00
/// </summary>
2022-03-11 13:25:18 +01:00
public virtual float DrawAlpha { get ; set ; } = 1 ;
2020-05-22 17:02:24 +02:00
/// <summary>
2020-06-04 20:52:21 +02:00
/// Stores whether this element is currently being moused over or touched.
2020-05-22 17:02:24 +02:00
/// </summary>
2022-04-15 14:18:55 +02:00
public bool IsMouseOver = > this . Controls . MousedElement = = this | | this . Controls . TouchedElement = = this ;
2020-05-22 17:02:24 +02:00
/// <summary>
2022-03-17 20:45:28 +01:00
/// Returns whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/>.
2023-04-24 11:15:16 +02:00
/// Note that, unlike <see cref="IsSelectedActive"/>, this property will be <see langword="true"/> even if this element's <see cref="Root"/> is not the <see cref="UiControls.ActiveRoot"/>.
2020-05-22 17:02:24 +02:00
/// </summary>
2022-03-17 20:45:28 +01:00
public bool IsSelected = > this . Root . SelectedElement = = this ;
2021-12-11 17:26:55 +01:00
/// <summary>
2023-04-24 11:15:16 +02:00
/// Returns whether this element is its <see cref="Controls"/>'s <see cref="UiControls.SelectedElement"/>.
/// Note that <see cref="IsSelected"/> can be used to query whether this element is its <see cref="Root"/>'s <see cref="RootElement.SelectedElement"/> instead.
/// </summary>
public bool IsSelectedActive = > this . Controls . SelectedElement = = this ;
/// <summary>
2021-12-11 17:26:55 +01:00
/// Returns whether this element's <see cref="SetAreaDirty"/> method has been recently called and its area has not been updated since then using <see cref="UpdateAreaIfDirty"/> or <see cref="ForceUpdateArea"/>.
/// </summary>
public bool AreaDirty { get ; private set ; }
2022-04-14 17:54:25 +02:00
/// <summary>
/// An optional string that represents a group of elements for automatic (keyboard and gamepad) navigation.
/// All elements that share the same auto-nav group will be able to be navigated between, and all other elements will not be reachable from elements of other groups.
2022-05-17 16:06:22 +02:00
/// Note that, if no element is previously selected and auto-navigation is invoked, this element cannot be chosen as the first element to navigate to if its auto-nav group is non-null.
2022-04-14 17:54:25 +02:00
/// </summary>
public virtual string AutoNavGroup { get ; set ; }
2021-10-30 15:33:38 +02:00
2022-01-22 23:34:52 +01:00
/// <summary>
/// This Element's current <see cref="UiStyle"/>.
/// When this property is set, <see cref="InitStyle"/> is called.
/// </summary>
public StyleProp < UiStyle > Style {
get = > this . style ;
set {
this . style = value ;
if ( this . style . HasValue ( ) )
this . InitStyle ( this . style ) ;
}
}
2021-10-30 15:01:04 +02:00
/// <summary>
/// A style property that contains the selection indicator that is displayed on this element if it is the <see cref="RootElement.SelectedElement"/>
/// </summary>
public StyleProp < NinePatch > SelectionIndicator ;
/// <summary>
/// A style property that contains the sound effect that is played when this element's <see cref="OnPressed"/> is called
/// </summary>
public StyleProp < SoundEffectInfo > ActionSound ;
/// <summary>
/// A style property that contains the sound effect that is played when this element's <see cref="OnSecondaryPressed"/> is called
/// </summary>
public StyleProp < SoundEffectInfo > SecondActionSound ;
2021-10-30 15:33:38 +02:00
/// <summary>
/// The padding that this element has.
/// The padding is subtracted from the element's <see cref="Size"/>, and it is an area that the element does not extend into. This means that this element's resulting <see cref="DisplayArea"/> does not include this padding.
/// </summary>
public StyleProp < Padding > Padding ;
/// <summary>
/// The child padding that this element has.
/// The child padding moves any <see cref="Children"/> added to this element inwards by the given amount in each direction.
/// </summary>
2021-12-21 00:01:57 +01:00
public StyleProp < Padding > ChildPadding {
get = > this . childPadding ;
set {
this . childPadding = value ;
this . SetAreaDirty ( ) ;
}
}
2023-06-14 10:21:32 +02:00
/// <summary>
/// A <see cref="UiAnimation"/> that is played when the mouse enters this element, in <see cref="OnMouseEnter"/>.
/// </summary>
public StyleProp < UiAnimation > MouseEnterAnimation ;
/// <summary>
/// A <see cref="UiAnimation"/> that is played when the mouse exits this element, in <see cref="OnMouseExit"/>.
/// </summary>
public StyleProp < UiAnimation > MouseExitAnimation ;
2020-02-06 01:59:33 +01:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called after this element is drawn, but before its children are drawn
/// </summary>
2020-02-06 01:59:33 +01:00
public DrawCallback OnDrawn ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element is updated
/// </summary>
2020-02-06 01:59:33 +01:00
public TimeCallback OnUpdated ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element is pressed
/// </summary>
2020-02-06 01:59:33 +01:00
public GenericCallback OnPressed ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element is pressed using the secondary action
/// </summary>
2020-02-06 01:59:33 +01:00
public GenericCallback OnSecondaryPressed ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element's <see cref="IsSelected"/> is turned true
/// </summary>
2020-02-06 01:59:33 +01:00
public GenericCallback OnSelected ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element's <see cref="IsSelected"/> is turned false
/// </summary>
2020-02-06 01:59:33 +01:00
public GenericCallback OnDeselected ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element starts being moused over
/// </summary>
2020-02-06 01:59:33 +01:00
public GenericCallback OnMouseEnter ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element stops being moused over
/// </summary>
2020-02-06 01:59:33 +01:00
public GenericCallback OnMouseExit ;
2020-05-22 17:02:24 +02:00
/// <summary>
2020-06-04 20:52:21 +02:00
/// Event that is called when this element starts being touched
/// </summary>
public GenericCallback OnTouchEnter ;
/// <summary>
/// Event that is called when this element stops being touched
/// </summary>
public GenericCallback OnTouchExit ;
/// <summary>
2020-05-22 17:02:24 +02:00
/// Event that is called when text input is made.
2021-04-23 00:17:46 +02:00
/// When an element uses this event, it should call <see cref="MlemPlatform.EnsureExists"/> on construction to ensure that the MLEM platform is initialized.
2020-05-22 17:02:24 +02:00
/// Note that this event is called for every element, even if it is not selected.
2021-04-23 00:17:46 +02:00
/// Also note that if the active <see cref="MlemPlatform"/> uses an on-screen keyboard, this event is never called.
2020-05-22 17:02:24 +02:00
/// </summary>
2020-02-06 01:59:33 +01:00
public TextInputCallback OnTextInput ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when this element's <see cref="DisplayArea"/> is changed.
/// </summary>
2020-02-06 01:59:33 +01:00
public GenericCallback OnAreaUpdated ;
2020-05-22 17:02:24 +02:00
/// <summary>
2022-01-22 23:05:29 +01:00
/// Event that is called when this element's <see cref="InitStyle"/> method is called while setting the <see cref="Style"/>.
/// </summary>
public GenericCallback OnStyleInit ;
/// <summary>
2020-05-22 17:02:24 +02:00
/// Event that is called when the element that is currently being moused changes within the ui system.
/// Note that the event fired doesn't necessarily correlate to this specific element.
/// </summary>
2020-02-06 01:59:33 +01:00
public OtherElementCallback OnMousedElementChanged ;
2020-05-22 17:02:24 +02:00
/// <summary>
2020-06-04 20:52:21 +02:00
/// Event that is called when the element that is currently being touched changes within the ui system.
/// Note that the event fired doesn't necessarily correlate to this specific element.
/// </summary>
public OtherElementCallback OnTouchedElementChanged ;
/// <summary>
2020-05-22 17:02:24 +02:00
/// Event that is called when the element that is currently selected changes within the ui system.
/// Note that the event fired doesn't necessarily correlate to this specific element.
/// </summary>
2020-02-06 01:59:33 +01:00
public OtherElementCallback OnSelectedElementChanged ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when the next element to select when pressing tab is calculated.
/// To cause a different element than the default one to be selected, return it during this event.
/// </summary>
2020-02-06 01:59:33 +01:00
public TabNextElementCallback GetTabNextElement ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// Event that is called when the next element to select when using gamepad input is calculated.
/// To cause a different element than the default one to be selected, return it during this event.
/// </summary>
2020-02-06 01:59:33 +01:00
public GamepadNextElementCallback GetGamepadNextElement ;
2020-05-22 17:02:24 +02:00
/// <summary>
2022-09-13 15:44:12 +02:00
/// Event that is called when a child is added to this element using <see cref="AddChild{T}"/>
/// Note that, while this event is only called for immediate children of this element, <see cref="RootElement.OnElementAdded"/> is called for all children and grandchildren.
2020-05-22 17:02:24 +02:00
/// </summary>
2020-05-20 00:48:53 +02:00
public OtherElementCallback OnChildAdded ;
2020-05-22 17:02:24 +02:00
/// <summary>
2022-09-13 15:44:12 +02:00
/// Event that is called when a child is removed from this element using <see cref="RemoveChild"/>.
/// Note that, while this event is only called for immediate children of this element, <see cref="RootElement.OnElementRemoved"/> is called for all children and grandchildren.
2020-05-22 17:02:24 +02:00
/// </summary>
2020-05-20 00:48:53 +02:00
public OtherElementCallback OnChildRemoved ;
2021-06-09 00:27:50 +02:00
/// <summary>
2022-09-13 14:27:49 +02:00
/// Event that is called when this element is added to a <see cref="UiSystem"/>, that is, when this element's <see cref="System"/> is set to a non-<see langword="null"/> value.
/// </summary>
public GenericCallback OnAddedToUi ;
/// <summary>
/// Event that is called when this element is removed from a <see cref="UiSystem"/>, that is, when this element's <see cref="System"/> is set to <see langword="null"/>.
/// </summary>
public GenericCallback OnRemovedFromUi ;
2020-02-06 01:59:33 +01:00
2020-05-22 17:02:24 +02:00
/// <summary>
2021-10-30 15:01:04 +02:00
/// A list of all of this element's direct children.
/// Use <see cref="AddChild{T}"/> or <see cref="RemoveChild"/> to manipulate this list while calling all of the necessary callbacks.
2020-05-22 17:02:24 +02:00
/// </summary>
2024-06-11 18:47:22 +02:00
public readonly IList < Element > Children ;
2023-06-14 10:21:32 +02:00
/// <summary>
/// A list of all of the <see cref="UiAnimation"/> instances that are currently playing.
/// You can modify this collection through <see cref="PlayAnimation"/> and <see cref="StopAnimation"/>.
/// </summary>
protected readonly List < UiAnimation > PlayingAnimations = new List < UiAnimation > ( ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
2021-10-30 15:01:04 +02:00
/// A sorted version of <see cref="Children"/>. The children are sorted by their <see cref="Priority"/>.
2020-05-22 17:02:24 +02:00
/// </summary>
2021-10-30 15:01:04 +02:00
protected IList < Element > SortedChildren {
get {
this . UpdateSortedChildrenIfDirty ( ) ;
return this . sortedChildren ;
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
2021-10-30 15:01:04 +02:00
/// The input handler that this element's <see cref="Controls"/> use
2020-05-22 17:02:24 +02:00
/// </summary>
2021-10-30 15:01:04 +02:00
protected InputHandler Input = > this . Controls . Input ;
2022-01-06 23:26:14 +01:00
/// <summary>
/// The <see cref="ChildPaddedArea"/> of this element's <see cref="Parent"/>, or the <see cref="UiSystem.Viewport"/> if this element has no parent.
/// This value is the one that is passed to <see cref="CalcActualSize"/> during <see cref="ForceUpdateArea"/>.
/// </summary>
2022-09-19 15:02:36 +02:00
protected RectangleF ParentArea = > this . Parent ? . ChildPaddedArea ? ? ( RectangleF ) this . System . Viewport ;
2021-10-30 15:01:04 +02:00
private readonly List < Element > children = new List < Element > ( ) ;
2022-03-26 20:06:59 +01:00
private readonly Stopwatch stopwatch = new Stopwatch ( ) ;
2021-10-30 15:01:04 +02:00
private bool sortedChildrenDirty ;
private IList < Element > sortedChildren ;
private UiSystem system ;
private Anchor anchor ;
private Vector2 size ;
2022-08-16 14:20:32 +02:00
private Vector2 autoSizeAddedAbsolute ;
2021-10-30 15:01:04 +02:00
private Vector2 offset ;
private RectangleF area ;
private bool isHidden ;
private int priority ;
2022-01-22 23:34:52 +01:00
private StyleProp < UiStyle > style ;
2021-12-21 00:01:57 +01:00
private StyleProp < Padding > childPadding ;
2022-05-21 20:42:54 +02:00
private bool canBeSelected = true ;
2022-12-23 13:25:56 +01:00
private bool canAutoAnchorsAttach = true ;
private bool setWidthBasedOnChildren ;
private bool setHeightBasedOnChildren ;
private bool treatSizeAsMinimum ;
private bool treatSizeAsMaximum ;
private bool preventParentSpill ;
2019-08-09 18:26:28 +02:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// Creates a new element with the given anchor and size and sets up some default event reactions.
/// </summary>
/// <param name="anchor">This element's <see cref="Anchor"/></param>
/// <param name="size">This element's default <see cref="Size"/></param>
protected Element ( Anchor anchor , Vector2 size ) {
2019-08-09 18:26:28 +02:00
this . anchor = anchor ;
this . size = size ;
2019-08-09 22:04:26 +02:00
2021-03-24 01:39:41 +01:00
this . Children = new ReadOnlyCollection < Element > ( this . children ) ;
2023-06-14 10:21:32 +02:00
this . GetTabNextElement + = ( backward , next ) = > next ;
this . GetGamepadNextElement + = ( dir , next ) = > next ;
this . OnMouseEnter + = e = > {
if ( e . MouseEnterAnimation . HasValue ( ) )
e . PlayAnimation ( e . MouseEnterAnimation ) ;
} ;
this . OnMouseExit + = e = > {
if ( e . MouseExitAnimation . HasValue ( ) )
e . PlayAnimation ( e . MouseExitAnimation ) ;
} ;
2019-08-09 22:04:26 +02:00
2019-08-12 14:44:42 +02:00
this . SetAreaDirty ( ) ;
2021-03-24 01:39:41 +01:00
this . SetSortedChildrenDirty ( ) ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Adds a child to this element.
/// </summary>
/// <param name="element">The child element to add</param>
/// <param name="index">The index to add the child at, or -1 to add it to the end of the <see cref="Children"/> list</param>
/// <typeparam name="T">The type of child to add</typeparam>
/// <returns>This element, for chaining</returns>
2021-03-24 22:01:02 +01:00
public virtual T AddChild < T > ( T element , int index = - 1 ) where T : Element {
2021-03-24 01:39:41 +01:00
if ( index < 0 | | index > this . children . Count )
index = this . children . Count ;
this . children . Insert ( index , element ) ;
2019-08-23 22:23:10 +02:00
element . Parent = this ;
2023-11-11 12:15:21 +01:00
element . AndChildren ( e = > e . AddedToUi ( this . System , this . Root ) ) ;
2022-09-13 15:44:12 +02:00
this . OnChildAdded ? . Invoke ( this , element ) ;
2019-08-12 14:44:42 +02:00
this . SetSortedChildrenDirty ( ) ;
2023-11-11 13:02:18 +01:00
element . SetAreaDirty ( ) ;
2019-08-09 22:23:16 +02:00
return element ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Removes the given child from this element.
/// </summary>
/// <param name="element">The child element to remove</param>
2021-03-24 22:01:02 +01:00
public virtual void RemoveChild ( Element element ) {
2021-03-24 01:39:41 +01:00
this . children . Remove ( element ) ;
2022-04-15 14:16:38 +02:00
if ( this . Root ? . SelectedElement = = element )
this . Root . SelectElement ( null ) ;
2020-05-17 00:33:16 +02:00
// set area dirty here so that a dirty call is made
// upwards to us if the element is auto-positioned
element . SetAreaDirty ( ) ;
2019-08-23 22:23:10 +02:00
element . Parent = null ;
2022-11-24 19:46:20 +01:00
element . AndChildren ( e = > e . RemovedFromUi ( ) ) ;
2022-09-13 15:44:12 +02:00
this . OnChildRemoved ? . Invoke ( this , element ) ;
2019-12-25 12:19:55 +01:00
this . SetSortedChildrenDirty ( ) ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Removes all children from this element that match the given condition.
/// </summary>
/// <param name="condition">The condition that determines if a child should be removed</param>
2021-03-24 22:01:02 +01:00
public virtual void RemoveChildren ( Func < Element , bool > condition = null ) {
2019-09-12 11:52:47 +02:00
for ( var i = this . Children . Count - 1 ; i > = 0 ; i - - ) {
2019-09-17 14:06:10 +02:00
var child = this . Children [ i ] ;
if ( condition = = null | | condition ( child ) ) {
this . RemoveChild ( child ) ;
}
2019-09-12 11:52:47 +02:00
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Causes <see cref="SortedChildren"/> to be recalculated as soon as possible.
/// </summary>
2019-08-12 14:44:42 +02:00
public void SetSortedChildrenDirty ( ) {
this . sortedChildrenDirty = true ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Updates the <see cref="SortedChildren"/> list if <see cref="SetSortedChildrenDirty"/> is true.
/// </summary>
2019-08-12 14:44:42 +02:00
public void UpdateSortedChildrenIfDirty ( ) {
2019-11-18 22:36:55 +01:00
if ( this . sortedChildrenDirty )
this . ForceUpdateSortedChildren ( ) ;
}
2019-08-12 14:44:42 +02:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// Forces an update of the <see cref="SortedChildren"/> list.
/// </summary>
2019-11-18 22:36:55 +01:00
public virtual void ForceUpdateSortedChildren ( ) {
this . sortedChildrenDirty = false ;
2021-03-24 01:39:41 +01:00
this . sortedChildren = new ReadOnlyCollection < Element > ( this . Children . OrderBy ( e = > e . Priority ) . ToArray ( ) ) ;
2019-11-18 22:36:55 +01:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Causes this element's <see cref="Area"/> to be recalculated as soon as possible.
/// If this element is auto-anchored or its parent automatically changes its size based on its children, this element's parent's area is also marked dirty.
/// </summary>
2019-11-18 22:36:55 +01:00
public void SetAreaDirty ( ) {
2021-12-11 17:26:55 +01:00
this . AreaDirty = true ;
2022-01-09 01:12:16 +01:00
this . Parent ? . OnChildAreaDirty ( this , false ) ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
2021-12-11 17:26:55 +01:00
/// Updates this element's <see cref="Area"/> and all of its <see cref="Children"/> by calling <see cref="ForceUpdateArea"/> if <see cref="AreaDirty"/> is true.
2020-05-22 17:02:24 +02:00
/// </summary>
2021-12-11 17:26:55 +01:00
/// <returns>Whether <see cref="AreaDirty"/> was true and <see cref="ForceUpdateArea"/> was called</returns>
2021-11-01 15:29:59 +01:00
public bool UpdateAreaIfDirty ( ) {
2021-12-11 17:26:55 +01:00
if ( this . AreaDirty ) {
2019-08-09 18:26:28 +02:00
this . ForceUpdateArea ( ) ;
2021-11-01 15:29:59 +01:00
return true ;
}
return false ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Forces this element's <see cref="Area"/> to be updated if it is not <see cref="IsHidden"/>.
/// This method also updates all of this element's <see cref="Children"/>'s areas.
/// </summary>
2019-08-09 19:28:48 +02:00
public virtual void ForceUpdateArea ( ) {
2021-12-11 17:26:55 +01:00
this . AreaDirty = false ;
2022-09-19 15:02:36 +02:00
if ( this . IsHidden | | this . System = = null )
2019-08-15 14:59:15 +02:00
return ;
2021-11-01 13:39:37 +01:00
// if the parent's area is dirty, it would get updated anyway when querying its ChildPaddedArea,
// which would cause our ForceUpdateArea code to be run twice, so we only update our parent instead
2021-11-01 15:29:59 +01:00
if ( this . Parent ! = null & & this . Parent . UpdateAreaIfDirty ( ) )
2021-11-01 13:39:37 +01:00
return ;
2022-03-26 20:06:59 +01:00
this . stopwatch . Restart ( ) ;
2019-08-09 18:26:28 +02:00
2021-03-29 06:41:38 +02:00
var recursion = 0 ;
2024-06-09 20:22:24 +02:00
UpdateDisplayArea ( ) ;
2021-03-24 01:10:42 +01:00
2022-03-26 20:06:59 +01:00
this . stopwatch . Stop ( ) ;
this . System . Metrics . ForceAreaUpdateTime + = this . stopwatch . Elapsed ;
2021-12-12 12:32:09 +01:00
this . System . Metrics . ForceAreaUpdates + + ;
2024-06-09 20:22:24 +02:00
void UpdateDisplayArea ( Vector2 ? overrideSize = null ) {
var parentArea = this . ParentArea ;
var parentCenterX = parentArea . X + parentArea . Width / 2 ;
var parentCenterY = parentArea . Y + parentArea . Height / 2 ;
var intendedSize = this . CalcActualSize ( parentArea ) ;
var newSize = overrideSize ? ? intendedSize ;
2021-03-24 01:10:42 +01:00
var pos = new Vector2 ( ) ;
2024-06-09 20:22:24 +02:00
2021-03-24 01:10:42 +01:00
switch ( this . anchor ) {
case Anchor . TopLeft :
case Anchor . AutoLeft :
2023-03-29 21:00:17 +02:00
case Anchor . AutoInline :
case Anchor . AutoInlineCenter :
case Anchor . AutoInlineBottom :
case Anchor . AutoInlineIgnoreOverflow :
case Anchor . AutoInlineCenterIgnoreOverflow :
case Anchor . AutoInlineBottomIgnoreOverflow :
2021-03-24 01:10:42 +01:00
pos . X = parentArea . X + this . ScaledOffset . X ;
pos . Y = parentArea . Y + this . ScaledOffset . Y ;
break ;
case Anchor . TopCenter :
case Anchor . AutoCenter :
2021-03-24 01:25:39 +01:00
pos . X = parentCenterX - newSize . X / 2 + this . ScaledOffset . X ;
2021-03-24 01:10:42 +01:00
pos . Y = parentArea . Y + this . ScaledOffset . Y ;
break ;
case Anchor . TopRight :
case Anchor . AutoRight :
2021-03-24 01:25:39 +01:00
pos . X = parentArea . Right - newSize . X - this . ScaledOffset . X ;
2021-03-24 01:10:42 +01:00
pos . Y = parentArea . Y + this . ScaledOffset . Y ;
break ;
case Anchor . CenterLeft :
pos . X = parentArea . X + this . ScaledOffset . X ;
2021-03-24 01:25:39 +01:00
pos . Y = parentCenterY - newSize . Y / 2 + this . ScaledOffset . Y ;
2021-03-24 01:10:42 +01:00
break ;
case Anchor . Center :
2021-03-24 01:25:39 +01:00
pos . X = parentCenterX - newSize . X / 2 + this . ScaledOffset . X ;
pos . Y = parentCenterY - newSize . Y / 2 + this . ScaledOffset . Y ;
2021-03-24 01:10:42 +01:00
break ;
case Anchor . CenterRight :
2021-03-24 01:25:39 +01:00
pos . X = parentArea . Right - newSize . X - this . ScaledOffset . X ;
pos . Y = parentCenterY - newSize . Y / 2 + this . ScaledOffset . Y ;
2021-03-24 01:10:42 +01:00
break ;
case Anchor . BottomLeft :
pos . X = parentArea . X + this . ScaledOffset . X ;
2021-03-24 01:25:39 +01:00
pos . Y = parentArea . Bottom - newSize . Y - this . ScaledOffset . Y ;
2021-03-24 01:10:42 +01:00
break ;
case Anchor . BottomCenter :
2021-03-24 01:25:39 +01:00
pos . X = parentCenterX - newSize . X / 2 + this . ScaledOffset . X ;
pos . Y = parentArea . Bottom - newSize . Y - this . ScaledOffset . Y ;
2021-03-24 01:10:42 +01:00
break ;
case Anchor . BottomRight :
2021-03-24 01:25:39 +01:00
pos . X = parentArea . Right - newSize . X - this . ScaledOffset . X ;
pos . Y = parentArea . Bottom - newSize . Y - this . ScaledOffset . Y ;
2021-03-24 01:10:42 +01:00
break ;
}
2022-07-27 11:19:40 +02:00
if ( this . Anchor . IsAuto ( ) ) {
2023-03-29 20:51:34 +02:00
if ( this . Anchor . IsInline ( ) ) {
var anchorEl = this . GetOlderSibling ( e = > ! e . IsHidden & & e . CanAutoAnchorsAttach ) ;
if ( anchorEl ! = null ) {
var anchorElArea = anchorEl . GetAreaForAutoAnchors ( ) ;
var newX = anchorElArea . Right + this . ScaledOffset . X ;
// with awkward ui scale values, floating point rounding can cause an element that would usually
// be positioned correctly to be pushed into the next line due to a very small deviation
if ( this . Anchor . IsIgnoreOverflow ( ) | | newX + newSize . X < = parentArea . Right + Element . Epsilon ) {
pos . X = newX ;
pos . Y = anchorElArea . Y + this . ScaledOffset . Y ;
if ( this . Anchor = = Anchor . AutoInlineCenter | | this . Anchor = = Anchor . AutoInlineCenterIgnoreOverflow ) {
pos . Y + = ( anchorElArea . Height - newSize . Y ) / 2 ;
} else if ( this . Anchor = = Anchor . AutoInlineBottom | | this . Anchor = = Anchor . AutoInlineBottomIgnoreOverflow ) {
pos . Y + = anchorElArea . Height - newSize . Y ;
2021-03-24 01:10:42 +01:00
}
2023-03-29 20:51:34 +02:00
} else {
2023-03-29 20:56:56 +02:00
// inline anchors that overflow into the next line act like AutoLeft
var newlineAnchorEl = this . GetLowestOlderSibling ( e = > ! e . IsHidden & & e . CanAutoAnchorsAttach ) ;
if ( newlineAnchorEl ! = null )
pos . Y = newlineAnchorEl . GetAreaForAutoAnchors ( ) . Bottom + this . ScaledOffset . Y ;
2023-03-29 20:51:34 +02:00
}
2021-03-24 01:10:42 +01:00
}
2023-03-29 20:51:34 +02:00
} else {
2023-03-29 21:00:17 +02:00
// auto anchors keep their x coordinates from the switch above
2023-03-29 20:51:34 +02:00
var anchorEl = this . GetLowestOlderSibling ( e = > ! e . IsHidden & & e . CanAutoAnchorsAttach ) ;
if ( anchorEl ! = null )
pos . Y = anchorEl . GetAreaForAutoAnchors ( ) . Bottom + this . ScaledOffset . Y ;
2020-09-23 00:41:24 +02:00
}
}
2021-03-24 01:10:42 +01:00
if ( this . PreventParentSpill ) {
if ( pos . X < parentArea . X )
pos . X = parentArea . X ;
if ( pos . Y < parentArea . Y )
pos . Y = parentArea . Y ;
2021-03-24 01:25:39 +01:00
if ( pos . X + newSize . X > parentArea . Right )
newSize . X = parentArea . Right - pos . X ;
if ( pos . Y + newSize . Y > parentArea . Bottom )
newSize . Y = parentArea . Bottom - pos . Y ;
2021-03-24 01:10:42 +01:00
}
2021-12-11 17:39:49 +01:00
this . SetAreaAndUpdateChildren ( new RectangleF ( pos , newSize ) ) ;
2021-03-29 06:41:38 +02:00
2021-04-26 19:21:11 +02:00
if ( this . SetWidthBasedOnChildren | | this . SetHeightBasedOnChildren ) {
2021-03-29 06:41:38 +02:00
Element foundChild = null ;
2021-03-29 08:28:49 +02:00
var autoSize = this . UnscrolledArea . Size ;
2021-06-09 00:27:50 +02:00
2021-03-29 06:41:38 +02:00
if ( this . SetHeightBasedOnChildren ) {
var lowest = this . GetLowestChild ( e = > ! e . IsHidden ) ;
2021-03-29 08:28:49 +02:00
if ( lowest ! = null ) {
2022-07-27 11:19:40 +02:00
if ( lowest . Anchor . IsTopAligned ( ) ) {
autoSize . Y = lowest . UnscrolledArea . Bottom - pos . Y + this . ScaledChildPadding . Bottom ;
} else {
autoSize . Y = lowest . UnscrolledArea . Height + this . ScaledChildPadding . Height ;
}
2021-04-26 19:06:54 +02:00
foundChild = lowest ;
2021-04-26 18:55:18 +02:00
} else {
autoSize . Y = 0 ;
2021-03-29 06:41:38 +02:00
}
}
2021-06-09 00:27:50 +02:00
2021-03-29 06:41:38 +02:00
if ( this . SetWidthBasedOnChildren ) {
var rightmost = this . GetRightmostChild ( e = > ! e . IsHidden ) ;
2021-03-29 08:28:49 +02:00
if ( rightmost ! = null ) {
2022-07-27 11:19:40 +02:00
if ( rightmost . Anchor . IsLeftAligned ( ) ) {
autoSize . X = rightmost . UnscrolledArea . Right - pos . X + this . ScaledChildPadding . Right ;
} else {
autoSize . X = rightmost . UnscrolledArea . Width + this . ScaledChildPadding . Width ;
}
2021-04-26 19:06:54 +02:00
foundChild = rightmost ;
2021-04-26 18:55:18 +02:00
} else {
autoSize . X = 0 ;
2021-03-29 06:41:38 +02:00
}
}
2021-06-09 00:27:50 +02:00
2021-06-25 16:48:41 +02:00
if ( this . TreatSizeAsMinimum ) {
2024-06-09 20:22:24 +02:00
autoSize = Vector2 . Max ( autoSize , intendedSize ) ;
2021-06-25 16:48:41 +02:00
} else if ( this . TreatSizeAsMaximum ) {
2024-06-09 20:22:24 +02:00
autoSize = Vector2 . Min ( autoSize , intendedSize ) ;
2021-06-25 16:48:41 +02:00
}
2021-09-24 16:35:53 +02:00
// we want to leave some leeway to prevent float rounding causing an infinite loop
2022-06-15 11:38:11 +02:00
if ( ! autoSize . Equals ( this . UnscrolledArea . Size , Element . Epsilon ) ) {
2021-03-29 06:41:38 +02:00
recursion + + ;
2024-06-09 20:29:23 +02:00
this . System . Metrics . SummedRecursionDepth + + ;
if ( recursion > this . System . Metrics . MaxRecursionDepth )
this . System . Metrics . MaxRecursionDepth = recursion ;
2023-02-20 11:01:15 +01:00
if ( recursion > = 64 )
throw new ArithmeticException ( $"The area of {this} has recursively updated too often. Does its child {foundChild} contain any conflicting auto-sizing settings?" ) ;
2022-07-27 11:19:40 +02:00
UpdateDisplayArea ( autoSize ) ;
2021-03-29 06:41:38 +02:00
}
}
2019-08-11 18:50:39 +02:00
}
2019-08-09 18:26:28 +02:00
}
2021-12-11 17:26:55 +01:00
/// <summary>
/// Sets this element's <see cref="Area"/> to the given <see cref="RectangleF"/> and invokes the <see cref="UiSystem.OnElementAreaUpdated"/> event.
/// This method also updates all of this element's <see cref="Children"/>'s areas.
/// Note that this method does not take into account any auto-sizing, anchoring or positioning, and so it should be used sparingly, if at all.
/// </summary>
/// <seealso cref="ForceUpdateArea"/>
2021-12-11 17:39:49 +01:00
public virtual void SetAreaAndUpdateChildren ( RectangleF area ) {
2021-12-11 17:26:55 +01:00
this . area = area ;
this . System . InvokeOnElementAreaUpdated ( this ) ;
foreach ( var child in this . Children )
child . ForceUpdateArea ( ) ;
2021-12-12 12:32:09 +01:00
this . System . Metrics . ActualAreaUpdates + + ;
2021-12-11 17:26:55 +01:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Calculates the actual size that this element should take up, based on the area that its parent encompasses.
2020-06-02 23:00:40 +02:00
/// By default, this is based on the information specified in <see cref="Size"/>'s documentation.
2020-05-22 17:02:24 +02:00
/// </summary>
/// <param name="parentArea">This parent's area, or the ui system's viewport if it has no parent</param>
/// <returns>The actual size of this element, taking <see cref="Scale"/> into account</returns>
2019-11-02 14:53:59 +01:00
protected virtual Vector2 CalcActualSize ( RectangleF parentArea ) {
2020-06-02 23:00:40 +02:00
var ret = new Vector2 (
2022-08-16 14:20:32 +02:00
this . size . X > 1 ? this . ScaledSize . X : parentArea . Width * this . size . X + this . ScaledAutoSizeAddedAbsolute . X ,
this . size . Y > 1 ? this . ScaledSize . Y : parentArea . Height * this . size . Y + this . ScaledAutoSizeAddedAbsolute . Y ) ;
2021-08-05 03:47:03 +02:00
if ( this . size . X < 0 )
2022-08-16 14:20:32 +02:00
ret . X = - this . size . X * ret . Y + this . ScaledAutoSizeAddedAbsolute . X ;
2021-08-05 03:47:03 +02:00
if ( this . size . Y < 0 )
2022-08-16 14:20:32 +02:00
ret . Y = - this . size . Y * ret . X + this . ScaledAutoSizeAddedAbsolute . Y ;
2020-06-02 23:00:40 +02:00
return ret ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns the area that should be used for determining where auto-anchoring children should attach.
/// </summary>
/// <returns>The area for auto anchors</returns>
2019-11-02 14:53:59 +01:00
protected virtual RectangleF GetAreaForAutoAnchors ( ) {
2019-08-24 22:27:47 +02:00
return this . UnscrolledArea ;
2019-08-18 17:49:52 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns this element's lowest child element (in terms of y position) that matches the given condition.
/// </summary>
/// <param name="condition">The condition to match</param>
2023-05-21 11:11:52 +02:00
/// <param name="total">Whether to evaluate based on the child's <see cref="GetTotalCoveredArea"/>, rather than its <see cref="UnscrolledArea"/>.</param>
2020-05-22 17:02:24 +02:00
/// <returns>The lowest element, or null if no such element exists</returns>
2023-05-21 11:11:52 +02:00
public Element GetLowestChild ( Func < Element , bool > condition = null , bool total = false ) {
2019-08-24 14:34:08 +02:00
Element lowest = null ;
2022-07-27 11:19:40 +02:00
var lowestX = float . MinValue ;
2019-11-05 21:33:45 +01:00
foreach ( var child in this . Children ) {
2019-09-12 12:39:18 +02:00
if ( condition ! = null & & ! condition ( child ) )
2019-08-24 14:34:08 +02:00
continue ;
2023-05-21 11:11:52 +02:00
var covered = total ? child . GetTotalCoveredArea ( true ) : child . UnscrolledArea ;
var x = ! child . Anchor . IsTopAligned ( ) ? covered . Height : covered . Bottom ;
2022-07-27 11:19:40 +02:00
if ( x > = lowestX ) {
2019-08-24 14:34:08 +02:00
lowest = child ;
2022-07-27 11:19:40 +02:00
lowestX = x ;
}
2019-08-24 14:34:08 +02:00
}
return lowest ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns this element's rightmost child (in terms of x position) that matches the given condition.
/// </summary>
/// <param name="condition">The condition to match</param>
2023-05-21 11:11:52 +02:00
/// <param name="total">Whether to evaluate based on the child's <see cref="GetTotalCoveredArea"/>, rather than its <see cref="UnscrolledArea"/>.</param>
2020-05-22 17:02:24 +02:00
/// <returns>The rightmost element, or null if no such element exists</returns>
2023-05-21 11:11:52 +02:00
public Element GetRightmostChild ( Func < Element , bool > condition = null , bool total = false ) {
2019-11-05 21:44:51 +01:00
Element rightmost = null ;
2022-07-27 11:19:40 +02:00
var rightmostX = float . MinValue ;
2019-11-05 21:44:51 +01:00
foreach ( var child in this . Children ) {
if ( condition ! = null & & ! condition ( child ) )
continue ;
2023-05-21 11:11:52 +02:00
var covered = total ? child . GetTotalCoveredArea ( true ) : child . UnscrolledArea ;
var x = ! child . Anchor . IsLeftAligned ( ) ? covered . Width : covered . Right ;
2022-07-27 11:52:28 +02:00
if ( x > = rightmostX ) {
2019-11-05 21:44:51 +01:00
rightmost = child ;
2022-07-27 11:19:40 +02:00
rightmostX = x ;
}
2019-11-05 21:44:51 +01:00
}
return rightmost ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns this element's lowest sibling that is also older (lower in its parent's <see cref="Children"/> list) than this element that also matches the given condition.
/// The returned element's <see cref="Parent"/> will always be equal to this element's <see cref="Parent"/>.
/// </summary>
/// <param name="condition">The condition to match</param>
2023-05-21 11:11:52 +02:00
/// <param name="total">Whether to evaluate based on the child's <see cref="GetTotalCoveredArea"/>, rather than its <see cref="UnscrolledArea"/>.</param>
2020-05-22 17:02:24 +02:00
/// <returns>The lowest older sibling of this element, or null if no such element exists</returns>
2023-05-21 11:11:52 +02:00
public Element GetLowestOlderSibling ( Func < Element , bool > condition = null , bool total = false ) {
2019-08-09 18:26:28 +02:00
if ( this . Parent = = null )
return null ;
2019-08-24 15:12:11 +02:00
Element lowest = null ;
2019-08-12 14:44:42 +02:00
foreach ( var child in this . Parent . Children ) {
2019-08-09 18:26:28 +02:00
if ( child = = this )
break ;
2019-09-12 12:39:18 +02:00
if ( condition ! = null & & ! condition ( child ) )
2019-08-24 15:12:11 +02:00
continue ;
2023-05-21 11:11:52 +02:00
if ( lowest = = null | | ( total ? child . GetTotalCoveredArea ( true ) : child . UnscrolledArea ) . Bottom > = lowest . UnscrolledArea . Bottom )
2019-08-24 15:12:11 +02:00
lowest = child ;
2019-08-09 18:26:28 +02:00
}
2019-08-24 15:12:11 +02:00
return lowest ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns this element's first older sibling that matches the given condition.
/// The returned element's <see cref="Parent"/> will always be equal to this element's <see cref="Parent"/>.
/// </summary>
/// <param name="condition">The condition to match</param>
/// <returns>The older sibling, or null if no such element exists</returns>
2019-09-12 12:39:18 +02:00
public Element GetOlderSibling ( Func < Element , bool > condition = null ) {
2019-08-24 15:20:00 +02:00
if ( this . Parent = = null )
return null ;
Element older = null ;
foreach ( var child in this . Parent . Children ) {
if ( child = = this )
break ;
2019-09-12 12:39:18 +02:00
if ( condition ! = null & & ! condition ( child ) )
2019-08-24 15:20:00 +02:00
continue ;
older = child ;
}
return older ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns all of this element's siblings that match the given condition.
/// Siblings are elements that have the same <see cref="Parent"/> as this element.
/// </summary>
/// <param name="condition">The condition to match</param>
/// <returns>This element's siblings</returns>
2019-09-12 12:39:18 +02:00
public IEnumerable < Element > GetSiblings ( Func < Element , bool > condition = null ) {
2019-08-13 21:23:20 +02:00
if ( this . Parent = = null )
yield break ;
foreach ( var child in this . Parent . Children ) {
2019-09-12 12:39:18 +02:00
if ( condition ! = null & & ! condition ( child ) )
2019-08-13 21:23:20 +02:00
continue ;
if ( child ! = this )
yield return child ;
}
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns all of this element's children of the given type that match the given condition.
/// Optionally, the entire tree of children (grandchildren) can be searched.
/// </summary>
/// <param name="condition">The condition to match</param>
/// <param name="regardGrandchildren">If this value is true, children of children of this element are also searched</param>
/// <param name="ignoreFalseGrandchildren">If this value is true, children for which the condition does not match will not have their children searched</param>
/// <typeparam name="T">The type of children to search for</typeparam>
/// <returns>All children that match the condition</returns>
2019-09-12 12:39:18 +02:00
public IEnumerable < T > GetChildren < T > ( Func < T , bool > condition = null , bool regardGrandchildren = false , bool ignoreFalseGrandchildren = false ) where T : Element {
2019-08-18 18:32:34 +02:00
foreach ( var child in this . Children ) {
2019-09-12 12:39:18 +02:00
var applies = child is T t & & ( condition = = null | | condition ( t ) ) ;
if ( applies )
yield return ( T ) child ;
if ( regardGrandchildren & & ( ! ignoreFalseGrandchildren | | applies ) ) {
foreach ( var cc in child . GetChildren ( condition , true , ignoreFalseGrandchildren ) )
2019-08-18 18:32:34 +02:00
yield return cc ;
}
}
}
2020-05-22 17:02:24 +02:00
/// <inheritdoc cref="GetChildren{T}"/>
2019-11-05 21:33:45 +01:00
public IEnumerable < Element > GetChildren ( Func < Element , bool > condition = null , bool regardGrandchildren = false , bool ignoreFalseGrandchildren = false ) {
return this . GetChildren < Element > ( condition , regardGrandchildren , ignoreFalseGrandchildren ) ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns the parent tree of this element.
/// The parent tree is this element's <see cref="Parent"/>, followed by its parent, and so on, up until the <see cref="RootElement"/>'s <see cref="RootElement.Element"/>.
/// </summary>
/// <returns>This element's parent tree</returns>
2019-08-21 17:00:22 +02:00
public IEnumerable < Element > GetParentTree ( ) {
if ( this . Parent = = null )
yield break ;
yield return this . Parent ;
foreach ( var parent in this . Parent . GetParentTree ( ) )
yield return parent ;
}
2023-05-21 11:11:52 +02:00
/// <summary>
/// Returns the total covered area of this element, which is its <see cref="Area"/> (or <see cref="UnscrolledArea"/>), unioned with all of the total covered areas of its <see cref="Children"/>.
/// The returned area is only different from this element's <see cref="Area"/> (or <see cref="UnscrolledArea"/>) if it has any <see cref="Children"/> that are outside of this element's area, or are bigger than this element.
/// </summary>
/// <param name="unscrolled">Whether to use elements' <see cref="UnscrolledArea"/> (instead of their <see cref="Area"/>).</param>
/// <returns>This element's total covered area.</returns>
public RectangleF GetTotalCoveredArea ( bool unscrolled ) {
var ret = unscrolled ? this . UnscrolledArea : this . Area ;
foreach ( var child in this . Children ) {
if ( ! child . IsHidden )
ret = RectangleF . Union ( ret , child . GetTotalCoveredArea ( unscrolled ) ) ;
}
return ret ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns a subset of <see cref="Children"/> that are currently relevant in terms of drawing and input querying.
/// A <see cref="Panel"/> only returns elements that are currently in view here.
/// </summary>
/// <returns>This element's relevant children</returns>
2021-03-24 01:39:41 +01:00
protected virtual IList < Element > GetRelevantChildren ( ) {
2019-09-20 13:48:49 +02:00
return this . SortedChildren ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
2022-05-04 13:54:15 +02:00
/// Updates this element and all of its <see cref="SortedChildren"/>
2020-05-22 17:02:24 +02:00
/// </summary>
/// <param name="time">The game's time</param>
2019-08-09 19:28:48 +02:00
public virtual void Update ( GameTime time ) {
2021-06-09 00:27:50 +02:00
this . System . InvokeOnElementUpdated ( this , time ) ;
2019-09-25 16:47:19 +02:00
2023-06-14 10:21:32 +02:00
for ( var i = this . PlayingAnimations . Count - 1 ; i > = 0 ; i - - ) {
var anim = this . PlayingAnimations [ i ] ;
if ( anim . Update ( this , time ) ) {
anim . OnFinished ( this ) ;
this . PlayingAnimations . RemoveAt ( i ) ;
}
}
2022-05-04 13:54:15 +02:00
// update all sorted children, not just relevant ones, because they might become relevant or irrelevant through updates
foreach ( var child in this . SortedChildren ) {
2020-02-06 01:51:41 +01:00
if ( child . System ! = null )
2019-12-05 14:59:53 +01:00
child . Update ( time ) ;
2021-12-14 13:42:31 +01:00
}
2021-12-12 12:32:09 +01:00
2021-12-14 13:42:31 +01:00
if ( this . System ! = null )
this . System . Metrics . Updates + + ;
2019-08-09 18:26:28 +02:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
2022-04-25 15:25:58 +02:00
/// Draws this element by calling <see cref="Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/> internally.
2024-07-17 18:21:13 +02:00
/// If <see cref="Transform"/> is set, a new <c>SpriteBatch.Begin</c> call is also started.
2022-04-25 15:25:58 +02:00
/// </summary>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param>
/// <param name="alpha">The alpha to draw this element and its children with</param>
/// <param name="context">The sprite batch context to use for drawing</param>
public void DrawTransformed ( GameTime time , SpriteBatch batch , float alpha , SpriteBatchContext context ) {
2024-07-17 18:21:13 +02:00
var customDraw = this . Transform ! = Matrix . Identity ;
2022-04-25 15:25:58 +02:00
var transformed = context ;
transformed . TransformMatrix = this . Transform * transformed . TransformMatrix ;
2022-01-30 01:13:59 +01:00
// TODO ending and beginning again when the matrix changes isn't ideal (https://github.com/MonoGame/MonoGame/issues/3156)
2020-07-15 23:21:52 +02:00
if ( customDraw ) {
// end the usual draw so that we can begin our own
batch . End ( ) ;
// begin our own draw call
2022-04-25 15:25:58 +02:00
batch . Begin ( transformed ) ;
2020-07-15 23:21:52 +02:00
}
2021-12-14 13:42:31 +01:00
2020-07-15 23:21:52 +02:00
// draw content in custom begin call
2024-07-17 18:21:13 +02:00
this . Draw ( time , batch , alpha , transformed ) ;
2021-12-14 13:42:31 +01:00
if ( this . System ! = null )
this . System . Metrics . Draws + + ;
2020-07-15 23:21:52 +02:00
if ( customDraw ) {
// end our draw
batch . End ( ) ;
// begin the usual draw again for other elements
2022-04-25 15:25:58 +02:00
batch . Begin ( context ) ;
2020-07-15 23:21:52 +02:00
}
}
2022-04-25 15:25:58 +02:00
/// <summary>
/// Draws this element and all of its children. Override this method to draw the content of custom elements.
2022-06-24 14:01:26 +02:00
/// Note that, when this is called, <c>SpriteBatch.Begin</c> has already been called with custom <see cref="Transform"/> etc. applied.
2022-04-25 15:25:58 +02:00
/// </summary>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch to use for drawing</param>
/// <param name="alpha">The alpha to draw this element and its children with</param>
/// <param name="context">The sprite batch context to use for drawing</param>
public virtual void Draw ( GameTime time , SpriteBatch batch , float alpha , SpriteBatchContext context ) {
2024-05-30 12:48:08 +02:00
this . System . InvokeOnElementDrawn ( this , time , batch , alpha , context ) ;
2020-05-27 15:19:17 +02:00
if ( this . IsSelected )
2024-05-30 12:48:08 +02:00
this . System . InvokeOnSelectedElementDrawn ( this , time , batch , alpha , context ) ;
2019-08-31 18:07:43 +02:00
2019-09-20 13:48:49 +02:00
foreach ( var child in this . GetRelevantChildren ( ) ) {
2019-08-09 19:39:51 +02:00
if ( ! child . IsHidden )
2024-07-17 18:21:13 +02:00
child . DrawTransformed ( time , batch , alpha * child . DrawAlpha , context ) ;
2019-08-09 19:39:51 +02:00
}
2019-08-09 19:28:48 +02:00
}
2019-08-09 18:26:28 +02:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// Returns the element under the given position, searching the current element and all of its <see cref="GetRelevantChildren"/>.
/// </summary>
/// <param name="position">The position to query</param>
/// <returns>The element under the position, or null if no such element exists</returns>
2019-11-02 14:53:59 +01:00
public virtual Element GetElementUnderPos ( Vector2 position ) {
2019-08-28 18:27:17 +02:00
if ( this . IsHidden )
2019-08-09 22:04:26 +02:00
return null ;
2021-11-22 15:13:08 +01:00
position = this . TransformInverse ( position ) ;
2021-03-24 01:39:41 +01:00
var relevant = this . GetRelevantChildren ( ) ;
for ( var i = relevant . Count - 1 ; i > = 0 ; i - - ) {
var element = relevant [ i ] . GetElementUnderPos ( position ) ;
2019-08-09 22:04:26 +02:00
if ( element ! = null )
return element ;
}
2019-09-26 19:36:15 +02:00
return this . CanBeMoused & & this . DisplayArea . Contains ( position ) ? this : null ;
2019-08-09 22:04:26 +02:00
}
2023-06-14 10:21:32 +02:00
/// <summary>
/// Plays the given <see cref="UiAnimation"/> on this element, causing it to be added to the <see cref="PlayingAnimations"/> and updated in <see cref="Update"/>.
/// If the given <paramref name="animation"/> is already playing on this element, it will be restarted.
/// </summary>
/// <param name="animation">The animation to play.</param>
public virtual void PlayAnimation ( UiAnimation animation ) {
if ( this . PlayingAnimations . Contains ( animation ) ) {
// if we're already playing this animation, just restart it
animation . OnFinished ( this ) ;
} else {
this . PlayingAnimations . Add ( animation ) ;
}
}
/// <summary>
/// Stops the given <see cref="UiAnimation"/> on this element, causing it to be removed from the <see cref="PlayingAnimations"/> and <see cref="UiAnimation.OnFinished"/> to be invoked.
/// </summary>
/// <param name="animation">The animation to stop.</param>
/// <returns>Whether the animation was present in this element's <see cref="PlayingAnimations"/>.</returns>
public virtual bool StopAnimation ( UiAnimation animation ) {
if ( this . PlayingAnimations . Remove ( animation ) ) {
animation . OnFinished ( this ) ;
return true ;
}
return false ;
}
2023-02-20 11:01:15 +01:00
/// <inheritdoc />
public override string ToString ( ) {
2023-12-16 21:37:49 +01:00
var ret = this . GetType ( ) . Name ;
// elements will contain their path up to the root and their index in each parent
// eg Paragraph 2 @ Panel 3 @ ... @ Group RootName
2023-02-20 11:01:15 +01:00
if ( this . Parent ! = null ) {
2023-12-16 21:37:49 +01:00
ret + = $" {this.Parent.Children.IndexOf(this)} @ {this.Parent}" ;
2023-02-20 11:01:15 +01:00
} else if ( this . Root ? . Element = = this ) {
2023-12-16 21:37:49 +01:00
ret + = $" {this.Root.Name}" ;
2023-02-20 11:01:15 +01:00
}
return ret ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Performs the specified action on this element and all of its <see cref="Children"/>
/// </summary>
/// <param name="action">The action to perform</param>
2019-09-02 18:41:05 +02:00
public void AndChildren ( Action < Element > action ) {
action ( this ) ;
foreach ( var child in this . Children )
child . AndChildren ( action ) ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Sorts this element's <see cref="Children"/> using the given comparison.
/// </summary>
/// <param name="comparison">The comparison to sort by</param>
2019-12-12 21:14:41 +01:00
public void ReorderChildren ( Comparison < Element > comparison ) {
2021-03-24 01:39:41 +01:00
this . children . Sort ( comparison ) ;
2019-12-12 21:14:41 +01:00
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Reverses this element's <see cref="Children"/> list in the given range.
/// </summary>
/// <param name="index">The index to start reversing at</param>
/// <param name="count">The amount of elements to reverse</param>
2019-12-19 12:52:31 +01:00
public void ReverseChildren ( int index = 0 , int? count = null ) {
2021-03-24 01:39:41 +01:00
this . children . Reverse ( index , count ? ? this . Children . Count ) ;
2019-12-19 12:52:31 +01:00
}
2020-07-15 23:21:52 +02:00
/// <summary>
/// Scales this element's <see cref="Transform"/> matrix based on the given scale and origin.
/// </summary>
/// <param name="scale">The scale to use</param>
/// <param name="origin">The origin to use for scaling, or null to use this element's center point</param>
public void ScaleTransform ( float scale , Vector2 ? origin = null ) {
this . Transform = Matrix . CreateScale ( scale , scale , 1 ) * Matrix . CreateTranslation ( new Vector3 ( ( 1 - scale ) * ( origin ? ? this . DisplayArea . Center ) , 0 ) ) ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// Initializes this element's <see cref="StyleProp{T}"/> instances using the ui system's <see cref="UiStyle"/>.
/// </summary>
/// <param name="style">The new style</param>
2019-08-10 21:37:10 +02:00
protected virtual void InitStyle ( UiStyle style ) {
2021-12-21 11:54:32 +01:00
this . SelectionIndicator = this . SelectionIndicator . OrStyle ( style . SelectionIndicator ) ;
this . ActionSound = this . ActionSound . OrStyle ( style . ActionSound ) ;
this . SecondActionSound = this . SecondActionSound . OrStyle ( style . ActionSound ) ;
2023-06-14 14:54:49 +02:00
this . MouseEnterAnimation = this . MouseEnterAnimation . OrStyle ( style . MouseEnterAnimation ) ;
this . MouseExitAnimation = this . MouseExitAnimation . OrStyle ( style . MouseExitAnimation ) ;
2022-01-22 23:34:52 +01:00
this . System ? . InvokeOnElementStyleInit ( this ) ;
2023-06-14 14:54:49 +02:00
style . ApplyCustomStyle ( this ) ;
2021-12-11 17:26:55 +01:00
}
/// <summary>
2022-01-09 01:12:16 +01:00
/// A method that gets called by this element's <see cref="Children"/> or any of its grandchildren when their <see cref="SetAreaDirty"/> methods get called.
2021-12-11 17:26:55 +01:00
/// Note that the element's area might already be dirty, which will not stop this method from being called.
/// </summary>
2022-01-09 01:12:16 +01:00
/// <param name="child">The child whose area is being set dirty.</param>
/// <param name="grandchild">Whether the <paramref name="child"/> is a grandchild of this element, rather than a direct child.</param>
protected virtual void OnChildAreaDirty ( Element child , bool grandchild ) {
if ( ! grandchild ) {
2022-08-04 20:14:29 +02:00
if ( child . Anchor . IsAuto ( ) | | child . PreventParentSpill | | this . SetWidthBasedOnChildren | | this . SetHeightBasedOnChildren )
2022-01-09 01:12:16 +01:00
this . SetAreaDirty ( ) ;
}
this . Parent ? . OnChildAreaDirty ( child , true ) ;
2019-08-10 21:37:10 +02:00
}
2021-11-22 15:13:08 +01:00
/// <summary>
/// Transforms the given <paramref name="position"/> by the inverse of this element's <see cref="Transform"/> matrix.
/// </summary>
/// <param name="position">The position to transform</param>
/// <returns>The transformed position</returns>
protected Vector2 TransformInverse ( Vector2 position ) {
return this . Transform ! = Matrix . Identity ? Vector2 . Transform ( position , Matrix . Invert ( this . Transform ) ) : position ;
}
/// <summary>
/// Transforms the given <paramref name="position"/> by this element's <see cref="Root"/>'s <see cref="RootElement.InvTransform"/>, the inverses of all of the <see cref="Transform"/> matrices of this element's parent tree (<see cref="GetParentTree"/>), and the inverse of this element's <see cref="Transform"/> matrix.
/// Note that, when using <see cref="UiControls.GetElementUnderPos"/>, this operation is done recursively, which is more efficient.
/// </summary>
/// <param name="position">The position to transform</param>
/// <returns>The transformed position</returns>
protected Vector2 TransformInverseAll ( Vector2 position ) {
position = Vector2 . Transform ( position , this . Root . InvTransform ) ;
foreach ( var parent in this . GetParentTree ( ) . Reverse ( ) )
position = parent . TransformInverse ( position ) ;
return this . TransformInverse ( position ) ;
}
2022-11-24 19:46:20 +01:00
/// <summary>
/// Called when this element is added to a <see cref="UiSystem"/> and, optionally, a given <see cref="RootElement"/>.
/// This method is called in <see cref="AddChild{T}"/> and <see cref="UiSystem.Add"/>.
/// </summary>
/// <param name="system">The ui system to add to.</param>
/// <param name="root">The root element to add to.</param>
protected internal virtual void AddedToUi ( UiSystem system , RootElement root ) {
this . Root = root ;
this . System = system ;
this . OnAddedToUi ? . Invoke ( this ) ;
root ? . InvokeOnElementAdded ( this ) ;
}
/// <summary>
/// Called when this element is removed from a <see cref="UiSystem"/> and <see cref="RootElement"/>.
/// This method is called in <see cref="RemoveChild"/> and <see cref="UiSystem.Remove"/>.
/// </summary>
protected internal virtual void RemovedFromUi ( ) {
var root = this . Root ;
this . Root = null ;
this . System = null ;
this . OnRemovedFromUi ? . Invoke ( this ) ;
root ? . InvokeOnElementRemoved ( this ) ;
}
2020-05-22 17:02:24 +02:00
/// <summary>
/// A delegate used for the <see cref="Element.OnTextInput"/> event.
/// </summary>
/// <param name="element">The current element</param>
/// <param name="key">The key that was pressed</param>
/// <param name="character">The character that was input</param>
2019-08-10 18:41:56 +02:00
public delegate void TextInputCallback ( Element element , Keys key , char character ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// A generic element-specific delegate.
/// </summary>
/// <param name="element">The current element</param>
2019-08-10 18:41:56 +02:00
public delegate void GenericCallback ( Element element ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// A generic element-specific delegate that includes a second element.
/// </summary>
/// <param name="thisElement">The current element</param>
/// <param name="otherElement">The other element</param>
2019-08-28 18:58:05 +02:00
public delegate void OtherElementCallback ( Element thisElement , Element otherElement ) ;
2019-08-11 18:02:21 +02:00
2020-05-22 17:02:24 +02:00
/// <summary>
2022-04-25 15:25:58 +02:00
/// A delegate used inside of <see cref="Element.Draw(Microsoft.Xna.Framework.GameTime,Microsoft.Xna.Framework.Graphics.SpriteBatch,float,MLEM.Graphics.SpriteBatchContext)"/>
2020-05-22 17:02:24 +02:00
/// </summary>
/// <param name="element">The element that is being drawn</param>
/// <param name="time">The game's time</param>
/// <param name="batch">The sprite batch used for drawing</param>
/// <param name="alpha">The alpha this element is drawn with</param>
2024-05-30 13:24:35 +02:00
/// <param name="context">The sprite batch context to use for drawing</param>
2024-05-30 12:48:08 +02:00
public delegate void DrawCallback ( Element element , GameTime time , SpriteBatch batch , float alpha , SpriteBatchContext context ) ;
2019-08-31 18:07:43 +02:00
2020-05-22 17:02:24 +02:00
/// <summary>
/// A generic delegate used inside of <see cref="Element.Update"/>
/// </summary>
/// <param name="element">The current element</param>
/// <param name="time">The game's time</param>
2019-09-25 16:47:19 +02:00
public delegate void TimeCallback ( Element element , GameTime time ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// A delegate used by <see cref="Element.GetTabNextElement"/>.
/// </summary>
/// <param name="backward">If this value is true, <see cref="ModifierKey.Shift"/> is being held</param>
/// <param name="usualNext">The element that is considered to be the next element by default</param>
2019-09-11 10:49:51 +02:00
public delegate Element TabNextElementCallback ( bool backward , Element usualNext ) ;
2020-05-22 17:02:24 +02:00
/// <summary>
/// A delegate used by <see cref="Element.GetGamepadNextElement"/>.
/// </summary>
/// <param name="dir">The direction of the gamepad button that was pressed</param>
/// <param name="usualNext">The element that is considered to be the next element by default</param>
2019-09-11 10:49:51 +02:00
public delegate Element GamepadNextElementCallback ( Direction2 dir , Element usualNext ) ;
2019-08-09 18:26:28 +02:00
}
2022-06-17 18:23:47 +02:00
}