diff --git a/Source/Engine/UI/GUI/Common/Slider.cs b/Source/Engine/UI/GUI/Common/Slider.cs new file mode 100644 index 000000000..06c73f15e --- /dev/null +++ b/Source/Engine/UI/GUI/Common/Slider.cs @@ -0,0 +1,347 @@ +using System; + +namespace FlaxEngine.GUI; + +/// +/// The slider control. +/// +public class Slider : ContainerControl +{ + /// + /// The minimum value. + /// + protected float _minimum; + + /// + /// The maximum value. + /// + protected float _maximum = 100f; + + /// + /// Gets or sets the minimum value. + /// + [EditorOrder(20), Tooltip("The minimum value.")] + public float Minimum + { + get => _minimum; + set + { + if (value > _maximum) + throw new ArgumentOutOfRangeException(); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + _minimum = value; + if (Value < _minimum) + Value = _minimum; + } + } + + /// + /// Gets or sets the maximum value. + /// + [EditorOrder(30), Tooltip("The maximum value.")] + public float Maximum + { + get => _maximum; + set + { + if (value < _minimum || Mathf.IsZero(value)) + throw new ArgumentOutOfRangeException(); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + _maximum = value; + if (Value > _maximum) + Value = _maximum; + } + } + + private float _value = 100f; + private Rectangle _thumbRect; + private float _thumbCenter; + private Float2 _thumbSize = new Float2(16, 16); + private bool _isSliding; + + /// + /// Gets or sets the value (normalized to range 0-100). + /// + [EditorOrder(10), Tooltip("The current value.")] + public float Value + { + get => _value; + set + { + value = Mathf.Clamp(value, Minimum, Maximum); + if (WholeNumbers) + value = Mathf.RoundToInt(value); + if (!Mathf.NearEqual(value, _value)) + { + _value = value; + + // Update + UpdateThumb(); + ValueChanged?.Invoke(); + } + } + } + + /// + /// The height of the track. + /// + [EditorOrder(40), Tooltip("The track height.")] + public int TrackHeight { get; set; } = 2; + + /// + /// The thumb size. + /// + [EditorOrder(41), Tooltip("The size of the thumb.")] + public Float2 ThumbSize { + get => _thumbSize; + set + { + _thumbSize = value; + UpdateThumb(); + } + } + + /// + /// Whether to fill the track. + /// + [EditorOrder(42), Tooltip("Fill the track.")] + public bool FillTrack = true; + + /// + /// Whether to use whole numbers. + /// + [EditorOrder(43), Tooltip("Use whole numbers.")] + public bool WholeNumbers = false; + + /// + /// The color of the slider track line + /// + [EditorDisplay("Track Style"), EditorOrder(2010), Tooltip("The color of the slider track line."), ExpandGroups] + public Color TrackLineColor { get; set; } + + /// + /// The color of the slider fill track line + /// + [EditorDisplay("Track Style"), EditorOrder(2011), VisibleIf(nameof(FillTrack)), Tooltip("The color of the slider fill track line.")] + public Color TrackFillLineColor { get; set; } + + /// + /// Gets the size of the track. + /// + private float TrackWidth => Width; + + /// + /// Gets or sets the brush used for slider track drawing. + /// + [EditorDisplay("Track Style"), EditorOrder(2012), Tooltip("The brush used for slider track drawing.")] + public IBrush TrackBrush { get; set; } + + /// + /// Gets or sets the brush used for slider fill track drawing. + /// + [EditorDisplay("Track Style"), EditorOrder(2013), VisibleIf(nameof(FillTrack)), Tooltip("The brush used for slider fill track drawing.")] + public IBrush FillTrackBrush { get; set; } + + /// + /// The color of the slider thumb when it's not selected + /// + [EditorDisplay("Thumb Style"), EditorOrder(2030), Tooltip("The color of the slider thumb when it's not selected."), ExpandGroups] + public Color ThumbColor { get; set; } + + /// + /// The color of the slider thumb when it's selected + /// + [EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's selected.")] + public Color ThumbColorSelected { get; set; } + + /// + /// Gets or sets the brush used for slider thumb drawing. + /// + [EditorDisplay("Thumb Style"), EditorOrder(2032), Tooltip("The brush of the slider thumb.")] + public IBrush ThumbBrush { get; set; } + + /// + /// Gets a value indicating whether user is using a slider. + /// + [HideInEditor] + public bool IsSliding => _isSliding; + + /// + /// Occurs when sliding starts. + /// + public event Action SlidingStart; + + /// + /// Occurs when sliding ends. + /// + public event Action SlidingEnd; + + /// + /// Occurs when value gets changed. + /// + public event Action ValueChanged; + + /// + /// Initializes a new instance of the class. + /// + public Slider() + : this(120, 30) + { + } + + /// + /// Initializes a new instance of the class. + /// + /// The width. + /// The height. + public Slider(float width, float height) + : base(0, 0, width, height) + { + var style = Style.Current; + TrackLineColor = style.BackgroundHighlighted; + TrackFillLineColor = style.LightBackground; + ThumbColor = style.BackgroundNormal; + ThumbColorSelected = style.BackgroundSelected; + UpdateThumb(); + } + + private void UpdateThumb() + { + // Cache data + float trackSize = TrackWidth; + float range = Maximum - Minimum; + float pixelRange = trackSize - _thumbSize.X; + float perc = (_value - Minimum) / range; + float thumbPosition = (int)(perc * pixelRange); + _thumbCenter = thumbPosition + _thumbSize.X / 2; + _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); + } + + private void EndSliding() + { + _isSliding = false; + EndMouseCapture(); + SlidingEnd?.Invoke(); + } + + /// + public override void Draw() + { + base.Draw(); + + // Draw track line + //var lineRect = new Rectangle(4, (Height - TrackHeight) / 2, Width - 8, TrackHeight); + var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight) / 2, Width - _thumbSize.X, TrackHeight); + if (TrackBrush != null) + TrackBrush.Draw(lineRect, TrackLineColor); + else + Render2D.FillRectangle(lineRect, TrackLineColor); + + // Draw track fill + if (FillTrack) + { + var fillLineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackHeight - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2, TrackHeight + 2); + Render2D.PushClip(ref fillLineRect); + if (FillTrackBrush != null) + FillTrackBrush.Draw(lineRect, TrackFillLineColor); + else + Render2D.FillRectangle(lineRect, TrackFillLineColor); + Render2D.PopClip(); + } + + // Draw thumb + var thumbColor = _isSliding ? ThumbColorSelected : ThumbColor; + if (ThumbBrush != null) + ThumbBrush.Draw(_thumbRect, thumbColor); + else + Render2D.FillRectangle(_thumbRect, thumbColor); + } + + /// + public override void OnLostFocus() + { + if (_isSliding) + { + EndSliding(); + } + + base.OnLostFocus(); + } + + /// + public override bool OnMouseDown(Float2 location, MouseButton button) + { + if (button == MouseButton.Left) + { + Focus(); + float mousePosition = location.X; + + if (_thumbRect.Contains(ref location)) + { + // Start sliding + _isSliding = true; + StartMouseCapture(); + SlidingStart?.Invoke(); + return true; + } + else + { + // Click change + Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; + } + } + + return base.OnMouseDown(location, button); + } + + /// + public override void OnMouseMove(Float2 location) + { + if (_isSliding) + { + // Update sliding + var slidePosition = location + Root.TrackingMouseOffset; + Value = Mathf.Remap(slidePosition.X, 4, TrackWidth - 4, Minimum, Maximum); + } + else + { + base.OnMouseMove(location); + } + } + + /// + public override bool OnMouseUp(Float2 location, MouseButton button) + { + if (button == MouseButton.Left && _isSliding) + { + EndSliding(); + return true; + } + + return base.OnMouseUp(location, button); + } + + /// + public override void OnEndMouseCapture() + { + // Check if was sliding + if (_isSliding) + { + EndSliding(); + } + else + { + base.OnEndMouseCapture(); + } + } + + /// + protected override void OnSizeChanged() + { + base.OnSizeChanged(); + + UpdateThumb(); + } +}