// Copyright (c) 2012-2020 Wojciech Figat. All rights reserved. using System; namespace FlaxEngine.GUI { public partial class Control { /// /// Gets or sets X coordinate of the upper-left corner of the control relative to the upper-left corner of its container. /// [HideInEditor, NoSerialize] public float X { get => _bounds.X; set => Bounds = new Rectangle(value, Y, _bounds.Size); } /// /// Gets or sets Y coordinate of the upper-left corner of the control relative to the upper-left corner of its container. /// [HideInEditor, NoSerialize] public float Y { get => _bounds.Y; set => Bounds = new Rectangle(X, value, _bounds.Size); } /// /// Gets or sets the normalized position in the parent control that the upper left corner is anchored to (range 0-1). /// [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; } } } /// /// Gets or sets the normalized position in the parent control that the bottom right corner is anchored to (range 0-1). /// [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; } } } /// /// Gets or sets the offsets of the corners of the control relative to its anchors. /// [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(); } } } /// /// Gets or sets coordinates of the upper-left corner of the control relative to the upper-left corner of its container. /// [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); } /// /// Gets or sets width of the control. /// [NoSerialize, HideInEditor] public float Width { get => _bounds.Width; set => Bounds = new Rectangle(_bounds.Location, value, _bounds.Height); } /// /// Gets or sets height of the control. /// [NoSerialize, HideInEditor] public float Height { get => _bounds.Height; set => Bounds = new Rectangle(_bounds.Location, _bounds.Width, value); } /// /// Gets or sets control's size. /// [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); } /// /// Gets Y coordinate of the top edge of the control relative to the upper-left corner of its container. /// public float Top => _bounds.Top; /// /// Gets Y coordinate of the bottom edge of the control relative to the upper-left corner of its container. /// public float Bottom => _bounds.Bottom; /// /// Gets X coordinate of the left edge of the control relative to the upper-left corner of its container. /// public float Left => _bounds.Left; /// /// Gets X coordinate of the right edge of the control relative to the upper-left corner of its container. /// public float Right => _bounds.Right; /// /// Gets position of the upper left corner of the control relative to the upper-left corner of its container. /// public Vector2 UpperLeft => _bounds.UpperLeft; /// /// Gets position of the upper right corner of the control relative to the upper-left corner of its container. /// public Vector2 UpperRight => _bounds.UpperRight; /// /// Gets position of the bottom right corner of the control relative to the upper-left corner of its container. /// public Vector2 BottomRight => _bounds.BottomRight; /// /// Gets position of the bottom left of the control relative to the upper-left corner of its container. /// public Vector2 BottomLeft => _bounds.BottomLeft; /// /// Gets center position of the control relative to the upper-left corner of its container. /// public Vector2 Center => _bounds.Center; /// /// Gets or sets control's bounds rectangle. /// [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(); } } } /// /// Gets or sets the scale. /// [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); } } } /// /// 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. /// [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); } } } /// /// Gets or sets the shear transform angles (x, y). Defined in degrees. /// [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); } } } /// /// Gets or sets the rotation angle (in degrees). /// [EditorDisplay("Transform"), EditorOrder(1050), Tooltip("The control rotation angle (in degrees).")] public float Rotation { get => _rotation; set { if (!Mathf.NearEqual(_rotation, value)) { SetRotationInternal(value); } } } /// /// Updates the control cached bounds (based on anchors and offsets). /// [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(); } } /// /// Updates the control cached transformation matrix (based on bounds). /// [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); } /// /// 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. /// /// The anchor preset to set. /// True if preserve current control bounds, otherwise will align control position accordingly to the anchor location. 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; } } } } }