Files
FlaxEngine/Source/Engine/UI/GUI/Control.Bounds.cs
2020-12-07 23:40:54 +01:00

525 lines
21 KiB
C#

// Copyright (c) 2012-2020 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 normalized position in the parent control that the upper left corner is anchored to (range 0-1).
/// </summary>
[Serialize]
[ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(990), Tooltip("The normalized position in the parent control that the upper left corner is anchored to (range 0-1).")]
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]
[ExpandGroups, Limit(0.0f, 1.0f, 0.01f), EditorDisplay("Transform"), EditorOrder(991), Tooltip("The normalized position in the parent control that the bottom right corner is anchored to (range 0-1).")]
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]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(992), Tooltip("The offsets of the corners of the control relative to its anchors.")]
public Margin Offsets
{
get => _offsets;
set
{
if (!_offsets.Equals(ref value))
{
_offsets = value;
UpdateBounds();
}
}
}
/// <summary>
/// Gets or sets coordinates of the upper-left corner of the control relative to the upper-left corner of its container.
/// </summary>
[NoSerialize]
[ExpandGroups, EditorDisplay("Transform"), EditorOrder(1000), Tooltip("The location of the upper-left corner of the control relative to he upper-left corner of its container.")]
public Vector2 Location
{
get => _bounds.Location;
set => Bounds = new Rectangle(value, _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]
[EditorDisplay("Transform"), EditorOrder(1010), Tooltip("The size of the control bounds.")]
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>
public Vector2 Center => _bounds.Center;
/// <summary>
/// Gets or sets control's bounds rectangle.
/// </summary>
[HideInEditor, NoSerialize]
public Rectangle Bounds
{
get => _bounds;
set
{
if (!_bounds.Equals(ref 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.
/// </summary>
[EditorDisplay("Transform"), Limit(float.MinValue, float.MaxValue, 0.1f), EditorOrder(1020), Tooltip("The control scale parameter.")]
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>
[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.
/// </summary>
[EditorDisplay("Transform"), EditorOrder(1040), Tooltip("The shear transform angles (x, y). Defined in degrees.")]
public Vector2 Shear
{
get => _shear;
set
{
if (!_shear.Equals(ref value))
{
SetShearInternal(ref value);
}
}
}
/// <summary>
/// Gets or sets the rotation angle (in degrees).
/// </summary>
[EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees).")]
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 v1, v2;
Vector2.Multiply(ref _pivot, ref _bounds.Size, out v1);
Vector2.Negate(ref v1, out 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 m1, m2;
Matrix2x2.Scale(ref _scale, out m1);
Matrix2x2.Shear(ref _shear, out 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 m3;
Matrix3x3.Translation2D(ref v2, out 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>
public void SetAnchorPreset(AnchorPresets anchorPreset, bool preserveBounds)
{
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;
}
Bounds = bounds;
}
return;
}
}
}
}
}