Files
FlaxEngine/Source/Engine/UI/GUI/Control.Bounds.cs
2021-10-08 16:25:15 +02:00

603 lines
23 KiB
C#

// Copyright (c) 2012-2021 Wojciech Figat. All rights reserved.
using System;
namespace FlaxEngine.GUI
{
public partial class Control
{
/// <summary>
/// Gets or sets X coordinate of the upper-left corner of the control relative to the upper-left corner of its container.
/// </summary>
[HideInEditor, NoSerialize]
public float X
{
get => _bounds.X;
set => Bounds = new Rectangle(value, Y, _bounds.Size);
}
/// <summary>
/// Gets or sets Y coordinate of the upper-left corner of the control relative to the upper-left corner of its container.
/// </summary>
[HideInEditor, NoSerialize]
public float Y
{
get => _bounds.Y;
set => Bounds = new Rectangle(X, value, _bounds.Size);
}
/// <summary>
/// Gets or sets the local X coordinate of the pivot of the control relative to the anchor in parent of its container.
/// </summary>
[HideInEditor, NoSerialize]
public float LocalX
{
get => LocalLocation.X;
set => LocalLocation = new Vector2(value, LocalLocation.Y);
}
/// <summary>
/// Gets or sets the local Y coordinate of the pivot of the control relative to the anchor in parent of its container.
/// </summary>
[HideInEditor, NoSerialize]
public float LocalY
{
get => LocalLocation.Y;
set => LocalLocation = new Vector2(LocalLocation.X, value);
}
/// <summary>
/// Gets or sets the normalized position in the parent control that the upper left corner is anchored to (range 0-1).
/// </summary>
[Serialize, HideInEditor]
public Vector2 AnchorMin
{
get => _anchorMin;
set
{
if (!_anchorMin.Equals(ref value))
{
var bounds = Bounds;
_anchorMin = value;
UpdateBounds();
Bounds = bounds;
}
}
}
/// <summary>
/// Gets or sets the normalized position in the parent control that the bottom right corner is anchored to (range 0-1).
/// </summary>
[Serialize, HideInEditor]
public Vector2 AnchorMax
{
get => _anchorMax;
set
{
if (!_anchorMax.Equals(ref value))
{
var bounds = Bounds;
_anchorMax = value;
UpdateBounds();
Bounds = bounds;
}
}
}
/// <summary>
/// Gets or sets the offsets of the corners of the control relative to its anchors.
/// </summary>
[Serialize, HideInEditor]
public Margin Offsets
{
get => _offsets;
set
{
if (!_offsets.Equals(ref value))
{
_offsets = value;
UpdateBounds();
}
}
}
#if FLAX_EDITOR
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Left
{
get => _offsets.Left;
set => Offsets = new Margin(value, _offsets.Right, _offsets.Top, _offsets.Bottom);
}
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Right
{
get => _offsets.Right;
set => Offsets = new Margin(_offsets.Left, value, _offsets.Top, _offsets.Bottom);
}
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Top
{
get => _offsets.Top;
set => Offsets = new Margin(_offsets.Left, _offsets.Right, value, _offsets.Bottom);
}
/// <summary>
/// Helper for Editor UI (see UIControlControlEditor).
/// </summary>
[NoSerialize, HideInEditor]
internal float Proxy_Offset_Bottom
{
get => _offsets.Bottom;
set => Offsets = new Margin(_offsets.Left, _offsets.Right, _offsets.Top, value);
}
#endif
/// <summary>
/// Gets or sets coordinates of the upper-left corner of the control relative to the upper-left corner of its container.
/// </summary>
[NoSerialize, HideInEditor]
public Vector2 Location
{
get => _bounds.Location;
set => Bounds = new Rectangle(value, _bounds.Size);
}
/// <summary>
/// Gets or sets the local position of the pivot of the control relative to the anchor in parent of its container.
/// </summary>
[NoSerialize, HideInEditor]
public Vector2 LocalLocation
{
get => _bounds.Location - (_parent != null ? _parent._bounds.Size * (_anchorMax + _anchorMin) * 0.5f : Vector2.Zero) + _bounds.Size * _pivot;
set => Bounds = new Rectangle(value + (_parent != null ? _parent.Bounds.Size * (_anchorMax + _anchorMin) * 0.5f : Vector2.Zero) - _bounds.Size * _pivot, _bounds.Size);
}
/// <summary>
/// Gets or sets width of the control.
/// </summary>
[NoSerialize, HideInEditor]
public float Width
{
get => _bounds.Width;
set => Bounds = new Rectangle(_bounds.Location, value, _bounds.Height);
}
/// <summary>
/// Gets or sets height of the control.
/// </summary>
[NoSerialize, HideInEditor]
public float Height
{
get => _bounds.Height;
set => Bounds = new Rectangle(_bounds.Location, _bounds.Width, value);
}
/// <summary>
/// Gets or sets control's size.
/// </summary>
[NoSerialize, HideInEditor]
public Vector2 Size
{
get => _bounds.Size;
set => Bounds = new Rectangle(_bounds.Location, value);
}
/// <summary>
/// Gets Y coordinate of the top edge of the control relative to the upper-left corner of its container.
/// </summary>
public float Top => _bounds.Top;
/// <summary>
/// Gets Y coordinate of the bottom edge of the control relative to the upper-left corner of its container.
/// </summary>
public float Bottom => _bounds.Bottom;
/// <summary>
/// Gets X coordinate of the left edge of the control relative to the upper-left corner of its container.
/// </summary>
public float Left => _bounds.Left;
/// <summary>
/// Gets X coordinate of the right edge of the control relative to the upper-left corner of its container.
/// </summary>
public float Right => _bounds.Right;
/// <summary>
/// Gets position of the upper left corner of the control relative to the upper-left corner of its container.
/// </summary>
public Vector2 UpperLeft => _bounds.UpperLeft;
/// <summary>
/// Gets position of the upper right corner of the control relative to the upper-left corner of its container.
/// </summary>
public Vector2 UpperRight => _bounds.UpperRight;
/// <summary>
/// Gets position of the bottom right corner of the control relative to the upper-left corner of its container.
/// </summary>
public Vector2 BottomRight => _bounds.BottomRight;
/// <summary>
/// Gets position of the bottom left of the control relative to the upper-left corner of its container.
/// </summary>
public Vector2 BottomLeft => _bounds.BottomLeft;
/// <summary>
/// Gets center position of the control relative to the upper-left corner of its container.
/// </summary>
[HideInEditor, NoSerialize]
public Vector2 Center
{
get => _bounds.Center;
set => Location = value - Size * 0.5f;
}
/// <summary>
/// Gets or sets control's bounds rectangle.
/// </summary>
[HideInEditor, NoSerialize]
public Rectangle Bounds
{
get => _bounds;
set
{
if (!_bounds.Equals(ref value))
SetBounds(ref value);
}
}
private void SetBounds(ref Rectangle value)
{
// Calculate anchors based on the parent container client area
Margin anchors;
if (_parent != null)
{
_parent.GetDesireClientArea(out var parentBounds);
anchors = new Margin
(
_anchorMin.X * parentBounds.Size.X + parentBounds.Location.X,
_anchorMax.X * parentBounds.Size.X,
_anchorMin.Y * parentBounds.Size.Y + parentBounds.Location.Y,
_anchorMax.Y * parentBounds.Size.Y
);
}
else
{
anchors = Margin.Zero;
}
// Calculate offsets on X axis
_offsets.Left = value.Location.X - anchors.Left;
if (_anchorMin.X != _anchorMax.X)
{
_offsets.Right = anchors.Right - value.Location.X - value.Size.X;
}
else
{
_offsets.Right = value.Size.X;
}
// Calculate offsets on Y axis
_offsets.Top = value.Location.Y - anchors.Top;
if (_anchorMin.Y != _anchorMax.Y)
{
_offsets.Bottom = anchors.Bottom - value.Location.Y - value.Size.Y;
}
else
{
_offsets.Bottom = value.Size.Y;
}
// Flush the control bounds
UpdateBounds();
}
/// <summary>
/// Gets or sets the scale. Scales control according to its Pivot which by default is (0.5,0.5) (middle of the control). If you set pivot to (0,0) it will scale the control based on it's upper-left corner.
/// </summary>
[ExpandGroups, EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter. Scales control according to its Pivot which by default is (0.5,0.5) (middle of the control). If you set pivot to (0,0) it will scale the control based on it's upper-left corner.")]
public Vector2 Scale
{
get => _scale;
set
{
if (!_scale.Equals(ref value))
{
SetScaleInternal(ref value);
}
}
}
/// <summary>
/// Gets or sets the normalized pivot location (used to transform control around it). Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner.
/// </summary>
[ExpandGroups, EditorDisplay("Transform"), Limit(0.0f, 1.0f, 0.1f), EditorOrder(1030), Tooltip("The control rotation pivot location in normalized control size. Point (0,0) is upper left corner, (0.5,0.5) is center, (1,1) is bottom right corner.")]
public Vector2 Pivot
{
get => _pivot;
set
{
if (!_pivot.Equals(ref value))
{
SetPivotInternal(ref value);
}
}
}
/// <summary>
/// Gets or sets the shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.
/// </summary>
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees. Shearing happens relative to the control pivot point.")]
public Vector2 Shear
{
get => _shear;
set
{
if (!_shear.Equals(ref value))
{
SetShearInternal(ref value);
}
}
}
/// <summary>
/// Gets or sets the rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).
/// </summary>
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees). Control is rotated around it's pivot point (middle of the control by default).")]
public float Rotation
{
get => _rotation;
set
{
if (!Mathf.NearEqual(_rotation, value))
{
SetRotationInternal(value);
}
}
}
/// <summary>
/// Updates the control cached bounds (based on anchors and offsets).
/// </summary>
[NoAnimate]
public void UpdateBounds()
{
var prevBounds = _bounds;
// Calculate anchors based on the parent container client area
Margin anchors;
Vector2 offset;
if (_parent != null)
{
_parent.GetDesireClientArea(out var parentBounds);
anchors = new Margin
(
_anchorMin.X * parentBounds.Size.X,
_anchorMax.X * parentBounds.Size.X,
_anchorMin.Y * parentBounds.Size.Y,
_anchorMax.Y * parentBounds.Size.Y
);
offset = parentBounds.Location;
}
else
{
anchors = Margin.Zero;
offset = Vector2.Zero;
}
// Calculate position and size on X axis
_bounds.Location.X = anchors.Left + _offsets.Left;
if (_anchorMin.X != _anchorMax.X)
{
_bounds.Size.X = anchors.Right - _bounds.Location.X - _offsets.Right;
}
else
{
_bounds.Size.X = _offsets.Right;
}
// Calculate position and size on Y axis
_bounds.Location.Y = anchors.Top + _offsets.Top;
if (_anchorMin.Y != _anchorMax.Y)
{
_bounds.Size.Y = anchors.Bottom - _bounds.Location.Y - _offsets.Bottom;
}
else
{
_bounds.Size.Y = _offsets.Bottom;
}
// Apply the offset
_bounds.Location += offset;
// Update cached transformation matrix
UpdateTransform();
// Handle location/size changes
if (_bounds.Location != prevBounds.Location)
{
OnLocationChanged();
}
if (_bounds.Size != prevBounds.Size)
{
OnSizeChanged();
}
}
/// <summary>
/// Updates the control cached transformation matrix (based on bounds).
/// </summary>
[NoAnimate]
public void UpdateTransform()
{
// Actual pivot and negative pivot
Vector2.Multiply(ref _pivot, ref _bounds.Size, out Vector2 v1);
Vector2.Negate(ref v1, out Vector2 v2);
Vector2.Add(ref v1, ref _bounds.Location, out v1);
// ------ Matrix3x3 based version:
/*
// Negative pivot
Matrix3x3 m1, m2;
Matrix3x3.Translation2D(ref v2, out m1);
// Scale
Matrix3x3.Scaling(_scale.X, _scale.Y, 1, out m2);
Matrix3x3.Multiply(ref m1, ref m2, out m1);
// Shear
Matrix3x3.Shear(ref _shear, out m2);
Matrix3x3.Multiply(ref m1, ref m2, out m1);
// Rotation
Matrix3x3.RotationZ(_rotation * Mathf.DegreesToRadians, out m2);
Matrix3x3.Multiply(ref m1, ref m2, out m1);
// Pivot + Location
Matrix3x3.Translation2D(ref v1, out m2);
Matrix3x3.Multiply(ref m1, ref m2, out _cachedTransform);
*/
// ------ Matrix2x2 based version:
// 2D transformation
Matrix2x2.Scale(ref _scale, out Matrix2x2 m1);
Matrix2x2.Shear(ref _shear, out Matrix2x2 m2);
Matrix2x2.Multiply(ref m1, ref m2, out m1);
Matrix2x2.Rotation(Mathf.DegreesToRadians * _rotation, out m2);
Matrix2x2.Multiply(ref m1, ref m2, out m1);
// Mix all the stuff
Matrix3x3.Translation2D(ref v2, out Matrix3x3 m3);
Matrix3x3 m4 = (Matrix3x3)m1;
Matrix3x3.Multiply(ref m3, ref m4, out m3);
Matrix3x3.Translation2D(ref v1, out m4);
Matrix3x3.Multiply(ref m3, ref m4, out _cachedTransform);
// Cache inverted transform
Matrix3x3.Invert(ref _cachedTransform, out _cachedTransformInv);
}
/// <summary>
/// Sets the anchor preset for the control. Can be use to auto-place the control for a given preset or can preserve the current control bounds.
/// </summary>
/// <param name="anchorPreset">The anchor preset to set.</param>
/// <param name="preserveBounds">True if preserve current control bounds, otherwise will align control position accordingly to the anchor location.</param>
/// <param name="setPivotToo">Whether or not we should set the pivot too, eg left-top 0,0, bottom-right 1,1</param>
public void SetAnchorPreset(AnchorPresets anchorPreset, bool preserveBounds, bool setPivotToo = false)
{
for (int i = 0; i < AnchorPresetsData.Length; i++)
{
if (AnchorPresetsData[i].Preset == anchorPreset)
{
var anchorMin = AnchorPresetsData[i].Min;
var anchorMax = AnchorPresetsData[i].Max;
var bounds = _bounds;
if (!Vector2.NearEqual(ref _anchorMin, ref anchorMin) ||
!Vector2.NearEqual(ref _anchorMax, ref anchorMax))
{
// Disable scrolling for anchored controls (by default but can be manually restored)
if (!anchorMin.IsZero || !anchorMax.IsZero)
IsScrollable = false;
_anchorMin = anchorMin;
_anchorMax = anchorMax;
if (preserveBounds)
{
UpdateBounds();
Bounds = bounds;
}
}
if (!preserveBounds)
{
if (_parent != null)
{
_parent.GetDesireClientArea(out var parentBounds);
switch (anchorPreset)
{
case AnchorPresets.TopLeft:
bounds.Location = Vector2.Zero;
break;
case AnchorPresets.TopCenter:
bounds.Location = new Vector2(parentBounds.Width * 0.5f - bounds.Width * 0.5f, 0);
break;
case AnchorPresets.TopRight:
bounds.Location = new Vector2(parentBounds.Width - bounds.Width, 0);
break;
case AnchorPresets.MiddleLeft:
bounds.Location = new Vector2(0, parentBounds.Height * 0.5f - bounds.Height * 0.5f);
break;
case AnchorPresets.MiddleCenter:
bounds.Location = new Vector2(parentBounds.Width * 0.5f - bounds.Width * 0.5f, parentBounds.Height * 0.5f - bounds.Height * 0.5f);
break;
case AnchorPresets.MiddleRight:
bounds.Location = new Vector2(parentBounds.Width - bounds.Width, parentBounds.Height * 0.5f - bounds.Height * 0.5f);
break;
case AnchorPresets.BottomLeft:
bounds.Location = new Vector2(0, parentBounds.Height - bounds.Height);
break;
case AnchorPresets.BottomCenter:
bounds.Location = new Vector2(parentBounds.Width * 0.5f - bounds.Width * 0.5f, parentBounds.Height - bounds.Height);
break;
case AnchorPresets.BottomRight:
bounds.Location = new Vector2(parentBounds.Width - bounds.Width, parentBounds.Height - bounds.Height);
break;
case AnchorPresets.VerticalStretchLeft:
bounds.Location = Vector2.Zero;
bounds.Size = new Vector2(bounds.Width, parentBounds.Height);
break;
case AnchorPresets.VerticalStretchCenter:
bounds.Location = new Vector2(parentBounds.Width * 0.5f - bounds.Width * 0.5f, 0);
bounds.Size = new Vector2(bounds.Width, parentBounds.Height);
break;
case AnchorPresets.VerticalStretchRight:
bounds.Location = new Vector2(parentBounds.Width - bounds.Width, 0);
bounds.Size = new Vector2(bounds.Width, parentBounds.Height);
break;
case AnchorPresets.HorizontalStretchTop:
bounds.Location = Vector2.Zero;
bounds.Size = new Vector2(parentBounds.Width, bounds.Height);
break;
case AnchorPresets.HorizontalStretchMiddle:
bounds.Location = new Vector2(0, parentBounds.Height * 0.5f - bounds.Height * 0.5f);
bounds.Size = new Vector2(parentBounds.Width, bounds.Height);
break;
case AnchorPresets.HorizontalStretchBottom:
bounds.Location = new Vector2(0, parentBounds.Height - bounds.Height);
bounds.Size = new Vector2(parentBounds.Width, bounds.Height);
break;
case AnchorPresets.StretchAll:
bounds.Location = Vector2.Zero;
bounds.Size = parentBounds.Size;
break;
default: throw new ArgumentOutOfRangeException(nameof(anchorPreset), anchorPreset, null);
}
bounds.Location += parentBounds.Location;
}
SetBounds(ref bounds);
}
if (setPivotToo)
{
Pivot = (anchorMin + anchorMax) / 2;
}
_parent?.PerformLayout();
return;
}
}
}
}
}