// Copyright (c) Wojciech Figat. All rights reserved. using System; namespace FlaxEngine.GUI; /// /// The slider control. /// [ActorToolbox("GUI")] public class Slider : ContainerControl { /// /// The slider direction /// public enum SliderDirection { /// /// Slider direction, horizontal right /// HorizontalRight, /// /// Slider direction, horizontal left /// HorizontalLeft, /// /// Slider direction, vertical up /// VerticalUp, /// /// Slider direction, vertical down /// VerticalDown, } /// /// The minimum value. /// protected float _minimum; /// /// The maximum value. /// protected float _maximum = 100; /// /// Gets or sets the maximum value. /// [EditorOrder(30), Tooltip("The maximum value.")] public float Maximum { get => _maximum; set { if (WholeNumbers) value = Mathf.RoundToInt(value); _maximum = value; if (Value > _maximum) Value = _maximum; } } /// /// Gets or sets the minimum value. /// [EditorOrder(20), Tooltip("The minimum value.")] public float Minimum { get => _minimum; set { if (WholeNumbers) value = Mathf.RoundToInt(value); _minimum = value; if (Value < _minimum) Value = _minimum; } } /// /// Gets or sets the slider direction. /// [EditorOrder(40), Tooltip("Slider Direction.")] public SliderDirection Direction { get => _direction; set { _direction = value; UpdateThumb(); } } private SliderDirection _direction = SliderDirection.HorizontalRight; private float _value = 100f; private Rectangle _thumbRect; private float _thumbCenter; private Float2 _thumbSize = new Float2(16, 16); private bool _isSliding; private bool _mouseOverThumb; /// /// 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 local position of the thumb center /// [HideInEditor] public Float2 ThumbCenter => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? new Float2(_thumbCenter, Height / 2) : new Float2(Width / 2, _thumbCenter); /// /// The local position of the beginning of the track. /// [HideInEditor] public Float2 TrackBeginning { get { switch (Direction) { case SliderDirection.HorizontalRight: return new Float2(_thumbSize.X / 2, Height / 2); case SliderDirection.HorizontalLeft: return new Float2(Width - _thumbSize.X / 2, Height / 2); case SliderDirection.VerticalUp: return new Float2(Width / 2, Height - _thumbSize.Y / 2); case SliderDirection.VerticalDown: return new Float2(Width / 2, _thumbSize.Y / 2); default: break; } return Float2.Zero; } } /// /// The local position of the end of the track. /// [HideInEditor] public Float2 TrackEnd { get { switch (Direction) { case SliderDirection.HorizontalRight: return new Float2(Width - _thumbSize.X / 2, Height / 2); case SliderDirection.HorizontalLeft: return new Float2(_thumbSize.X / 2, Height / 2); case SliderDirection.VerticalUp: return new Float2(Width / 2, _thumbSize.Y / 2); case SliderDirection.VerticalDown: return new Float2(Width / 2, Height - _thumbSize.Y / 2); default: break; } return Float2.Zero; } } /// /// The height of the track. /// [EditorOrder(40), Tooltip("The track height.")] public int TrackThickness { 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 width of the track. /// private float TrackWidth => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? Width - _thumbSize.X : TrackThickness; /// /// Gets the height of the track. /// private float TrackHeight => (Direction is SliderDirection.HorizontalLeft or SliderDirection.HorizontalRight) ? TrackThickness : Height - _thumbSize.Y; /// /// 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 highlighted. /// [EditorDisplay("Thumb Style"), EditorOrder(2031), Tooltip("The color of the slider thumb when it's highlighted.")] public Color ThumbColorHighlighted { get; set; } /// /// The color of the slider thumb when it's selected. /// [EditorDisplay("Thumb Style"), EditorOrder(2032), 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(2033), 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) { AutoFocus = true; var style = Style.Current; TrackLineColor = style.BackgroundHighlighted; TrackFillLineColor = style.LightBackground; ThumbColor = style.BackgroundNormal; ThumbColorSelected = style.BackgroundSelected; ThumbColorHighlighted = style.BackgroundHighlighted; UpdateThumb(); } private void UpdateThumb() { // Cache data var isHorizontal = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft; float trackSize = isHorizontal ? Width : Height; float range = Maximum - Minimum; float pixelRange = trackSize - (isHorizontal ? _thumbSize.X : _thumbSize.Y); float perc = (_value - Minimum) / range; float thumbPosition = (int)(perc * pixelRange); switch (Direction) { case SliderDirection.HorizontalRight: _thumbCenter = thumbPosition + _thumbSize.X / 2; _thumbRect = new Rectangle(thumbPosition, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); break; case SliderDirection.VerticalDown: _thumbCenter = thumbPosition + _thumbSize.Y / 2; _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, thumbPosition, _thumbSize.X, _thumbSize.Y); break; case SliderDirection.HorizontalLeft: _thumbCenter = Width - thumbPosition - _thumbSize.X / 2; _thumbRect = new Rectangle(Width - thumbPosition - _thumbSize.X, (Height - _thumbSize.Y) / 2, _thumbSize.X, _thumbSize.Y); break; case SliderDirection.VerticalUp: _thumbCenter = Height - thumbPosition - _thumbSize.Y / 2; _thumbRect = new Rectangle((Width - _thumbSize.X) / 2, Height - thumbPosition - _thumbSize.Y, _thumbSize.X, _thumbSize.Y); break; default: break; } } private void EndSliding() { _isSliding = false; EndMouseCapture(); SlidingEnd?.Invoke(); } /// public override void Draw() { base.Draw(); // Set rectangles var lineRect = new Rectangle(_thumbSize.X / 2, (Height - TrackThickness) / 2, Width - _thumbSize.X, TrackThickness); var fillLineRect = new Rectangle(_thumbSize.X / 2 - 1, (Height - TrackThickness - 2) / 2, Width - (Width - _thumbCenter) - _thumbSize.X / 2 + 1, TrackThickness + 2); switch (Direction) { case SliderDirection.HorizontalRight: break; case SliderDirection.VerticalDown: lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, _thumbSize.Y / 2 - 1, TrackThickness + 2, Height - (Height - _thumbCenter) - _thumbSize.Y / 2 + 1); break; case SliderDirection.HorizontalLeft: fillLineRect = new Rectangle(Width - (Width - _thumbCenter) - 1, (Height - TrackThickness - 2) / 2, Width - _thumbCenter + 1, TrackThickness + 2); break; case SliderDirection.VerticalUp: lineRect = new Rectangle((Width - TrackThickness) / 2, _thumbSize.Y / 2, TrackThickness, Height - _thumbSize.Y); fillLineRect = new Rectangle((Width - TrackThickness - 2) / 2, Height - (Height - _thumbCenter) - 1, TrackThickness + 2, Height - _thumbCenter + 1); break; default: break; } // Draw track line if (TrackBrush != null) TrackBrush.Draw(lineRect, TrackLineColor); else Render2D.FillRectangle(lineRect, TrackLineColor); // Draw track fill if (FillTrack) { Render2D.PushClip(ref fillLineRect); if (FillTrackBrush != null) FillTrackBrush.Draw(lineRect, TrackFillLineColor); else Render2D.FillRectangle(lineRect, TrackFillLineColor); Render2D.PopClip(); } // Draw thumb var thumbColorV = _isSliding ? ThumbColorSelected : (_mouseOverThumb || IsNavFocused ? ThumbColorHighlighted : ThumbColor); if (ThumbBrush != null) ThumbBrush.Draw(_thumbRect, thumbColorV); else Render2D.FillRectangle(_thumbRect, thumbColorV); } /// public override void OnLostFocus() { if (_isSliding) { EndSliding(); } base.OnLostFocus(); } /// public override bool OnMouseDown(Float2 location, MouseButton button) { if (button == MouseButton.Left) { Focus(); float mousePosition = Direction is SliderDirection.HorizontalRight or SliderDirection.HorizontalLeft ? location.X : location.Y; if (_thumbRect.Contains(ref location)) { // Start sliding _isSliding = true; StartMouseCapture(); SlidingStart?.Invoke(); return true; } else { // Click change switch (Direction) { case SliderDirection.HorizontalRight or SliderDirection.VerticalDown: Value += (mousePosition < _thumbCenter ? -1 : 1) * 10; break; case SliderDirection.HorizontalLeft or SliderDirection.VerticalUp: Value -= (mousePosition < _thumbCenter ? -1 : 1) * 10; break; default: break; } } } return base.OnMouseDown(location, button); } /// public override void OnMouseMove(Float2 location) { _mouseOverThumb = _thumbRect.Contains(location); if (_isSliding) { // Update sliding var slidePosition = location + Root.TrackingMouseOffset; switch (Direction) { case SliderDirection.HorizontalRight: Value = Mathf.Remap(slidePosition.X, 4, Width - 4, Minimum, Maximum); break; case SliderDirection.VerticalDown: Value = Mathf.Remap(slidePosition.Y, 4, Height - 4, Minimum, Maximum); break; case SliderDirection.HorizontalLeft: Value = Mathf.Remap(slidePosition.X, Width - 4, 4, Minimum, Maximum); break; case SliderDirection.VerticalUp: Value = Mathf.Remap(slidePosition.Y, Height - 4, 4, Minimum, Maximum); break; default: break; } } 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(); } }