// Copyright (c) 2012-2024 Wojciech Figat. All rights reserved. using FlaxEditor.CustomEditors.Elements; using FlaxEngine; using FlaxEngine.GUI; namespace FlaxEditor.CustomEditors.GUI { /// /// properties list control. /// /// [HideInEditor] public class PropertiesList : PanelWithMargins { // TODO: sync splitter for whole presenter /// /// The splitter size (in pixels). /// public const int SplitterSize = 2; /// /// The splitter margin (in pixels). /// public const int SplitterMargin = 4; private const int SplitterSizeHalf = SplitterSize / 2; private PropertiesListElement _element; private float _splitterValue; private Rectangle _splitterRect; private bool _splitterClicked, _mouseOverSplitter; private bool _cursorChanged; /// /// Gets or sets the splitter value (always in range [0; 1]). /// /// /// The splitter value (always in range [0; 1]). /// public float SplitterValue { get => _splitterValue; set { value = Mathf.Clamp(value, 0.05f, 0.95f); if (!Mathf.NearEqual(_splitterValue, value)) { _splitterValue = value; UpdateSplitRect(); PerformLayout(true); } } } /// /// Gets the properties list element. It's a parent object for this control. /// public PropertiesListElement Element => _element; /// /// Initializes a new instance of the class. /// /// The element. public PropertiesList(PropertiesListElement element) { _element = element; _splitterValue = 0.4f; BottomMargin = TopMargin = RightMargin = SplitterMargin; UpdateSplitRect(); } private void UpdateSplitRect() { _splitterRect = new Rectangle(Mathf.Clamp(_splitterValue * Width - SplitterSizeHalf, 0.0f, Width), 0, SplitterSize, Height); LeftMargin = _splitterValue * Width + SplitterMargin; } private void StartTracking() { // Start move _splitterClicked = true; // Start capturing mouse StartMouseCapture(); } private void EndTracking() { if (_splitterClicked) { // Clear flag _splitterClicked = false; // End capturing mouse EndMouseCapture(); } } /// public override void Draw() { base.Draw(); var style = Style.Current; // Draw splitter Render2D.FillRectangle(_splitterRect, _splitterClicked ? style.BackgroundSelected : _mouseOverSplitter ? style.BackgroundHighlighted : style.Background * 0.8f); } /// public override void OnLostFocus() { EndTracking(); base.OnLostFocus(); } /// public override void OnMouseMove(Float2 location) { _mouseOverSplitter = _splitterRect.Contains(location); if (_splitterClicked) { SplitterValue = location.X / Width; Cursor = CursorType.SizeWE; _cursorChanged = true; } else if (_mouseOverSplitter) { Cursor = CursorType.SizeWE; _cursorChanged = true; } else if (_cursorChanged) { Cursor = CursorType.Default; _cursorChanged = false; } base.OnMouseMove(location); } /// public override bool OnMouseDown(Float2 location, MouseButton button) { if (button == MouseButton.Left) { if (_splitterRect.Contains(location)) { // Start moving splitter StartTracking(); return false; } } return base.OnMouseDown(location, button); } /// public override bool OnMouseUp(Float2 location, MouseButton button) { if (_splitterClicked) { EndTracking(); return true; } return base.OnMouseUp(location, button); } /// public override void OnMouseLeave() { // Clear flag _mouseOverSplitter = false; if (_cursorChanged) { Cursor = CursorType.Default; _cursorChanged = false; } base.OnMouseLeave(); } /// public override void OnEndMouseCapture() { // Clear flag _splitterClicked = false; } /// protected override void OnSizeChanged() { base.OnSizeChanged(); // Refresh UpdateSplitRect(); PerformLayout(true); } /// protected override void PerformLayoutBeforeChildren() { base.PerformLayoutBeforeChildren(); // Pre-set width of all controls float w = Width - _margin.Width; for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; if (!(c is PropertyNameLabel)) { c.Width = w; } } } /// protected override void PerformLayoutAfterChildren() { // Sort controls from up to down into two columns: one for labels and one for the rest of the stuff float y = _margin.Top; float w = Width - _margin.Width; for (int i = 0; i < _children.Count; i++) { Control c = _children[i]; if (!(c is PropertyNameLabel)) { var h = c.Height; c.Bounds = new Rectangle(_margin.Left, y + _spacing, w, h); if (c.Visible) y = c.Bottom; } } y += _margin.Bottom; float namesWidth = _splitterValue * Width; int count = _element.Labels.Count; float[] yStarts = new float[count + 1]; for (int i = 0; i < count; i++) { var label = _element.Labels[i]; if (label.FirstChildControlIndex < 0) yStarts[i] = 0; else if (_children.Count <= label.FirstChildControlIndex) yStarts[i] = y; else { yStarts[i] = _children[label.FirstChildControlIndex].Top; if (i == count - 1) yStarts[i + 1] = _children[label.FirstChildControlIndex].Bottom; } } for (int i = 0; i < count; i++) { var label = _element.Labels[i]; var rect = new Rectangle(0, yStarts[i] + 1, namesWidth, yStarts[i + 1] - yStarts[i] - 2); //label.Parent = this; label.Bounds = rect; } if (_autoSize) Height = y; } } }