From 66b4c64f989802ee6e599dbd0d5f8df4415a3cb6 Mon Sep 17 00:00:00 2001 From: Saas Date: Wed, 4 Mar 2026 15:17:47 +0100 Subject: [PATCH] polish sliders and wheel - Hide mouse cursor when clicking on wheel or sliders - Move mouse position to knob position when letting go of slider or wheel - Show mouse again when letting go of slider or wheel (obviously) - Provide some visual feedback when the clicks on the wheel or sliders - Make sliders wider - Add alpha grid background to alpha slider --- .../Editor/GUI/Dialogs/ColorPickerDialog.cs | 4 +- Source/Editor/GUI/Dialogs/ColorSelector.cs | 146 ++++++++++++------ 2 files changed, 103 insertions(+), 47 deletions(-) diff --git a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs index 46970c50e..f528710cd 100644 --- a/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs +++ b/Source/Editor/GUI/Dialogs/ColorPickerDialog.cs @@ -124,7 +124,7 @@ namespace FlaxEditor.GUI.Dialogs _savedColors = JsonSerializer.Deserialize>(savedColors); // Selector - _cSelector = new ColorSelectorWithSliders(180, 18) + _cSelector = new ColorSelectorWithSliders(180, 21) { Location = new Float2(PickerMargin, PickerMargin), Parent = this @@ -421,9 +421,7 @@ namespace FlaxEditor.GUI.Dialogs public override bool OnMouseUp(Float2 location, MouseButton button) { if (base.OnMouseUp(location, button)) - { return true; - } var child = GetChildAtRecursive(location); if (button == MouseButton.Right && child is Button b && b.Tag is Color c) diff --git a/Source/Editor/GUI/Dialogs/ColorSelector.cs b/Source/Editor/GUI/Dialogs/ColorSelector.cs index 79e4029f5..392a8d934 100644 --- a/Source/Editor/GUI/Dialogs/ColorSelector.cs +++ b/Source/Editor/GUI/Dialogs/ColorSelector.cs @@ -14,6 +14,11 @@ namespace FlaxEditor.GUI.Dialogs { private const String GrayedOutParamName = "GrayedOut"; + /// + /// Offset value applied to mouse cursor position when the user lets go of wheel or sliders. + /// + protected const float MouseCursorOffset = 6.0f; + /// /// The color. /// @@ -27,6 +32,19 @@ namespace FlaxEditor.GUI.Dialogs private readonly MaterialBase _hsWheelMaterial; private bool _isMouseDownWheel; + private Rectangle wheelDragRect + { + get + { + var hsv = _color.ToHSV(); + float hAngle = hsv.X * Mathf.DegreesToRadians; + float hRadius = hsv.Y * _wheelRect.Width * 0.5f; + var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle)); + float wheelBoxSize = IsSliding ? 9.0f : 5.0f; + return new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize)); + } + } + /// /// Occurs when selected color gets changed. /// @@ -168,7 +186,6 @@ namespace FlaxEditor.GUI.Dialogs { base.Draw(); - var hsv = _color.ToHSV(); bool enabled = EnabledInHierarchy; _hsWheelMaterial.SetParameterValue(GrayedOutParamName, enabled ? 1.0f : 0.5f); @@ -177,11 +194,10 @@ namespace FlaxEditor.GUI.Dialogs // Wheel float boxExpand = (2.0f * 4.0f / 128.0f) * _wheelRect.Width; Render2D.DrawMaterial(_hsWheelMaterial, _wheelRect, enabled ? Color.White : Color.Gray); - float hAngle = hsv.X * Mathf.DegreesToRadians; - float hRadius = hsv.Y * _wheelRect.Width * 0.5f; - var hsPos = new Float2(hRadius * Mathf.Cos(hAngle), -hRadius * Mathf.Sin(hAngle)); - const float wheelBoxSize = 4.0f; - Render2D.DrawRectangle(new Rectangle(hsPos - (wheelBoxSize * 0.5f) + _wheelRect.Center, new Float2(wheelBoxSize)), _isMouseDownWheel ? Color.Gray : Color.Black); + Color hsColor = Color.FromHSV(new Float3(Color.ToHSV().X, 1, 1)); + Rectangle wheelRect = wheelDragRect; + Render2D.FillRectangle(wheelRect, hsColor); + Render2D.DrawRectangle(wheelRect, _isMouseDownWheel ? Color.Gray : Color.Black, _isMouseDownWheel ? 3.0f : 1.0f); } /// @@ -208,6 +224,7 @@ namespace FlaxEditor.GUI.Dialogs if (!_isMouseDownWheel) { _isMouseDownWheel = true; + Cursor = CursorType.Hidden; StartMouseCapture(); SlidingStart?.Invoke(); } @@ -224,6 +241,10 @@ namespace FlaxEditor.GUI.Dialogs if (button == MouseButton.Left && _isMouseDownWheel) { EndMouseCapture(); + // Make the cursor appear where the user expects it to be (position of selection rectangle) + Rectangle dragRect = wheelDragRect; + Root.MousePosition = dragRect.Center + MouseCursorOffset; + Cursor = CursorType.Default; EndSliding(); return true; } @@ -252,10 +273,10 @@ namespace FlaxEditor.GUI.Dialogs /// public class ColorSelectorWithSliders : ColorSelector { - private Rectangle _slider1Rect; - private Rectangle _slider2Rect; - private bool _isMouseDownSlider1; - private bool _isMouseDownSlider2; + private Rectangle _valueSliderRect; + private Rectangle _alphaSliderRect; + private bool _isMouseDownValueSlider; + private bool _isMouseDownAlphaSlider; /// /// Initializes a new instance of the class. @@ -266,26 +287,26 @@ namespace FlaxEditor.GUI.Dialogs : base(wheelSize) { // Setup dimensions - const float slidersMargin = 8.0f; - _slider1Rect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize); - _slider2Rect = new Rectangle(_slider1Rect.Right + slidersMargin, _slider1Rect.Y, slidersThickness, _slider1Rect.Height); - Size = new Float2(_slider2Rect.Right, wheelSize); + const float slidersMargin = 10.0f; + _valueSliderRect = new Rectangle(wheelSize + slidersMargin, 0, slidersThickness, wheelSize); + _alphaSliderRect = new Rectangle(_valueSliderRect.Right + slidersMargin * 1.5f, _valueSliderRect.Y, slidersThickness, _valueSliderRect.Height); + Size = new Float2(_alphaSliderRect.Right, wheelSize); } /// protected override void UpdateMouse(ref Float2 location) { - if (_isMouseDownSlider1) + if (_isMouseDownValueSlider) { var hsv = _color.ToHSV(); - hsv.Z = 1.0f - Mathf.Saturate((location.Y - _slider1Rect.Y) / _slider1Rect.Height); + hsv.Z = 1.0f - Mathf.Saturate((location.Y - _valueSliderRect.Y) / _valueSliderRect.Height); Color = Color.FromHSV(hsv, _color.A); } - else if (_isMouseDownSlider2) + else if (_isMouseDownAlphaSlider) { var color = _color; - color.A = 1.0f - Mathf.Saturate((location.Y - _slider2Rect.Y) / _slider2Rect.Height); + color.A = 1.0f - Mathf.Saturate((location.Y - _alphaSliderRect.Y) / _alphaSliderRect.Height); Color = color; } @@ -306,32 +327,61 @@ namespace FlaxEditor.GUI.Dialogs var hs = hsv; hs.Z = 1.0f; Color hsC = Color.FromHSV(hs); - const float slidersOffset = 3.0f; - const float slidersThickness = 4.0f; - // Value - float valueY = _slider2Rect.Height * (1 - hsv.Z); - var valueR = new Rectangle(_slider1Rect.X - slidersOffset, _slider1Rect.Y + valueY - slidersThickness / 2, _slider1Rect.Width + slidersOffset * 2, slidersThickness); - Render2D.FillRectangle(_slider1Rect, hsC, hsC, Color.Black, Color.Black); - Render2D.DrawRectangle(_slider1Rect, _isMouseDownSlider1 ? style.BackgroundSelected : Color.Black); - Render2D.DrawRectangle(valueR, _isMouseDownSlider1 ? Color.White : Color.Gray); + // Value slider + float valueKnobExpand = _isMouseDownValueSlider ? 10.0f : 4.0f; + float valueY = _valueSliderRect.Height * (1 - hsv.Z); + float valueKnobWidth = _valueSliderRect.Width + valueKnobExpand; + float valueKnobHeight = _isMouseDownValueSlider ? 7.0f : 4.0f; + float valueKnobX = _valueSliderRect.X - valueKnobExpand * 0.5f; + float valueKnobY = _valueSliderRect.Y + valueY - valueKnobHeight * 0.5f; + Rectangle valueKnobRect = new Rectangle(valueKnobX, valueKnobY, valueKnobWidth, valueKnobHeight); + Render2D.FillRectangle(_valueSliderRect, hsC, hsC, Color.Black, Color.Black); + // Draw one black and one white border to make the knob visible at any saturation level + Render2D.DrawRectangle(valueKnobRect, Color.White, _isMouseDownValueSlider ? 3.0f : 2.0f); + Render2D.DrawRectangle(valueKnobRect, Color.Black, _isMouseDownValueSlider ? 2.0f : 1.0f); - // Alpha - float alphaY = _slider2Rect.Height * (1 - _color.A); - var alphaR = new Rectangle(_slider2Rect.X - slidersOffset, _slider2Rect.Y + alphaY - slidersThickness / 2, _slider2Rect.Width + slidersOffset * 2, slidersThickness); + // Draw checkerboard pattern as background of alpha slider + Render2D.FillRectangle(_alphaSliderRect, Color.White); + var smallRectSize = _alphaSliderRect.Width / 2.0f; + var numHor = Mathf.CeilToInt(_alphaSliderRect.Width / smallRectSize); + var numVer = Mathf.CeilToInt(_alphaSliderRect.Height / smallRectSize); + Render2D.PushClip(_alphaSliderRect); + for (int i = 0; i < numHor; i++) + { + for (int j = 0; j < numVer; j++) + { + if ((i + j) % 2 == 0) + { + var rect = new Rectangle(_alphaSliderRect.X + smallRectSize * i, _alphaSliderRect.Y + smallRectSize * j, new Float2(smallRectSize)); + Render2D.FillRectangle(rect, Color.Gray); + } + } + } + Render2D.PopClip(); + + // Alpha slider + float alphaKnobExpand = _isMouseDownAlphaSlider ? 10.0f : 4.0f; + float alphaY = _alphaSliderRect.Height * (1 - _color.A); + float alphaKnobWidth = _alphaSliderRect.Width + alphaKnobExpand; + float alphaKnobHeight = _isMouseDownAlphaSlider ? 7.0f : 4.0f; + float alphaKnobX = _alphaSliderRect.X - alphaKnobExpand * 0.5f; + float alphaKnobY = _alphaSliderRect.Y + alphaY - alphaKnobExpand * 0.5f; + Rectangle alphaKnobRect = new Rectangle(alphaKnobX, alphaKnobY, alphaKnobWidth, alphaKnobHeight); var color = _color; - color.A = 1; // Keep slider 2 fill rect from changing color alpha while selecting. - Render2D.FillRectangle(_slider2Rect, color, color, Color.Transparent, Color.Transparent); - Render2D.DrawRectangle(_slider2Rect, _isMouseDownSlider2 ? style.BackgroundSelected : Color.Black); - Render2D.DrawRectangle(alphaR, _isMouseDownSlider2 ? Color.White : Color.Gray); + color.A = 1; // Prevent alpha slider fill from becoming transparent + Render2D.FillRectangle(_alphaSliderRect, color, color, Color.Transparent, Color.Transparent); + // Draw one black and one white border to make the knob visible at any saturation level + Render2D.DrawRectangle(alphaKnobRect, Color.White, _isMouseDownAlphaSlider ? 3.0f : 2.0f); + Render2D.DrawRectangle(alphaKnobRect, Color.Black, _isMouseDownAlphaSlider ? 2.0f : 1.0f); } /// public override void OnLostFocus() { // Clear flags - _isMouseDownSlider1 = false; - _isMouseDownSlider2 = false; + _isMouseDownValueSlider = false; + _isMouseDownAlphaSlider = false; base.OnLostFocus(); } @@ -339,15 +389,17 @@ namespace FlaxEditor.GUI.Dialogs /// public override bool OnMouseDown(Float2 location, MouseButton button) { - if (button == MouseButton.Left && _slider1Rect.Contains(location)) + if (button == MouseButton.Left && _valueSliderRect.Contains(location)) { - _isMouseDownSlider1 = true; + _isMouseDownValueSlider = true; + Cursor = CursorType.Hidden; StartMouseCapture(); UpdateMouse(ref location); } - if (button == MouseButton.Left && _slider2Rect.Contains(location)) + if (button == MouseButton.Left && _alphaSliderRect.Contains(location)) { - _isMouseDownSlider2 = true; + _isMouseDownAlphaSlider = true; + Cursor = CursorType.Hidden; StartMouseCapture(); UpdateMouse(ref location); } @@ -358,10 +410,16 @@ namespace FlaxEditor.GUI.Dialogs /// public override bool OnMouseUp(Float2 location, MouseButton button) { - if (button == MouseButton.Left && (_isMouseDownSlider1 || _isMouseDownSlider2)) + if (button == MouseButton.Left && (_isMouseDownValueSlider || _isMouseDownAlphaSlider)) { - _isMouseDownSlider1 = false; - _isMouseDownSlider2 = false; + // Make the cursor appear where the user expects it to be (center of slider horizontally and slider knob position vertically) + float sliderCenter = _isMouseDownValueSlider ? _valueSliderRect.Center.X : _alphaSliderRect.Center.X; + // Calculate y position based on the slider knob to avoid incrementing value by a small amount when the user repeatedly clicks the slider (f.e. when moving in small steps) + float mouseSliderPosition = _isMouseDownValueSlider ? _valueSliderRect.Y + _valueSliderRect.Height * (1 - _color.ToHSV().Z) + MouseCursorOffset : _alphaSliderRect.Y + _alphaSliderRect.Height * (1 - _color.A) + MouseCursorOffset; + Root.MousePosition = new Float2(sliderCenter + MouseCursorOffset, Mathf.Clamp(mouseSliderPosition, _valueSliderRect.Top, _valueSliderRect.Bottom)); + Cursor = CursorType.Default; + _isMouseDownValueSlider = false; + _isMouseDownAlphaSlider = false; EndMouseCapture(); return true; } @@ -373,8 +431,8 @@ namespace FlaxEditor.GUI.Dialogs public override void OnEndMouseCapture() { // Clear flags - _isMouseDownSlider1 = false; - _isMouseDownSlider2 = false; + _isMouseDownValueSlider = false; + _isMouseDownAlphaSlider = false; base.OnEndMouseCapture(); }