// Copyright (c) 2012-2023 Wojciech Figat. All rights reserved.
using System;
using FlaxEngine;
using FlaxEngine.GUI;
namespace FlaxEditor.GUI.Input
{
///
/// Base class for text boxes for float/int value editing. Supports slider and range clamping.
///
/// The value type.
///
[HideInEditor]
public abstract class ValueBox : TextBox where T : struct, IComparable
{
///
/// The sliding box size.
///
protected const float SlidingBoxSize = 12.0f;
///
/// The current value.
///
protected T _value;
///
/// The minimum value.
///
protected T _min;
///
/// The maximum value.
///
protected T _max;
///
/// The slider speed.
///
protected float _slideSpeed;
///
/// True if slider is in use.
///
protected bool _isSliding;
///
/// The value cached on sliding start.
///
protected T _startSlideValue;
///
/// The text cached on editing start. Used to compare with the end result to detect changes.
///
protected string _startEditText;
private Float2 _startSlideLocation;
private double _clickStartTime = -1;
private bool _cursorChanged;
private Float2 _mouseClickedPosition;
///
/// Occurs when value gets changed.
///
public event Action ValueChanged;
///
/// Occurs when value gets changed.
///
public event Action> BoxValueChanged;
///
/// Gets or sets the value.
///
public abstract T Value { get; set; }
///
/// Gets or sets the minimum value.
///
public abstract T MinValue { get; set; }
///
/// Gets or sets the maximum value.
///
public abstract T MaxValue { get; set; }
///
/// Gets a value indicating whether user is using a slider.
///
public bool IsSliding => _isSliding;
///
/// Occurs when sliding starts.
///
public event Action SlidingStart;
///
/// Occurs when sliding ends.
///
public event Action SlidingEnd;
///
/// Gets or sets the slider speed. Use value 0 to disable and hide slider UI.
///
public float SlideSpeed
{
get => _slideSpeed;
set => _slideSpeed = value;
}
///
/// Initializes a new instance of the class.
///
/// The value.
/// The x.
/// The y.
/// The width.
/// The minimum.
/// The maximum.
/// The slider speed.
protected ValueBox(T value, float x, float y, float width, T min, T max, float sliderSpeed)
: base(false, x, y, width)
{
_value = value;
_min = min;
_max = max;
_slideSpeed = sliderSpeed;
}
///
/// Updates the text of the textbox.
///
protected abstract void UpdateText();
///
/// Tries the get value from the textbox text.
///
protected abstract void TryGetValue();
///
/// Applies the sliding delta to the value.
///
/// The delta (scaled).
protected abstract void ApplySliding(float delta);
///
/// Called when value gets changed.
///
protected virtual void OnValueChanged()
{
ValueChanged?.Invoke();
BoxValueChanged?.Invoke(this);
}
///
/// Gets a value indicating whether this value box can use sliding.
///
protected virtual bool CanUseSliding => _slideSpeed > Mathf.Epsilon;
///
/// Gets the slide rectangle.
///
protected virtual Rectangle SlideRect
{
get
{
float x = Width - SlidingBoxSize - 1.0f;
float y = (Height - SlidingBoxSize) * 0.5f;
return new Rectangle(x, y, SlidingBoxSize, SlidingBoxSize);
}
}
private void EndSliding()
{
_isSliding = false;
EndEditOnClick = true;
EndMouseCapture();
if (_cursorChanged)
{
Cursor = CursorType.Default;
_cursorChanged = false;
}
SlidingEnd?.Invoke();
Defocus();
Parent?.Focus();
}
///
public override void Draw()
{
base.Draw();
if (CanUseSliding)
{
var style = Style.Current;
// Draw sliding UI
Render2D.DrawSprite(style.Scalar, SlideRect, style.Foreground);
// Check if is sliding
if (_isSliding)
{
// Draw overlay
// TODO: render nicer overlay with some glow from the borders (inside)
Render2D.FillRectangle(new Rectangle(Float2.Zero, Size), Color.Orange * 0.3f);
}
}
}
///
public override void OnGotFocus()
{
base.OnGotFocus();
SelectAll();
}
///
public override void OnLostFocus()
{
// Check if was sliding
if (_isSliding)
{
EndSliding();
base.OnLostFocus();
}
else
{
base.OnLostFocus();
// Update
UpdateText();
}
Cursor = CursorType.Default;
ResetViewOffset();
}
///
public override bool OnMouseDown(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && CanUseSliding && SlideRect.Contains(location))
{
// Start sliding
_isSliding = true;
_startSlideLocation = location;
_startSlideValue = _value;
StartMouseCapture(true);
EndEditOnClick = false;
// Hide cursor and cache location
Cursor = CursorType.Hidden;
_mouseClickedPosition = PointToWindow(location);
_cursorChanged = true;
SlidingStart?.Invoke();
return true;
}
if (button == MouseButton.Left && !IsFocused)
_clickStartTime = Platform.TimeSeconds;
return base.OnMouseDown(location, button);
}
///
public override void OnMouseMove(Float2 location)
{
if (_isSliding && !RootWindow.Window.IsMouseFlippingHorizontally)
{
// Update sliding
var slideLocation = location + Root.TrackingMouseOffset;
ApplySliding(Mathf.RoundToInt(slideLocation.X - _startSlideLocation.X) * _slideSpeed);
return;
}
// Update cursor type so user knows they can slide value
if (CanUseSliding && SlideRect.Contains(location) && !_isSliding)
{
Cursor = CursorType.SizeWE;
_cursorChanged = true;
}
else if (_cursorChanged && !_isSliding)
{
Cursor = CursorType.Default;
_cursorChanged = false;
}
base.OnMouseMove(location);
}
///
public override bool OnMouseUp(Float2 location, MouseButton button)
{
if (button == MouseButton.Left && _isSliding)
{
// End sliding and return mouse to original location
RootWindow.MousePosition = _mouseClickedPosition;
EndSliding();
return true;
}
if (button == MouseButton.Left && _clickStartTime > 0 && (Platform.TimeSeconds - _clickStartTime) < 0.2f)
{
_clickStartTime = -1;
OnSelectingEnd();
SelectAll();
return true;
}
return base.OnMouseUp(location, button);
}
///
public override void OnMouseLeave()
{
if (_cursorChanged)
{
Cursor = CursorType.Default;
_cursorChanged = false;
}
base.OnMouseLeave();
}
///
protected override void OnEditBegin()
{
base.OnEditBegin();
_startEditText = _text;
}
///
protected override void OnEditEnd()
{
if (_startEditText != _text)
{
// Update value
TryGetValue();
}
_startEditText = null;
base.OnEditEnd();
}
///
public override void OnEndMouseCapture()
{
// Check if was sliding
if (_isSliding)
{
EndSliding();
}
else
{
base.OnEndMouseCapture();
}
}
///
protected override Rectangle TextRectangle
{
get
{
var result = base.TextRectangle;
if (CanUseSliding)
{
result.Size.X -= SlidingBoxSize;
}
return result;
}
}
///
protected override Rectangle TextClipRectangle
{
get
{
var result = base.TextRectangle;
if (CanUseSliding)
{
result.Size.X -= SlidingBoxSize;
}
return result;
}
}
}
}