// 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; } } } }