Browse Source

Starting implementing ScrollBar.

BDisp 11 months ago
parent
commit
5e7bb7bd6c

+ 19 - 7
Terminal.Gui/Views/Scroll/Scroll.cs

@@ -10,8 +10,11 @@ namespace Terminal.Gui;
 public class Scroll : View
 public class Scroll : View
 {
 {
     /// <inheritdoc/>
     /// <inheritdoc/>
-    public Scroll ()
+    public Scroll () : this (null) { }
+
+    public Scroll (ScrollBar? host)
     {
     {
+        _host = host;
         _slider = new (this);
         _slider = new (this);
         Add (_slider);
         Add (_slider);
 
 
@@ -23,6 +26,7 @@ public class Scroll : View
     }
     }
 
 
 
 
+    internal readonly ScrollBar? _host;
     internal bool _wasSliderLayoutComplete = true;
     internal bool _wasSliderLayoutComplete = true;
 
 
     private readonly ScrollSlider _slider;
     private readonly ScrollSlider _slider;
@@ -193,6 +197,20 @@ public class Scroll : View
     /// <summary>Virtual method called when <see cref="Size"/> has changed. Raises <see cref="SizeChanged"/>.</summary>
     /// <summary>Virtual method called when <see cref="Size"/> has changed. Raises <see cref="SizeChanged"/>.</summary>
     protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); }
     protected void OnSizeChanged (int size) { SizeChanged?.Invoke (this, new (in size)); }
 
 
+    internal void AdjustScroll ()
+    {
+        if (_host is { })
+        {
+            X = Orientation == Orientation.Vertical ? 0 : 1;
+            Y = Orientation == Orientation.Vertical ? 1 : 0;
+            Width = Orientation == Orientation.Vertical ? Dim.Fill () : Dim.Fill (1);
+            Height = Orientation == Orientation.Vertical ? Dim.Fill (1) : Dim.Fill ();
+        }
+
+        _slider.AdjustSlider ();
+        SetScrollText ();
+    }
+
     /// <inheritdoc/>
     /// <inheritdoc/>
     internal override void OnLayoutComplete (LayoutEventArgs args)
     internal override void OnLayoutComplete (LayoutEventArgs args)
     {
     {
@@ -201,12 +219,6 @@ public class Scroll : View
         AdjustScroll ();
         AdjustScroll ();
     }
     }
 
 
-    private void AdjustScroll ()
-    {
-        _slider.AdjustSlider ();
-        SetScrollText ();
-    }
-
     private void SetScrollText ()
     private void SetScrollText ()
     {
     {
         TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
         TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;

+ 118 - 0
Terminal.Gui/Views/Scroll/ScrollBar.cs

@@ -0,0 +1,118 @@
+#nullable enable
+
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+/// <summary>ScrollBars are views that display a 1-character scrollbar, either horizontal or vertical</summary>
+/// <remarks>
+///     <para>
+///         The scrollbar is drawn to be a representation of the Size, assuming that the scroll position is set at
+///         Position.
+///     </para>
+///     <para>If the region to display the scrollbar is larger than three characters, arrow indicators are drawn.</para>
+/// </remarks>
+public class ScrollBar : View
+{
+    /// <inheritdoc/>
+    public ScrollBar ()
+    {
+        _scroll = new (this);
+        _decrease = new (this);
+        _increase = new (this, VariationMode.Increase);
+        Add (_scroll, _decrease, _increase);
+
+        CanFocus = false;
+        Orientation = Orientation.Vertical;
+        Width = Dim.Auto (DimAutoStyle.Content, 1);
+        Height = Dim.Auto (DimAutoStyle.Content, 1);
+
+        _scroll.PositionChanging += Scroll_PositionChanging;
+        _scroll.PositionChanged += Scroll_PositionChanged;
+        _scroll.SizeChanged += _scroll_SizeChanged;
+    }
+
+    private readonly Scroll _scroll;
+    private readonly ScrollButton _decrease;
+    private readonly ScrollButton _increase;
+
+    /// <summary>Defines if a scrollbar is vertical or horizontal.</summary>
+    public Orientation Orientation
+    {
+        get => _scroll.Orientation;
+        set
+        {
+            Resize (value);
+            _scroll.Orientation = value;
+        }
+    }
+
+    /// <summary>The position, relative to <see cref="Size"/>, to set the scrollbar at.</summary>
+    /// <value>The position.</value>
+    public int Position
+    {
+        get => _scroll.Position;
+        set
+        {
+            _scroll.Position = value;
+            AdjustAll ();
+        }
+    }
+
+    /// <summary>Raised when the <see cref="Position"/> has changed.</summary>
+    public event EventHandler<EventArgs<int>>? PositionChanged;
+
+    /// <summary>
+    ///     Raised when the <see cref="Position"/> is changing. Set <see cref="CancelEventArgs.Cancel"/> to
+    ///     <see langword="true"/> to prevent the position from being changed.
+    /// </summary>
+    public event EventHandler<CancelEventArgs<int>>? PositionChanging;
+
+    public int Size
+    {
+        get => _scroll.Size;
+        set
+        {
+            _scroll.Size = value;
+            AdjustAll ();
+        }
+    }
+
+    /// <summary>Raised when <see cref="Size"/> has changed.</summary>
+    public event EventHandler<EventArgs<int>>? SizeChanged;
+
+    /// <inheritdoc/>
+    internal override void OnLayoutComplete (LayoutEventArgs args)
+    {
+        base.OnLayoutComplete (args);
+
+        AdjustAll ();
+    }
+
+    private void _scroll_SizeChanged (object? sender, EventArgs<int> e) { SizeChanged?.Invoke (this, e); }
+
+    private void AdjustAll ()
+    {
+        _scroll.AdjustScroll ();
+        _decrease.AdjustButton ();
+        _increase.AdjustButton ();
+    }
+
+    private void Resize (Orientation orientation)
+    {
+        switch (orientation)
+        {
+            case Orientation.Horizontal:
+
+                break;
+            case Orientation.Vertical:
+                break;
+            default:
+                throw new ArgumentOutOfRangeException (nameof (orientation), orientation, null);
+        }
+    }
+
+    private void Scroll_PositionChanged (object? sender, EventArgs<int> e) { PositionChanged?.Invoke (this, e); }
+
+    private void Scroll_PositionChanging (object? sender, CancelEventArgs<int> e) { PositionChanging?.Invoke (this, e); }
+}

+ 143 - 0
Terminal.Gui/Views/Scroll/ScrollButton.cs

@@ -0,0 +1,143 @@
+#nullable enable
+
+namespace Terminal.Gui;
+
+internal enum VariationMode
+{
+    Decrease,
+    Increase
+}
+
+internal class ScrollButton : View
+{
+    public ScrollButton (ScrollBar host, VariationMode variation = VariationMode.Decrease)
+    {
+        _host = host;
+        VariationMode = variation;
+        TextAlignment = Alignment.Center;
+        VerticalTextAlignment = Alignment.Center;
+        Id = "scrollButton";
+
+        //Width = Dim.Auto (DimAutoStyle.Content, 1);
+        //Height = Dim.Auto (DimAutoStyle.Content, 1);
+        WantContinuousButtonPressed = true;
+    }
+
+    private readonly ScrollBar _host;
+    private ColorScheme? _savedColorScheme;
+
+    public void AdjustButton ()
+    {
+        if (!IsInitialized)
+        {
+            return;
+        }
+
+        Width = _host.Orientation == Orientation.Vertical ? Dim.Fill () : 1;
+        Height = _host.Orientation == Orientation.Vertical ? 1 : Dim.Fill ();
+
+        switch (VariationMode)
+        {
+            case VariationMode.Decrease:
+                X = 0;
+                Y = 0;
+
+                break;
+            case VariationMode.Increase:
+                X = _host.Orientation == Orientation.Vertical ? 0 : Pos.AnchorEnd (1);
+                Y = _host.Orientation == Orientation.Vertical ? Pos.AnchorEnd (1) : 0;
+
+                break;
+            default:
+                throw new ArgumentOutOfRangeException ();
+        }
+
+        SetButtonText ();
+    }
+
+    /// <inheritdoc/>
+    public override Attribute GetNormalColor ()
+    {
+        if (_savedColorScheme is null)
+        {
+            ColorScheme = new () { Normal = new (_host.ColorScheme.HotNormal.Foreground, _host.ColorScheme.HotNormal.Background) };
+        }
+        else
+        {
+            ColorScheme = new () { Normal = new (_host.ColorScheme.Normal.Background, _host.ColorScheme.Normal.Foreground) };
+        }
+
+        return base.GetNormalColor ();
+    }
+
+    public VariationMode VariationMode { get; }
+
+    /// <inheritdoc/>
+    protected internal override bool? OnMouseEnter (MouseEvent mouseEvent)
+    {
+        _savedColorScheme ??= _host.ColorScheme;
+
+        ColorScheme = new ()
+        {
+            Normal = new (_savedColorScheme.HotNormal.Foreground, _savedColorScheme.HotNormal.Foreground),
+            Focus = new (_savedColorScheme.Focus.Foreground, _savedColorScheme.Focus.Foreground),
+            HotNormal = new (_savedColorScheme.Normal.Foreground, _savedColorScheme.Normal.Foreground),
+            HotFocus = new (_savedColorScheme.HotFocus.Foreground, _savedColorScheme.HotFocus.Foreground),
+            Disabled = new (_savedColorScheme.Disabled.Foreground, _savedColorScheme.Disabled.Foreground)
+        };
+
+        return base.OnMouseEnter (mouseEvent);
+    }
+
+    /// <inheritdoc/>
+    protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
+    {
+        if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+        {
+            switch (VariationMode)
+            {
+                case VariationMode.Decrease:
+                    _host.Position--;
+
+                    return true;
+                case VariationMode.Increase:
+                    _host.Position++;
+
+                    return true;
+                default:
+                    throw new ArgumentOutOfRangeException ();
+            }
+        }
+
+        return base.OnMouseEvent (mouseEvent);
+    }
+
+    /// <inheritdoc/>
+    protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
+    {
+        if (_savedColorScheme is { } && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+        {
+            ColorScheme = _savedColorScheme;
+            _savedColorScheme = null;
+        }
+
+        return base.OnMouseLeave (mouseEvent);
+    }
+
+    private void SetButtonText ()
+    {
+        switch (VariationMode)
+        {
+            case VariationMode.Decrease:
+                Text = _host.Orientation == Orientation.Vertical ? Glyphs.UpArrow.ToString () : Glyphs.LeftArrow.ToString ();
+
+                break;
+            case VariationMode.Increase:
+                Text = _host.Orientation == Orientation.Vertical ? Glyphs.DownArrow.ToString () : Glyphs.RightArrow.ToString ();
+
+                break;
+            default:
+                throw new ArgumentOutOfRangeException ();
+        }
+    }
+}

+ 259 - 0
UICatalog/Scenarios/ScrollBarDemo.cs

@@ -0,0 +1,259 @@
+using System;
+using Terminal.Gui;
+
+namespace UICatalog.Scenarios;
+
+[ScenarioMetadata ("ScrollBar Demo", "Demonstrates using ScrollBar view.")]
+[ScenarioCategory ("Drawing")]
+[ScenarioCategory ("Scrolling")]
+public class ScrollBarDemo : Scenario
+{
+    private ViewDiagnosticFlags _diagnosticFlags;
+
+    public override void Main ()
+    {
+        Application.Init ();
+
+        _diagnosticFlags = View.Diagnostics;
+
+        Window app = new ()
+        {
+            Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}"
+        };
+
+        var editor = new AdornmentsEditor ();
+        app.Add (editor);
+
+        var view = new FrameView
+        {
+            Title = "Demo View",
+            X = Pos.Right (editor),
+            Width = Dim.Fill (),
+            Height = Dim.Fill (),
+            ColorScheme = Colors.ColorSchemes ["Base"]
+        };
+        app.Add (view);
+
+        var scrollBar = new ScrollBar
+        {
+            X = Pos.AnchorEnd (),
+            Height = Dim.Fill (),
+        };
+        view.Add (scrollBar);
+
+        var lblWidthHeight = new Label
+        {
+            Text = "Width/Height:"
+        };
+        view.Add (lblWidthHeight);
+
+        NumericUpDown<int> scrollWidthHeight = new ()
+        {
+            Value = scrollBar.Frame.Width,
+            X = Pos.Right (lblWidthHeight) + 1,
+            Y = Pos.Top (lblWidthHeight)
+        };
+        view.Add (scrollWidthHeight);
+
+        scrollWidthHeight.ValueChanging += (s, e) =>
+                                           {
+                                               if (e.NewValue < 1
+                                                   || (e.NewValue
+                                                       > (scrollBar.Orientation == Orientation.Vertical
+                                                              ? scrollBar.SuperView?.GetContentSize ().Width
+                                                              : scrollBar.SuperView?.GetContentSize ().Height)))
+                                               {
+                                                   // TODO: This must be handled in the ScrollSlider if Width and Height being virtual
+                                                   e.Cancel = true;
+
+                                                   return;
+                                               }
+
+                                               if (scrollBar.Orientation == Orientation.Vertical)
+                                               {
+                                                   scrollBar.Width = e.NewValue;
+                                               }
+                                               else
+                                               {
+                                                   scrollBar.Height = e.NewValue;
+                                               }
+                                           };
+
+        var rgOrientation = new RadioGroup
+        {
+            Y = Pos.Bottom (lblWidthHeight),
+            RadioLabels = ["Vertical", "Horizontal"],
+            Orientation = Orientation.Horizontal
+        };
+        view.Add (rgOrientation);
+
+        rgOrientation.SelectedItemChanged += (s, e) =>
+                                             {
+                                                 if (e.SelectedItem == e.PreviousSelectedItem)
+                                                 {
+                                                     return;
+                                                 }
+
+                                                 if (rgOrientation.SelectedItem == 0)
+                                                 {
+                                                     scrollBar.Orientation = Orientation.Vertical;
+                                                     scrollBar.X = Pos.AnchorEnd ();
+                                                     scrollBar.Y = 0;
+                                                     scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Width);
+                                                     scrollBar.Width = scrollWidthHeight.Value;
+                                                     scrollBar.Height = Dim.Fill ();
+                                                     scrollBar.Size /= 3;
+                                                 }
+                                                 else
+                                                 {
+                                                     scrollBar.Orientation = Orientation.Horizontal;
+                                                     scrollBar.X = 0;
+                                                     scrollBar.Y = Pos.AnchorEnd ();
+                                                     scrollBar.Width = Dim.Fill ();
+
+                                                     scrollWidthHeight.Value = Math.Min (scrollWidthHeight.Value, scrollBar.SuperView.GetContentSize ().Height);
+                                                     scrollBar.Height = scrollWidthHeight.Value;
+                                                     scrollBar.Size *= 3;
+                                                 }
+                                             };
+
+        var lblSize = new Label
+        {
+            Y = Pos.Bottom (rgOrientation),
+            Text = "Size:"
+        };
+        view.Add (lblSize);
+
+        NumericUpDown<int> scrollSize = new ()
+        {
+            Value = scrollBar.Size,
+            X = Pos.Right (lblSize) + 1,
+            Y = Pos.Top (lblSize)
+        };
+        view.Add (scrollSize);
+
+        scrollSize.ValueChanging += (s, e) =>
+                                    {
+                                        if (e.NewValue < 0)
+                                        {
+                                            e.Cancel = true;
+
+                                            return;
+                                        }
+
+                                        if (scrollBar.Size != e.NewValue)
+                                        {
+                                            scrollBar.Size = e.NewValue;
+                                        }
+                                    };
+
+        var lblPosition = new Label
+        {
+            Y = Pos.Bottom (lblSize),
+            Text = "Position:"
+        };
+        view.Add (lblPosition);
+
+        NumericUpDown<int> scrollPosition = new ()
+        {
+            Value = scrollBar.Position,
+            X = Pos.Right (lblPosition) + 1,
+            Y = Pos.Top (lblPosition)
+        };
+        view.Add (scrollPosition);
+
+        scrollPosition.ValueChanging += (s, e) =>
+                                        {
+                                            if (e.NewValue < 0)
+                                            {
+                                                e.Cancel = true;
+
+                                                return;
+                                            }
+
+                                            if (scrollBar.Position != e.NewValue)
+                                            {
+                                                scrollBar.Position = e.NewValue;
+                                            }
+
+                                            if (scrollBar.Position != e.NewValue)
+                                            {
+                                                e.Cancel = true;
+                                            }
+                                        };
+
+        var lblSizeChanged = new Label
+        {
+            Y = Pos.Bottom (lblPosition) + 1
+        };
+        view.Add (lblSizeChanged);
+
+        scrollBar.SizeChanged += (s, e) =>
+                              {
+                                  lblSizeChanged.Text = $"SizeChanged event - CurrentValue: {e.CurrentValue}";
+
+                                  if (scrollSize.Value != e.CurrentValue)
+                                  {
+                                      scrollSize.Value = e.CurrentValue;
+                                  }
+                              };
+
+        var lblPosChanging = new Label
+        {
+            Y = Pos.Bottom (lblSizeChanged)
+        };
+        view.Add (lblPosChanging);
+
+        scrollBar.PositionChanging += (s, e) => { lblPosChanging.Text = $"PositionChanging event - CurrentValue: {e.CurrentValue}; NewValue: {e.NewValue}"; };
+
+        var lblPositionChanged = new Label
+        {
+            Y = Pos.Bottom (lblPosChanging)
+        };
+        view.Add (lblPositionChanged);
+
+        scrollBar.PositionChanged += (s, e) =>
+                                  {
+                                      lblPositionChanged.Text = $"PositionChanged event - CurrentValue: {e.CurrentValue}";
+                                      scrollPosition.Value = e.CurrentValue;
+                                  };
+
+        var lblScrollFrame = new Label
+        {
+            Y = Pos.Bottom (lblPositionChanged) + 1
+        };
+        view.Add (lblScrollFrame);
+
+        var lblScrollViewport = new Label
+        {
+            Y = Pos.Bottom (lblScrollFrame)
+        };
+        view.Add (lblScrollViewport);
+
+        var lblScrollContentSize = new Label
+        {
+            Y = Pos.Bottom (lblScrollViewport)
+        };
+        view.Add (lblScrollContentSize);
+
+
+        scrollBar.LayoutComplete += (s, e) =>
+                                 {
+                                     lblScrollFrame.Text = $"ScrollBar Frame: {scrollBar.Frame.ToString ()}";
+                                     lblScrollViewport.Text = $"ScrollBar Viewport: {scrollBar.Viewport.ToString ()}";
+                                     lblScrollContentSize.Text = $"ScrollBar ContentSize: {scrollBar.GetContentSize ().ToString ()}";
+                                 };
+
+        editor.Initialized += (s, e) =>
+                              {
+                                  scrollBar.Size = int.Max (app.GetContentSize ().Height * 2, app.GetContentSize ().Width * 2);
+                                  editor.ViewToEdit = scrollBar;
+                              };
+
+        app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags;
+
+        Application.Run (app);
+        app.Dispose ();
+        Application.Shutdown ();
+    }
+}