diff --git a/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs b/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs index deb0b876f4..1a915ea8b9 100644 --- a/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs +++ b/Terminal.Gui/Views/ScrollBar/ScrollSlider.cs @@ -7,7 +7,7 @@ namespace Terminal.Gui.Views; /// Can be dragged with the mouse, constrained by the size of the Viewport of it's superview. Can be /// oriented either vertically or horizontally. /// -public sealed class ScrollSlider : View, IOrientation, IDesignable +public sealed class ScrollSlider : View, IOrientation, IDesignable, IValue { /// /// Initializes a new instance. @@ -208,6 +208,23 @@ internal void MoveToPosition (int position) private void RaisePositionChangeEvents (int newPosition) { + int oldPosition = _position; + + // Raise IValue ValueChanging event + ValueChangingEventArgs valueChangingArgs = new (oldPosition, newPosition); + + if (OnValueChanging (valueChangingArgs) || valueChangingArgs.Handled) + { + return; + } + + ValueChanging?.Invoke (this, valueChangingArgs); + + if (valueChangingArgs.Handled) + { + return; + } + CancelEventArgs args = new (ref _position, ref newPosition); PositionChanging?.Invoke (this, args); @@ -223,6 +240,12 @@ private void RaisePositionChangeEvents (int newPosition) PositionChanged?.Invoke (this, new (in _position)); + // Raise IValue ValueChanged and ValueChangedUntyped events + ValueChangedEventArgs valueChangedArgs = new (oldPosition, _position); + OnValueChanged (valueChangedArgs); + ValueChanged?.Invoke (this, valueChangedArgs); + ValueChangedUntyped?.Invoke (this, new ValueChangedEventArgs (oldPosition, _position)); + Scrolled?.Invoke (this, new (in distance)); RaiseActivating (new CommandContext (Command.Activate, new WeakReference (this), new CommandBinding ([Command.Activate], null, distance))); @@ -240,6 +263,42 @@ private void RaisePositionChangeEvents (int newPosition) /// Raised when the has changed. Indicates how much to scroll. public event EventHandler>? Scrolled; + #region IValue Implementation + + /// + /// Gets or sets the position of the ScrollSlider. This is an alias for + /// provided to satisfy the interface. + /// + public int Value + { + get => Position; + set => Position = value; + } + + /// + public event EventHandler>? ValueChanging; + + /// + public event EventHandler>? ValueChanged; + + /// + public event EventHandler>? ValueChangedUntyped; + + /// + /// Called when is changing. Return to cancel the change. + /// + /// The event arguments containing old and new values. + /// to cancel the change; otherwise . + private bool OnValueChanging (ValueChangingEventArgs args) => false; + + /// + /// Called when has changed. + /// + /// The event arguments containing old and new values. + private void OnValueChanged (ValueChangedEventArgs args) { } + + #endregion + /// protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute) { diff --git a/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs b/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs index be93f43740..e92113d000 100644 --- a/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs +++ b/Tests/UnitTestsParallelizable/Views/ScrollSliderTests.cs @@ -1010,4 +1010,150 @@ public void Draws_Correctly (int superViewportWidth, int superViewportHeight, in _ = DriverAssert.AssertDriverContentsWithFrameAre (expected, output, driver); } + + // Copilot - IValue tests + + [Fact] + public void Value_Gets_And_Sets_Position () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + Assert.Equal (0, slider.Value); + + slider.Value = 5; + Assert.Equal (5, slider.Value); + Assert.Equal (5, slider.Position); + + slider.Position = 10; + Assert.Equal (10, slider.Value); + Assert.Equal (10, slider.Position); + + slider.Dispose (); + } + + [Fact] + public void ValueChanging_Event_Is_Raised () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + var eventRaised = false; + int oldValue = -1; + int newValue = -1; + + slider.ValueChanging += (_, e) => + { + eventRaised = true; + oldValue = e.CurrentValue; + newValue = e.NewValue; + }; + + slider.Value = 5; + + Assert.True (eventRaised); + Assert.Equal (0, oldValue); + Assert.Equal (5, newValue); + + slider.Dispose (); + } + + [Fact] + public void ValueChanging_Can_Cancel_Change () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + + slider.ValueChanging += (_, e) => e.Handled = true; + + slider.Value = 5; + + Assert.Equal (0, slider.Value); + + slider.Dispose (); + } + + [Fact] + public void ValueChanged_Event_Is_Raised () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + var eventRaised = false; + int oldValue = -1; + int newValue = -1; + + slider.ValueChanged += (_, e) => + { + eventRaised = true; + oldValue = e.OldValue; + newValue = e.NewValue; + }; + + slider.Value = 5; + + Assert.True (eventRaised); + Assert.Equal (0, oldValue); + Assert.Equal (5, newValue); + + slider.Dispose (); + } + + [Fact] + public void ValueChangedUntyped_Event_Is_Raised () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + var eventRaised = false; + object? oldValue = null; + object? newValue = null; + + slider.ValueChangedUntyped += (_, e) => + { + eventRaised = true; + oldValue = e.OldValue; + newValue = e.NewValue; + }; + + slider.Value = 5; + + Assert.True (eventRaised); + Assert.Equal (0, oldValue); + Assert.Equal (5, newValue); + + slider.Dispose (); + } + + [Fact] + public void GetValue_Returns_Boxed_Position () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + slider.Value = 7; + + IValue ivalue = slider; + Assert.Equal (7, ivalue.GetValue ()); + + slider.Dispose (); + } + + [Fact] + public void TrySetValueFromString_Sets_Position () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + + IValue ivalue = slider; + bool result = ivalue.TrySetValueFromString ("5"); + + Assert.True (result); + Assert.Equal (5, slider.Position); + Assert.Equal (5, slider.Value); + + slider.Dispose (); + } + + [Fact] + public void TrySetValueFromString_Returns_False_For_Invalid_Input () + { + ScrollSlider slider = new () { VisibleContentSize = 100, Size = 10 }; + + IValue ivalue = slider; + bool result = ivalue.TrySetValueFromString ("not-a-number"); + + Assert.False (result); + Assert.Equal (0, slider.Value); + + slider.Dispose (); + } }