Files
FlaxEngine/Source/Editor/GUI/Input/SliderControl.cs

469 lines
14 KiB
C#

// Copyright (c) 2012-2022 Wojciech Figat. All rights reserved.
using System;
using System.Globalization;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Input
{
/// <summary>
/// Float value editor with fixed size text box and slider.
/// </summary>
[HideInEditor]
public class SliderControl : ContainerControl
{
/// <summary>
/// The horizontal slider control.
/// </summary>
/// <seealso cref="FlaxEngine.GUI.Control" />
[HideInEditor]
protected class Slider : Control
{
/// <summary>
/// The default size.
/// </summary>
public const int DefaultSize = 16;
/// <summary>
/// The default thickness.
/// </summary>
public const int DefaultThickness = 6;
/// <summary>
/// The minimum value (constant)
/// </summary>
public const float Minimum = 0.0f;
/// <summary>
/// The maximum value (constant).
/// </summary>
public const float Maximum = 100.0f;
private float _value;
private Rectangle _thumbRect;
private float _thumbCenter, _thumbSize;
private bool _isSliding;
/// <summary>
/// Gets or sets the value (normalized to range 0-100).
/// </summary>
public float Value
{
get => _value;
set
{
value = Mathf.Clamp(value, Minimum, Maximum);
if (!Mathf.NearEqual(value, _value))
{
_value = value;
// Update
UpdateThumb();
ValueChanged?.Invoke();
}
}
}
/// <summary>
/// Occurs when value gets changed.
/// </summary>
public Action ValueChanged;
/// <summary>
/// The color of the slider track line
/// </summary>
public Color TrackLineColor { get; set; }
/// <summary>
/// The color of the slider thumb when it's not selected
/// </summary>
public Color ThumbColor { get; set; }
/// <summary>
/// The color of the slider thumb when it's selected
/// </summary>
public Color ThumbColorSelected { get; set; }
/// <summary>
/// Gets a value indicating whether user is using a slider.
/// </summary>
public bool IsSliding => _isSliding;
/// <summary>
/// Occurs when sliding starts.
/// </summary>
public Action SlidingStart;
/// <summary>
/// Occurs when sliding ends.
/// </summary>
public Action SlidingEnd;
/// <summary>
/// Initializes a new instance of the <see cref="Slider"/> class.
/// </summary>
/// <param name="width">The width.</param>
/// <param name="height">The height.</param>
public Slider(float width, float height)
: base(0, 0, width, height)
{
var style = Style.Current;
TrackLineColor = style.BackgroundHighlighted;
ThumbColor = style.BackgroundNormal;
ThumbColorSelected = style.BackgroundSelected;
}
private void UpdateThumb()
{
// Cache data
float trackSize = TrackSize;
float range = Maximum - Minimum;
_thumbSize = Mathf.Min(trackSize, Mathf.Max(trackSize / range * 10.0f, 30.0f));
float pixelRange = trackSize - _thumbSize;
float perc = (_value - Minimum) / range;
float thumbPosition = (int)(perc * pixelRange);
_thumbCenter = thumbPosition + _thumbSize / 2;
_thumbRect = new Rectangle(thumbPosition + 4, (Height - DefaultThickness) / 2, _thumbSize - 8, DefaultThickness);
}
private void EndSliding()
{
_isSliding = false;
EndMouseCapture();
SlidingEnd?.Invoke();
}
/// <summary>
/// Gets the size of the track.
/// </summary>
private float TrackSize => Width;
/// <inheritdoc />
public override void Draw()
{
base.Draw();
// Draw track line
var lineRect = new Rectangle(4, Height / 2, Width - 8, 1);
Render2D.FillRectangle(lineRect, TrackLineColor);
// Draw thumb
Render2D.FillRectangle(_thumbRect, _isSliding ? ThumbColorSelected : ThumbColor);
}
/// <inheritdoc />
public override void OnLostFocus()
{
if (_isSliding)
{
EndSliding();
}
base.OnLostFocus();
}
/// <inheritdoc />
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);
}
/// <inheritdoc />
public override void OnMouseMove(Float2 location)
{
if (_isSliding)
{
// Update sliding
var slidePosition = location + Root.TrackingMouseOffset;
Value = Mathf.Map(slidePosition.X, 4, TrackSize - 4, Minimum, Maximum);
}
else
{
base.OnMouseMove(location);
}
}
/// <inheritdoc />
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isSliding)
{
EndSliding();
return true;
}
return base.OnMouseUp(location, button);
}
/// <inheritdoc />
public override void OnEndMouseCapture()
{
// Check if was sliding
if (_isSliding)
{
EndSliding();
}
else
{
base.OnEndMouseCapture();
}
}
/// <inheritdoc />
protected override void OnSizeChanged()
{
base.OnSizeChanged();
UpdateThumb();
}
}
/// <summary>
/// The slider.
/// </summary>
protected Slider _slider;
/// <summary>
/// The text box.
/// </summary>
protected TextBox _textBox;
/// <summary>
/// The text box size (rest will be the slider area).
/// </summary>
protected const float TextBoxSize = 30.0f;
private float _value;
private float _min, _max;
private bool _valueIsChanging;
/// <summary>
/// Occurs when value gets changed.
/// </summary>
public event Action ValueChanged;
/// <summary>
/// Gets or sets the value.
/// </summary>
public float Value
{
get => _value;
set
{
value = Mathf.Clamp(value, _min, _max);
if (Math.Abs(_value - value) > Mathf.Epsilon)
{
// Set value
_value = value;
// Update
_valueIsChanging = true;
UpdateText();
UpdateSlider();
_valueIsChanging = false;
OnValueChanged();
}
}
}
/// <summary>
/// Gets or sets the minimum value.
/// </summary>
public float MinValue
{
get => _min;
set
{
if (!Mathf.NearEqual(_min, value))
{
if (value > _max)
throw new ArgumentException();
_min = value;
Value = Value;
}
}
}
/// <summary>
/// Gets or sets the maximum value.
/// </summary>
public float MaxValue
{
get => _max;
set
{
if (!Mathf.NearEqual(_max, value))
{
if (value < _min)
throw new ArgumentException();
_max = value;
Value = Value;
}
}
}
/// <summary>
/// Gets a value indicating whether user is using a slider.
/// </summary>
public bool IsSliding => _slider.IsSliding;
/// <summary>
/// Occurs when sliding starts.
/// </summary>
public event Action SlidingStart;
/// <summary>
/// Occurs when sliding ends.
/// </summary>
public event Action SlidingEnd;
/// <summary>
/// Initializes a new instance of the <see cref="SliderControl"/> class.
/// </summary>
/// <param name="value">The value.</param>
/// <param name="x">The position x.</param>
/// <param name="y">The position y.</param>
/// <param name="width">The width.</param>
/// <param name="min">The minimum value.</param>
/// <param name="max">The maximum value.</param>
public SliderControl(float value, float x = 0, float y = 0, float width = 120, float min = float.MinValue, float max = float.MaxValue)
: base(x, y, width, TextBox.DefaultHeight)
{
_min = min;
_max = max;
_value = Mathf.Clamp(value, min, max);
float split = Width - TextBoxSize;
_slider = new Slider(split, Height)
{
Parent = this,
};
_slider.ValueChanged += SliderOnValueChanged;
_slider.SlidingStart += SlidingStart;
_slider.SlidingEnd += SlidingEnd;
_textBox = new TextBox(false, split, 0)
{
Text = _value.ToString(CultureInfo.InvariantCulture),
Parent = this,
Location = new Float2(split, 0),
Size = new Float2(Height, TextBoxSize),
CanEndEditByClick = true,
};
_textBox.EditEnd += OnTextBoxEditEnd;
}
private void SliderOnValueChanged()
{
if (_valueIsChanging)
return;
Value = Mathf.Map(_slider.Value, Slider.Minimum, Slider.Maximum, MinValue, MaxValue);
}
private void OnTextBoxEditEnd()
{
if (_valueIsChanging)
return;
var text = _textBox.Text.Replace(',', '.');
if (double.TryParse(text, NumberStyles.Float | NumberStyles.AllowThousands, CultureInfo.InvariantCulture, out var value))
{
Value = (float)Math.Round(value, 5);
}
else
{
UpdateText();
}
}
/// <summary>
/// Sets the limits from the attribute.
/// </summary>
/// <param name="limits">The limits.</param>
public void SetLimits(RangeAttribute limits)
{
_min = limits.Min;
_max = Mathf.Max(_min, limits.Max);
Value = Value;
}
/// <summary>
/// Sets the limits from the attribute.
/// </summary>
/// <param name="limits">The limits.</param>
public void SetLimits(LimitAttribute limits)
{
_min = limits.Min;
_max = Mathf.Max(_min, limits.Max);
Value = Value;
}
/// <summary>
/// Updates the text of the textbox.
/// </summary>
protected virtual void UpdateText()
{
_textBox.Text = _value.ToString(CultureInfo.InvariantCulture);
}
/// <summary>
/// Updates the slider value.
/// </summary>
protected virtual void UpdateSlider()
{
_slider.Value = Mathf.Map(_value, MinValue, MaxValue, Slider.Minimum, Slider.Maximum);
}
/// <summary>
/// Called when value gets changed.
/// </summary>
protected virtual void OnValueChanged()
{
ValueChanged?.Invoke();
}
/// <inheritdoc />
protected override void PerformLayoutBeforeChildren()
{
base.PerformLayoutBeforeChildren();
float split = Width - TextBoxSize;
_slider.Bounds = new Rectangle(0, 0, split, Height);
_textBox.Bounds = new Rectangle(split, 0, TextBoxSize, Height);
}
/// <inheritdoc />
public override void OnDestroy()
{
_slider = null;
_textBox = null;
base.OnDestroy();
}
}
}