diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs
index 0950f872bb..6aa2088e5a 100644
--- a/Terminal.Gui/Drawing/Thickness.cs
+++ b/Terminal.Gui/Drawing/Thickness.cs
@@ -236,7 +236,7 @@ public Rectangle GetInside (Rectangle rect)
}
///
- /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and rigth sides
+ /// Gets the total width of the left and right sides of the rectangle. Sets the width of the left and right sides
/// of the rectangle to half the specified value.
///
public int Horizontal
diff --git a/Terminal.Gui/Input/Responder.cs b/Terminal.Gui/Input/Responder.cs
deleted file mode 100644
index 43cc082403..0000000000
--- a/Terminal.Gui/Input/Responder.cs
+++ /dev/null
@@ -1,89 +0,0 @@
-using System.Reflection;
-
-namespace Terminal.Gui;
-
-/// Responder base class implemented by objects that want to participate on keyboard and mouse input.
-public class Responder : IDisposable
-{
- private bool _disposedValue;
-
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
-
- public void Dispose ()
- {
- // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
- Disposing?.Invoke (this, EventArgs.Empty);
- Dispose (true);
- GC.SuppressFinalize (this);
-#if DEBUG_IDISPOSABLE
- WasDisposed = true;
-
- foreach (Responder instance in Instances.Where (x => x.WasDisposed).ToList ())
- {
- Instances.Remove (instance);
- }
-#endif
- }
-
- /// Event raised when has been called to signal that this object is being disposed.
- [CanBeNull]
- public event EventHandler Disposing;
-
- /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
- ///
- /// If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and
- /// unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from
- /// inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
- ///
- ///
- protected virtual void Dispose (bool disposing)
- {
- if (!_disposedValue)
- {
- if (disposing)
- {
- // TODO: dispose managed state (managed objects)
- }
-
- _disposedValue = true;
- }
- }
-
- // TODO: v2 - nuke this
- /// Utilty function to determine is overridden in the .
- /// The view.
- /// The method name.
- /// if it's overridden, otherwise.
- internal static bool IsOverridden (Responder subclass, string method)
- {
- MethodInfo m = subclass.GetType ()
- .GetMethod (
- method,
- BindingFlags.Instance
- | BindingFlags.Public
- | BindingFlags.NonPublic
- | BindingFlags.DeclaredOnly
- );
-
- if (m is null)
- {
- return false;
- }
-
- return m.GetBaseDefinition ().DeclaringType != m.DeclaringType;
- }
-
-#if DEBUG_IDISPOSABLE
- /// For debug purposes to verify objects are being disposed properly
- public bool WasDisposed;
-
- /// For debug purposes to verify objects are being disposed properly
- public int DisposedCount = 0;
-
- /// For debug purposes
- public static List Instances = new ();
-
- /// For debug purposes
- public Responder () { Instances.Add (this); }
-#endif
-}
diff --git a/Terminal.Gui/View/View.Content.cs b/Terminal.Gui/View/View.Content.cs
index 5f2818c783..d1bfafc794 100644
--- a/Terminal.Gui/View/View.Content.cs
+++ b/Terminal.Gui/View/View.Content.cs
@@ -218,6 +218,7 @@ public ViewportSettings ViewportSettings
{
// Force set Viewport to cause settings to be applied as needed
SetViewport (Viewport);
+ SetScrollBarsKeepContentInAllViewport (_viewportSettings);
}
}
}
@@ -325,6 +326,10 @@ private void SetViewport (Rectangle viewport)
Size = newSize
};
+ OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
+
+ return;
+
void ApplySettings (ref Rectangle newViewport)
{
if (!ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth))
diff --git a/Terminal.Gui/View/View.Drawing.Clipping.cs b/Terminal.Gui/View/View.Drawing.Clipping.cs
index 2996751f93..30f8b703c6 100644
--- a/Terminal.Gui/View/View.Drawing.Clipping.cs
+++ b/Terminal.Gui/View/View.Drawing.Clipping.cs
@@ -104,7 +104,7 @@ public static void SetClip (Region? region)
if (this is Adornment adornment && adornment.Thickness != Thickness.Empty)
{
// Ensure adornments can't draw outside their thickness
- frameRegion.Exclude (adornment.Thickness.GetInside (Frame));
+ frameRegion.Exclude (adornment.Thickness.GetInside (FrameToScreen()));
}
SetClip (frameRegion);
diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs
index f317b5bcb1..67e18955e3 100644
--- a/Terminal.Gui/View/View.Layout.cs
+++ b/Terminal.Gui/View/View.Layout.cs
@@ -96,11 +96,23 @@ private bool SetFrame (in Rectangle frame)
SetNeedsLayout ();
// BUGBUG: When SetFrame is called from Frame_set, this event gets raised BEFORE OnResizeNeeded. Is that OK?
- OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport));
-
+ OnFrameChanged (in frame);
+ FrameChanged?.Invoke (this, new (in frame));
return true;
}
+ ///
+ /// Called when changes.
+ ///
+ /// The new Frame.
+ protected virtual void OnFrameChanged (in Rectangle frame) { }
+
+ ///
+ /// Raised when the changes. This event is raised after the has been
+ /// updated.
+ ///
+ public event EventHandler>? FrameChanged;
+
/// Gets the with a screen-relative location.
/// The location and size of the view in screen-relative coordinates.
public virtual Rectangle FrameToScreen ()
diff --git a/Terminal.Gui/View/View.ScrollBars.cs b/Terminal.Gui/View/View.ScrollBars.cs
new file mode 100644
index 0000000000..d3e56344fd
--- /dev/null
+++ b/Terminal.Gui/View/View.ScrollBars.cs
@@ -0,0 +1,215 @@
+#nullable enable
+namespace Terminal.Gui;
+
+public partial class View
+{
+ private Lazy _horizontalScrollBar;
+ private Lazy _verticalScrollBar;
+
+ ///
+ /// Initializes the ScrollBars of the View. Called by the constructor.
+ ///
+ private void SetupScrollBars ()
+ {
+ _horizontalScrollBar = new (
+ () =>
+ {
+ var scrollBar = new ScrollBar
+ {
+ Orientation = Orientation.Horizontal,
+ X = 0,
+ Y = Pos.AnchorEnd (),
+ Width = Dim.Fill (
+ Dim.Func (
+ () =>
+ {
+ if (_verticalScrollBar.IsValueCreated)
+ {
+ return _verticalScrollBar.Value.Visible ? 1 : 0;
+ }
+
+ return 0;
+ })),
+ Size = GetContentSize ().Width,
+ Visible = false
+ };
+
+ Padding?.Add (scrollBar);
+
+ scrollBar.Initialized += (_, _) =>
+ {
+ Padding!.Thickness = Padding.Thickness with
+ {
+ Bottom = scrollBar.Visible ? Padding.Thickness.Bottom + 1 : 0
+ };
+
+ scrollBar.ContentPositionChanged += (_, args) =>
+ {
+ Viewport = Viewport with
+ {
+ X = Math.Min (
+ args.CurrentValue,
+ GetContentSize ().Width - (Viewport.Width))
+ };
+ };
+
+ scrollBar.VisibleChanged += (_, _) =>
+ {
+ Padding.Thickness = Padding.Thickness with
+ {
+ Bottom = scrollBar.Visible
+ ? Padding.Thickness.Bottom + 1
+ : Padding.Thickness.Bottom - 1
+ };
+ };
+ };
+
+ return scrollBar;
+ });
+
+ _verticalScrollBar = new (
+ () =>
+ {
+ var scrollBar = new ScrollBar
+ {
+ Orientation = Orientation.Vertical,
+ X = Pos.AnchorEnd (),
+ Y = Pos.Func (() => Padding.Thickness.Top),
+ Height = Dim.Fill (
+ Dim.Func (
+ () =>
+ {
+ if (_horizontalScrollBar.IsValueCreated)
+ {
+ return _horizontalScrollBar.Value.Visible ? 1 : 0;
+ }
+
+ return 0;
+ })),
+ Size = GetContentSize ().Height,
+ Visible = false
+ };
+
+ Padding?.Add (scrollBar);
+
+ scrollBar.Initialized += (_, _) =>
+ {
+ if (Padding is { })
+ {
+ Padding.Thickness = Padding.Thickness with
+ {
+ Right = scrollBar.Visible ? Padding.Thickness.Right + 1 : 0
+ };
+
+ scrollBar.ContentPositionChanged += (_, args) =>
+ {
+ Viewport = Viewport with
+ {
+ Y = Math.Min (
+ args.CurrentValue,
+ GetContentSize ().Height - (Viewport.Height))
+ };
+ };
+
+ scrollBar.VisibleChanged += (_, _) =>
+ {
+ Padding.Thickness = Padding.Thickness with
+ {
+ Right = scrollBar.Visible
+ ? Padding.Thickness.Right + 1
+ : Padding.Thickness.Right - 1
+ };
+ };
+ }
+ };
+
+ return scrollBar;
+ });
+
+ ViewportChanged += (_, _) =>
+ {
+ if (_verticalScrollBar.IsValueCreated)
+ {
+ _verticalScrollBar.Value.ContentPosition = Viewport.Y;
+ }
+
+ if (_horizontalScrollBar.IsValueCreated)
+ {
+ _horizontalScrollBar.Value.ContentPosition = Viewport.X;
+ }
+ };
+
+ ContentSizeChanged += (_, _) =>
+ {
+ if (_verticalScrollBar.IsValueCreated)
+ {
+ _verticalScrollBar.Value.Size = GetContentSize ().Height;
+ }
+ if (_horizontalScrollBar.IsValueCreated)
+ {
+ _horizontalScrollBar.Value.Size = GetContentSize ().Width;
+ }
+ };
+ }
+
+ ///
+ ///
+ public ScrollBar HorizontalScrollBar => _horizontalScrollBar.Value;
+
+ ///
+ ///
+ public ScrollBar VerticalScrollBar => _verticalScrollBar.Value;
+
+ ///
+ /// Clean up the ScrollBars of the View. Called by View.Dispose.
+ ///
+ private void DisposeScrollBars ()
+ {
+ if (_horizontalScrollBar.IsValueCreated)
+ {
+ Padding?.Remove (_horizontalScrollBar.Value);
+ _horizontalScrollBar.Value.Dispose ();
+ }
+
+ if (_verticalScrollBar.IsValueCreated)
+ {
+ Padding?.Remove (_verticalScrollBar.Value);
+ _verticalScrollBar.Value.Dispose ();
+ }
+ }
+
+ private void SetScrollBarsKeepContentInAllViewport (ViewportSettings viewportSettings)
+ {
+ if (viewportSettings == ViewportSettings.None)
+ {
+ _horizontalScrollBar.Value.KeepContentInAllViewport = true;
+ _verticalScrollBar.Value.KeepContentInAllViewport = true;
+ }
+ else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeX))
+ {
+ _horizontalScrollBar.Value.AutoHide = false;
+ }
+ else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeY))
+ {
+ _verticalScrollBar.Value.AutoHide = false;
+ }
+ else if (viewportSettings.HasFlag (ViewportSettings.AllowNegativeLocation))
+ {
+ _horizontalScrollBar.Value.AutoHide = false;
+ _verticalScrollBar.Value.AutoHide = false;
+ }
+ else if (viewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth))
+ {
+ _horizontalScrollBar.Value.KeepContentInAllViewport = false;
+ }
+ else if (viewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight))
+ {
+ _verticalScrollBar.Value.KeepContentInAllViewport = false;
+ }
+ else if (viewportSettings.HasFlag (ViewportSettings.AllowLocationGreaterThanContentSize))
+ {
+ _horizontalScrollBar.Value.KeepContentInAllViewport = false;
+ _verticalScrollBar.Value.KeepContentInAllViewport = false;
+ }
+ }
+}
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index 00e6df6ad4..7af099f79c 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -74,8 +74,10 @@ namespace Terminal.Gui;
/// To flag the entire view for redraw call .
///
///
-/// The method is called when the size or layout of a view has changed. The will
-/// cause to be called on the next so there is normally no reason to direclty call
+/// The method is called when the size or layout of a view has changed. The
+/// will
+/// cause to be called on the next so there is normally
+/// no reason to direclty call
/// see .
///
///
@@ -107,7 +109,7 @@ namespace Terminal.Gui;
#endregion API Docs
-public partial class View : Responder, ISupportInitializeNotification
+public partial class View : IDisposable, ISupportInitializeNotification
{
#region Constructors and Initialization
@@ -135,6 +137,10 @@ public partial class View : Responder, ISupportInitializeNotification
///
public View ()
{
+#if DEBUG_IDISPOSABLE
+ Instances.Add (this);
+#endif
+
SetupAdornments ();
SetupCommands ();
@@ -144,6 +150,8 @@ public View ()
//SetupMouse ();
SetupText ();
+
+ SetupScrollBars ();
}
///
@@ -359,9 +367,9 @@ public virtual bool Visible
VisibleChanged?.Invoke (this, EventArgs.Empty);
SetNeedsLayout ();
- SuperView?.SetNeedsLayout();
+ SuperView?.SetNeedsLayout ();
SetNeedsDraw ();
- SuperView?.SetNeedsDraw();
+ SuperView?.SetNeedsDraw ();
}
}
@@ -522,13 +530,22 @@ protected bool OnTitleChanging (ref string newTitle)
///
public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
- ///
- protected override void Dispose (bool disposing)
+ private bool _disposedValue;
+
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resources.
+ ///
+ /// If disposing equals true, the method has been called directly or indirectly by a user's code. Managed and
+ /// unmanaged resources can be disposed. If disposing equals false, the method has been called by the runtime from
+ /// inside the finalizer and you should not reference other objects. Only unmanaged resources can be disposed.
+ ///
+ ///
+ protected virtual void Dispose (bool disposing)
{
LineCanvas.Dispose ();
DisposeKeyboard ();
DisposeAdornments ();
+ DisposeScrollBars ();
for (int i = InternalSubviews.Count - 1; i >= 0; i--)
{
@@ -537,7 +554,49 @@ protected override void Dispose (bool disposing)
subview.Dispose ();
}
- base.Dispose (disposing);
+ if (!_disposedValue)
+ {
+ if (disposing)
+ {
+ // TODO: dispose managed state (managed objects)
+ }
+
+ _disposedValue = true;
+ }
+
Debug.Assert (InternalSubviews.Count == 0);
}
+
+ ///
+ /// Riased when the is being disposed.
+ ///
+ public event EventHandler? Disposing;
+
+ /// Performs application-defined tasks associated with freeing, releasing, or resetting unmanaged resource.
+ public void Dispose ()
+ {
+ // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method
+ Disposing?.Invoke (this, EventArgs.Empty);
+ Dispose (true);
+ GC.SuppressFinalize (this);
+#if DEBUG_IDISPOSABLE
+ WasDisposed = true;
+
+ foreach (View instance in Instances.Where (x => x.WasDisposed).ToList ())
+ {
+ Instances.Remove (instance);
+ }
+#endif
+ }
+
+#if DEBUG_IDISPOSABLE
+ /// For debug purposes to verify objects are being disposed properly
+ public bool WasDisposed { get; set; }
+
+ /// For debug purposes to verify objects are being disposed properly
+ public int DisposedCount { get; set; } = 0;
+
+ /// For debug purposes
+ public static List Instances { get; set; } = [];
+#endif
}
diff --git a/Terminal.Gui/View/ViewportSettings.cs b/Terminal.Gui/View/ViewportSettings.cs
index f7e8488c12..c3cc51ac07 100644
--- a/Terminal.Gui/View/ViewportSettings.cs
+++ b/Terminal.Gui/View/ViewportSettings.cs
@@ -97,10 +97,27 @@ public enum ViewportSettings
/// If set will clear only the portion of the content
/// area that is visible within the . This is useful for views that have a
/// content area larger than the Viewport and want the area outside the content to be visually distinct.
- ///
- /// must be set for this setting to work (clipping beyond the visible area must be
- /// disabled).
- ///
+ /// must be set for this setting to work (clipping beyond the visible area must be
+ /// disabled).
+ ///
+ ClearContentOnly = 32,
+
+ ///
+ /// If set, the vertical scroll bar (see ) will be enabled and automatically made visible
+ /// when the dimension of the is smaller than the dimension of .
+ ///
+ EnableHorizontalScrollBar = 64,
+
+ ///
+ /// If set, the vertical scroll bar (see ) will be enabled and automatically made visible
+ /// when the dimension of the is smaller than the dimension of .
+ ///
+ EnableVerticalScrollBar = 128,
+
+ ///
+ /// If set, the horizontal and vertical scroll bars (see cref="View.HorizontalScrollBar"/> and )
+ /// will be enabled and automatically made visible when the dimension of the is smaller than the
+ /// dimension of .
///
- ClearContentOnly = 32
+ EnableScrollBars = EnableHorizontalScrollBar | EnableVerticalScrollBar
}
diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs
new file mode 100644
index 0000000000..02ef593561
--- /dev/null
+++ b/Terminal.Gui/Views/Scroll/Scroll.cs
@@ -0,0 +1,397 @@
+#nullable enable
+
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+///
+/// Indicates the size of scrollable content and provides a visible element, referred to as the "ScrollSlider" that
+/// that is sized to
+/// show the proportion of the scrollable content to the size of the . The ScrollSlider
+/// can be dragged with the mouse. A Scroll can be oriented either vertically or horizontally and is used within a
+/// .
+///
+///
+///
+/// By default, this view cannot be focused and does not support keyboard.
+///
+///
+public class Scroll : View, IOrientation, IDesignable
+{
+ internal readonly ScrollSlider _slider;
+
+ ///
+ public Scroll ()
+ {
+ _slider = new ();
+ Add (_slider);
+ _slider.FrameChanged += OnSliderOnFrameChanged;
+
+ CanFocus = false;
+
+ _orientationHelper = new (this); // Do not use object initializer!
+ _orientationHelper.Orientation = Orientation.Vertical;
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
+ // This sets the width/height etc...
+ OnOrientationChanged (Orientation);
+ }
+
+ ///
+ protected override void OnSubviewLayout (LayoutEventArgs args)
+ {
+ if (ViewportDimension < 1)
+ {
+ _slider.Size = 1;
+
+ return;
+ }
+
+ _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size)), 1, ViewportDimension);
+ }
+
+ #region IOrientation members
+
+ private readonly OrientationHelper _orientationHelper;
+
+ ///
+ public Orientation Orientation
+ {
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
+ }
+
+ ///
+ public event EventHandler>? OrientationChanging;
+
+ ///
+ public event EventHandler>? OrientationChanged;
+
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
+ TextAlignment = Alignment.Center;
+ VerticalTextAlignment = Alignment.Center;
+
+ X = 0;
+ Y = 0;
+
+ if (Orientation == Orientation.Vertical)
+ {
+ Width = 1;
+ Height = Dim.Fill ();
+ }
+ else
+ {
+ Width = Dim.Fill ();
+ Height = 1;
+ }
+
+ _slider.Orientation = newOrientation;
+ }
+
+ #endregion
+
+ ///
+ /// Gets or sets whether the Scroll will show the percentage the slider
+ /// takes up within the .
+ ///
+ public bool ShowPercent
+ {
+ get => _slider.ShowPercent;
+ set => _slider.ShowPercent = value;
+ }
+
+ private int ViewportDimension => Orientation == Orientation.Vertical ? Viewport.Height : Viewport.Width;
+
+ private int _size;
+
+ ///
+ /// Gets or sets the total size of the content that can be scrolled.
+ ///
+ public int Size
+ {
+ get => _size;
+ set
+ {
+ if (value == _size || value < 0)
+ {
+ return;
+ }
+
+ _size = value;
+ OnSizeChanged (_size);
+ SizeChanged?.Invoke (this, new (in _size));
+ SetNeedsLayout ();
+ }
+ }
+
+ /// Called when has changed.
+ protected virtual void OnSizeChanged (int size) { }
+
+ /// Raised when has changed.
+ public event EventHandler>? SizeChanged;
+
+ #region SliderPosition
+ private void OnSliderOnFrameChanged (object? sender, EventArgs args)
+ {
+ if (ViewportDimension == 0)
+ {
+ return;
+ }
+
+ int framePos = Orientation == Orientation.Vertical ? args.CurrentValue.Y : args.CurrentValue.X;
+ SliderPosition = framePos;
+ }
+
+ ///
+ /// Gets or sets the position of the start of the Scroll slider, within the Viewport.
+ ///
+ public int SliderPosition
+ {
+ get => CalculateSliderPosition (_contentPosition);
+ set => RaiseSliderPositionChangeEvents (value);
+ }
+
+ private void RaiseSliderPositionChangeEvents (int newSliderPosition)
+ {
+ int currentSliderPosition = CalculateSliderPosition (_contentPosition);
+
+ if (/*newSliderPosition > Size - ViewportDimension ||*/ currentSliderPosition == newSliderPosition)
+ {
+ return;
+ }
+
+ if (OnSliderPositionChanging (currentSliderPosition, newSliderPosition))
+ {
+ return;
+ }
+
+ CancelEventArgs args = new (ref currentSliderPosition, ref newSliderPosition);
+ SliderPositionChanging?.Invoke (this, args);
+
+ if (args.Cancel)
+ {
+ return;
+ }
+
+ // This sets the slider position and clamps the value
+ _slider.Position = newSliderPosition;
+
+ ContentPosition = (int)Math.Round ((double)newSliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension));
+
+ OnSliderPositionChanged (newSliderPosition);
+ SliderPositionChanged?.Invoke (this, new (in newSliderPosition));
+ }
+
+ ///
+ /// Called when is changing. Return true to cancel the change.
+ ///
+ protected virtual bool OnSliderPositionChanging (int currentSliderPosition, int newSliderPosition) { return false; }
+
+ ///
+ /// Raised when the is changing. Set to
+ /// to prevent the position from being changed.
+ ///
+ public event EventHandler>? SliderPositionChanging;
+
+ /// Called when has changed.
+ protected virtual void OnSliderPositionChanged (int position) { }
+
+ /// Raised when the has changed.
+ public event EventHandler>? SliderPositionChanged;
+
+ private int CalculateSliderPosition (int contentPosition)
+ {
+ if (Size - ViewportDimension == 0)
+ {
+ return 0;
+ }
+
+ return (int)Math.Round ((double)contentPosition / (Size - ViewportDimension) * (ViewportDimension - _slider.Size));
+ }
+
+ #endregion SliderPosition
+
+ #region ContentPosition
+
+ private int _contentPosition;
+
+ ///
+ /// Gets or sets the position of the ScrollSlider within the range of 0....
+ ///
+ public int ContentPosition
+ {
+ get => _contentPosition;
+ set
+ {
+ if (value == _contentPosition)
+ {
+ return;
+ }
+
+ RaiseContentPositionChangeEvents (value);
+ }
+ }
+
+ private void RaiseContentPositionChangeEvents (int newContentPosition)
+ {
+ // Clamp the value between 0 and Size - ViewportDimension
+ newContentPosition = (int)Math.Clamp (newContentPosition, 0, Math.Max (0, Size - ViewportDimension));
+
+ if (OnContentPositionChanging (_contentPosition, newContentPosition))
+ {
+ return;
+ }
+
+ CancelEventArgs args = new (ref _contentPosition, ref newContentPosition);
+ ContentPositionChanging?.Invoke (this, args);
+
+ if (args.Cancel)
+ {
+ return;
+ }
+
+ _contentPosition = newContentPosition;
+
+ SliderPosition = CalculateSliderPosition (_contentPosition);
+
+ OnContentPositionChanged (_contentPosition);
+ ContentPositionChanged?.Invoke (this, new (in _contentPosition));
+ }
+
+ ///
+ /// Called when is changing. Return true to cancel the change.
+ ///
+ protected virtual bool OnContentPositionChanging (int currentPos, int newPos) { return false; }
+
+ ///
+ /// Raised when the is changing. Set to
+ /// to prevent the position from being changed.
+ ///
+ public event EventHandler>? ContentPositionChanging;
+
+ /// Called when has changed.
+ protected virtual void OnContentPositionChanged (int position) { }
+
+ /// Raised when the has changed.
+ public event EventHandler>? ContentPositionChanged;
+
+ #endregion ContentPosition
+
+ ///
+ protected override bool OnClearingViewport ()
+ {
+ FillRect (Viewport, Glyphs.Stipple);
+
+ return true;
+ }
+
+ ///
+ protected override bool OnMouseClick (MouseEventArgs args)
+ {
+ if (!args.IsSingleClicked)
+ {
+ return false;
+ }
+
+ if (Orientation == Orientation.Vertical)
+ {
+ // If the position is w/in the slider frame ignore
+ if (args.Position.Y >= _slider.Frame.Y && args.Position.Y < _slider.Frame.Y + _slider.Frame.Height)
+ {
+ return false;
+ }
+
+ SliderPosition = args.Position.Y;
+ }
+ else
+ {
+ // If the position is w/in the slider frame ignore
+ if (args.Position.X >= _slider.Frame.X && args.Position.X < _slider.Frame.X + _slider.Frame.Width)
+ {
+ return false;
+ }
+
+ SliderPosition = args.Position.X;
+ }
+
+ return true;
+ }
+
+ ///
+ /// Gets or sets the amount each mouse hweel event will incremenet/decrement the .
+ ///
+ ///
+ /// The default is 1.
+ ///
+ public int Increment { get; set; } = 1;
+
+ ///
+ protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
+ {
+ if (SuperView is null)
+ {
+ return false;
+ }
+
+ if (!mouseEvent.IsWheel)
+ {
+ return false;
+ }
+
+ if (Orientation == Orientation.Vertical)
+ {
+ if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown))
+ {
+ ContentPosition += Increment;
+ }
+
+ if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledUp))
+ {
+ ContentPosition -= Increment;
+ }
+ }
+ else
+ {
+ if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight))
+ {
+ ContentPosition += Increment;
+ }
+
+ if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft))
+ {
+ ContentPosition -= Increment;
+ }
+ }
+
+ return true;
+ }
+
+ ///
+ public bool EnableForDesign ()
+ {
+ OrientationChanged += (sender, args) =>
+ {
+ if (args.CurrentValue == Orientation.Vertical)
+ {
+ Width = 1;
+ Height = Dim.Fill ();
+ }
+ else
+ {
+ Width = Dim.Fill ();
+ Height = 1;
+ }
+ };
+
+ Width = 1;
+ Height = Dim.Fill ();
+ Size = 1000;
+ ContentPosition = 10;
+
+ return true;
+ }
+}
diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs
new file mode 100644
index 0000000000..7bf0e1ea68
--- /dev/null
+++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs
@@ -0,0 +1,319 @@
+#nullable enable
+
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+///
+/// Provides a visual indicator that content can be scrolled. ScrollBars consist of two buttons, one each for scrolling
+/// forward or backwards, a that can be dragged
+/// to scroll continuously. ScrollBars can be oriented either horizontally or vertically and support the user dragging
+/// and clicking with the mouse to scroll.
+///
+///
+///
+/// indicates the number of rows or columns the Scroll has moved from 0.
+///
+///
+public class ScrollBar : View, IOrientation, IDesignable
+{
+ private readonly Scroll _scroll;
+ private readonly Button _decreaseButton;
+ private readonly Button _increaseButton;
+
+ ///
+ public ScrollBar ()
+ {
+ CanFocus = false;
+
+ _scroll = new ();
+ _scroll.SliderPositionChanging += OnScrollOnSliderPositionChanging;
+ _scroll.SliderPositionChanged += OnScrollOnSliderPositionChanged;
+ _scroll.ContentPositionChanging += OnScrollOnContentPositionChanging;
+ _scroll.ContentPositionChanged += OnScrollOnContentPositionChanged;
+ _scroll.SizeChanged += OnScrollOnSizeChanged;
+
+ _decreaseButton = new ()
+ {
+ CanFocus = false,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ WantContinuousButtonPressed = true
+ };
+ _decreaseButton.Accepting += OnDecreaseButtonOnAccept;
+
+ _increaseButton = new ()
+ {
+ CanFocus = false,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ WantContinuousButtonPressed = true
+ };
+ _increaseButton.Accepting += OnIncreaseButtonOnAccept;
+ Add (_decreaseButton, _scroll, _increaseButton);
+
+ _orientationHelper = new (this); // Do not use object initializer!
+ _orientationHelper.Orientation = Orientation.Vertical;
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
+ // This sets the width/height etc...
+ OnOrientationChanged (Orientation);
+
+ return;
+
+ void OnDecreaseButtonOnAccept (object? s, CommandEventArgs e)
+ {
+ ContentPosition -= Increment;
+ e.Cancel = true;
+ }
+
+ void OnIncreaseButtonOnAccept (object? s, CommandEventArgs e)
+ {
+ ContentPosition += Increment;
+ e.Cancel = true;
+ }
+ }
+
+ #region IOrientation members
+
+ private readonly OrientationHelper _orientationHelper;
+
+ ///
+ public Orientation Orientation
+ {
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
+ }
+
+ ///
+ public event EventHandler>? OrientationChanging;
+
+ ///
+ public event EventHandler>? OrientationChanged;
+
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
+ TextAlignment = Alignment.Center;
+ VerticalTextAlignment = Alignment.Center;
+
+ if (Orientation == Orientation.Vertical)
+ {
+ Width = 1;
+ Height = Dim.Fill ();
+ }
+ else
+ {
+ Width = Dim.Fill ();
+ Height = 1;
+ }
+
+ _scroll.Orientation = newOrientation;
+ }
+
+ #endregion
+
+ private bool _autoHide = true;
+
+ ///
+ /// Gets or sets whether will be set to if the dimension of the
+ /// scroll bar is greater than or equal to .
+ ///
+ public bool AutoHide
+ {
+ get => _autoHide;
+ set
+ {
+ if (_autoHide != value)
+ {
+ _autoHide = value;
+
+ if (!AutoHide)
+ {
+ Visible = true;
+ }
+
+ SetNeedsLayout ();
+ }
+ }
+ }
+
+ ///
+ protected override void OnFrameChanged (in Rectangle frame) { ShowHide (); }
+
+ private void ShowHide ()
+ {
+ if (!AutoHide || !IsInitialized)
+ {
+ return;
+ }
+
+ if (Orientation == Orientation.Vertical)
+ {
+ Visible = Frame.Height - (_decreaseButton.Frame.Height + _increaseButton.Frame.Height) < Size;
+ }
+ else
+ {
+ Visible = Frame.Width - (_decreaseButton.Frame.Width + _increaseButton.Frame.Width) < Size;
+ }
+ }
+
+ ///
+ /// Gets or sets whether the Scroll will show the percentage the slider
+ /// takes up within the .
+ ///
+ public bool ShowPercent
+ {
+ get => _scroll.ShowPercent;
+ set => _scroll.ShowPercent = value;
+ }
+
+
+ /// Get or sets if the view-port is kept in all visible area of this .
+ public bool KeepContentInAllViewport
+ {
+ //get => _scroll.KeepContentInAllViewport;
+ //set => _scroll.KeepContentInAllViewport = value;
+ get;
+ set;
+ }
+
+ /// Gets or sets the position of the slider within the ScrollBar's Viewport.
+ /// The position.
+ public int SliderPosition
+ {
+ get => _scroll.SliderPosition;
+ set => _scroll.SliderPosition = value;
+ }
+
+ private void OnScrollOnSliderPositionChanging (object? sender, CancelEventArgs e) { SliderPositionChanging?.Invoke (this, e); }
+ private void OnScrollOnSliderPositionChanged (object? sender, EventArgs e) { SliderPositionChanged?.Invoke (this, e); }
+
+ ///
+ /// Raised when the is changing. Set to
+ /// to prevent the position from being changed.
+ ///
+ public event EventHandler>? SliderPositionChanging;
+
+ /// Raised when the has changed.
+ public event EventHandler>? SliderPositionChanged;
+
+
+ ///
+ /// Gets or sets the size of the Scroll. This is the total size of the content that can be scrolled through.
+ ///
+ public int Size
+ {
+ get => _scroll.Size;
+ set => _scroll.Size = value;
+ }
+
+ ///
+ /// Gets or sets the position of the ScrollSlider within the range of 0....
+ ///
+ public int ContentPosition
+ {
+ get => _scroll.ContentPosition;
+ set => _scroll.ContentPosition = value;
+ }
+
+ private void OnScrollOnContentPositionChanging (object? sender, CancelEventArgs e) { ContentPositionChanging?.Invoke (this, e); }
+ private void OnScrollOnContentPositionChanged (object? sender, EventArgs e) { ContentPositionChanged?.Invoke (this, e); }
+
+ ///
+ /// Raised when the is changing. Set to
+ /// to prevent the position from being changed.
+ ///
+ public event EventHandler>? ContentPositionChanging;
+
+ /// Raised when the has changed.
+ public event EventHandler>? ContentPositionChanged;
+
+ /// Raised when has changed.
+ public event EventHandler>? SizeChanged;
+
+ private void OnScrollOnSizeChanged (object? sender, EventArgs e)
+ {
+ ShowHide ();
+ SizeChanged?.Invoke (this, e);
+ }
+
+ ///
+ /// Gets or sets the amount each click of the increment/decrement buttons and each
+ /// mouse wheel event will incremenet/decrement the .
+ ///
+ ///
+ /// The default is 1.
+ ///
+ public int Increment { get => _scroll.Increment; set => _scroll.Increment = value; }
+
+ ///
+ protected override void OnSubviewLayout (LayoutEventArgs args) { PositionSubviews (); }
+
+ private void PositionSubviews ()
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ _decreaseButton.Y = 0;
+ _decreaseButton.X = 0;
+ _decreaseButton.Width = Dim.Fill ();
+ _decreaseButton.Height = 1;
+ _decreaseButton.Title = Glyphs.UpArrow.ToString ();
+ _increaseButton.Y = Pos.Bottom (_scroll);
+ _increaseButton.X = 0;
+ _increaseButton.Width = Dim.Fill ();
+ _increaseButton.Height = 1;
+ _increaseButton.Title = Glyphs.DownArrow.ToString ();
+ _scroll.X = 0;
+ _scroll.Y = Pos.Bottom (_decreaseButton);
+ _scroll.Height = Dim.Fill (1);
+ _scroll.Width = Dim.Fill ();
+ }
+ else
+ {
+ _decreaseButton.Y = 0;
+ _decreaseButton.X = 0;
+ _decreaseButton.Width = 1;
+ _decreaseButton.Height = Dim.Fill ();
+ _decreaseButton.Title = Glyphs.LeftArrow.ToString ();
+ _increaseButton.Y = 0;
+ _increaseButton.X = Pos.Right (_scroll);
+ _increaseButton.Width = 1;
+ _increaseButton.Height = Dim.Fill ();
+ _increaseButton.Title = Glyphs.RightArrow.ToString ();
+ _scroll.Y = 0;
+ _scroll.X = Pos.Right (_decreaseButton);
+ _scroll.Width = Dim.Fill (1);
+ _scroll.Height = Dim.Fill ();
+ }
+ }
+
+ ///
+ public bool EnableForDesign ()
+ {
+ OrientationChanged += (sender, args) =>
+ {
+ if (args.CurrentValue == Orientation.Vertical)
+ {
+ Width = 1;
+ Height = Dim.Fill ();
+ }
+ else
+ {
+ Width = Dim.Fill ();
+ Height = 1;
+ }
+ };
+
+ Width = 1;
+ Height = Dim.Fill ();
+ Size = 200;
+ SliderPosition = 10;
+ //ShowPercent = true;
+ return true;
+ }
+}
diff --git a/Terminal.Gui/Views/Scroll/ScrollSlider.cs b/Terminal.Gui/Views/Scroll/ScrollSlider.cs
new file mode 100644
index 0000000000..a3e58beddd
--- /dev/null
+++ b/Terminal.Gui/Views/Scroll/ScrollSlider.cs
@@ -0,0 +1,282 @@
+#nullable enable
+
+using System.Diagnostics;
+
+namespace Terminal.Gui;
+
+///
+/// The ScrollSlider can be dragged with the mouse, constrained by the size of the Viewport of it's superview. The ScrollSlider can be
+/// oriented either vertically or horizontally.
+///
+///
+///
+/// If is set, it will be displayed centered within the slider. Set
+/// to automatically have the Text
+/// be show what percent the slider is to the Superview's Viewport size.
+///
+///
+/// Used to represent the proportion of the visible content to the Viewport in a .
+///
+///
+public class ScrollSlider : View, IOrientation, IDesignable
+{
+ ///
+ /// Initializes a new instance.
+ ///
+ public ScrollSlider ()
+ {
+ Id = "scrollSlider";
+ WantMousePositionReports = true;
+
+ _orientationHelper = new (this); // Do not use object initializer!
+ _orientationHelper.Orientation = Orientation.Vertical;
+ _orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
+ _orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
+
+ OnOrientationChanged (Orientation);
+
+ HighlightStyle = HighlightStyle.Hover;
+
+ // Default size is 1
+ Size = 1;
+ }
+
+ #region IOrientation members
+ private readonly OrientationHelper _orientationHelper;
+
+ ///
+ public Orientation Orientation
+ {
+ get => _orientationHelper.Orientation;
+ set => _orientationHelper.Orientation = value;
+ }
+
+ ///
+ public event EventHandler>? OrientationChanging;
+
+ ///
+ public event EventHandler>? OrientationChanged;
+
+ ///
+ public void OnOrientationChanged (Orientation newOrientation)
+ {
+ TextDirection = Orientation == Orientation.Vertical ? TextDirection.TopBottom_LeftRight : TextDirection.LeftRight_TopBottom;
+ TextAlignment = Alignment.Center;
+ VerticalTextAlignment = Alignment.Center;
+
+ // Reset Position to 0 when changing orientation
+ X = 0;
+ Y = 0;
+
+ // Reset Size to 1 when changing orientation
+ if (Orientation == Orientation.Vertical)
+ {
+ Width = Dim.Fill ();
+ Height = 1;
+ }
+ else
+ {
+ Width = 1;
+ Height = Dim.Fill ();
+ }
+ }
+
+ #endregion
+
+ ///
+ protected override bool OnClearingViewport ()
+ {
+ FillRect (Viewport, Glyphs.ContinuousMeterSegment);
+
+ return true;
+ }
+
+ private bool _showPercent;
+
+ ///
+ /// Gets or sets whether the ScrollSlider will set to show the percentage the slider
+ /// takes up within the 's Viewport.
+ ///
+ public bool ShowPercent
+ {
+ get => _showPercent;
+ set
+ {
+ _showPercent = value;
+ SetNeedsDraw();
+ }
+ }
+
+ ///
+ /// Gets or sets the size of the ScrollSlider. This is a helper that simply gets or sets the Width or Height depending on the
+ /// . The size will be constrained such that the ScrollSlider will not go outside the Viewport of
+ /// the . The size will never be less than 1.
+ ///
+ ///
+ ///
+ /// The dimension of the ScrollSlider that is perpendicular to the will be set to
+ ///
+ ///
+ public int Size
+ {
+ get
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ return Frame.Height;
+ }
+ else
+ {
+ return Frame.Width;
+ }
+ }
+ set
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ Width = Dim.Fill ();
+ int viewport = Math.Max (1, SuperView?.Viewport.Height ?? 1);
+ Height = Math.Clamp (value, 1, viewport);
+ }
+ else
+ {
+ int viewport = Math.Max (1, SuperView?.Viewport.Width ?? 1);
+ Width = Math.Clamp (value, 1, viewport);
+ Height = Dim.Fill ();
+ }
+ }
+ }
+
+ ///
+ /// Gets or sets the position of the ScrollSlider relative to the size of the ScrollSlider's Frame. This is a helper that simply gets or sets the X or Y depending on the
+ /// . The position will be constrained such that the ScrollSlider will not go outside the Viewport of
+ /// the .
+ ///
+ public int Position
+ {
+ get
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ return Frame.Y;
+ }
+ else
+ {
+ return Frame.X;
+ }
+ }
+ set
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ int viewport = Math.Max (1, SuperView?.Viewport.Height ?? 1);
+ Y = Math.Clamp (value, 0, viewport - Frame.Height);
+ }
+ else
+ {
+ int viewport = Math.Max (1, SuperView?.Viewport.Width ?? 1);
+ X = Math.Clamp (value, 0, viewport - Frame.Width);
+ }
+ }
+ }
+
+ ///
+ protected override bool OnDrawingText ()
+ {
+ if (!ShowPercent)
+ {
+ Text = string.Empty;
+
+ return false;
+ }
+
+ if (SuperView is null)
+ {
+ return false;
+ }
+
+ if (Orientation == Orientation.Vertical)
+ {
+ Text = $"{(int)Math.Round ((double)Viewport.Height / SuperView!.GetContentSize ().Height * 100)}%";
+ }
+ else
+ {
+ Text = $"{(int)Math.Round ((double)Viewport.Width / SuperView!.GetContentSize ().Width * 100)}%";
+ }
+
+ return false;
+ }
+
+ ///
+ public override Attribute GetNormalColor () { return base.GetHotNormalColor (); }
+
+ /////
+ private int _lastLocation = -1;
+
+ ///
+ protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
+ {
+ if (SuperView is null)
+ {
+ return false;
+ }
+
+ int location = Orientation == Orientation.Vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
+ int offset = _lastLocation > -1 ? location - _lastLocation : 0;
+ int superViewDimension = Orientation == Orientation.Vertical ? SuperView!.Viewport.Height : SuperView!.Viewport.Width;
+
+ if (mouseEvent.IsPressed || mouseEvent.IsReleased)
+ {
+ if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) && _lastLocation == -1)
+ {
+ if (Application.MouseGrabView != this)
+ {
+ Application.GrabMouse (this);
+ _lastLocation = location;
+ }
+ }
+ else if (mouseEvent.Flags == (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
+ {
+ if (Orientation == Orientation.Vertical)
+ {
+ Y = Frame.Y + offset < 0
+ ? 0
+ : Frame.Y + offset + Frame.Height > superViewDimension
+ ? Math.Max (superViewDimension - Frame.Height, 0)
+ : Frame.Y + offset;
+ }
+ else
+ {
+ X = Frame.X + offset < 0
+ ? 0
+ : Frame.X + offset + Frame.Width > superViewDimension
+ ? Math.Max (superViewDimension - Frame.Width, 0)
+ : Frame.X + offset;
+ }
+ }
+ else if (mouseEvent.Flags == MouseFlags.Button1Released)
+ {
+ _lastLocation = -1;
+
+ if (Application.MouseGrabView == this)
+ {
+ Application.UngrabMouse ();
+ }
+ }
+ return true;
+ }
+ return false;
+
+ }
+
+ ///
+ public bool EnableForDesign ()
+ {
+ Orientation = Orientation.Vertical;
+ Width = 1;
+ Height = 10;
+ ShowPercent = true;
+
+ return true;
+ }
+}
diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs
deleted file mode 100644
index 8873e5ba78..0000000000
--- a/Terminal.Gui/Views/ScrollBarView.cs
+++ /dev/null
@@ -1,1086 +0,0 @@
-//
-// ScrollBarView.cs: ScrollBarView view.
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
-
-using System.Diagnostics;
-
-namespace Terminal.Gui;
-
-/// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical
-///
-///
-/// The scrollbar is drawn to be a representation of the Size, assuming that the scroll position is set at
-/// Position.
-///
-/// If the region to display the scrollbar is larger than three characters, arrow indicators are drawn.
-///
-public class ScrollBarView : View
-{
- private bool _autoHideScrollBars = true;
- private View _contentBottomRightCorner;
- private bool _hosted;
- private bool _keepContentAlwaysInViewport = true;
- private int _lastLocation = -1;
- private ScrollBarView _otherScrollBarView;
- private int _posBarOffset;
- private int _posBottomTee;
- private int _posLeftTee;
- private int _posRightTee;
- private int _posTopTee;
- private bool _showScrollIndicator;
- private int _size, _position;
- private bool _vertical;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public ScrollBarView ()
- {
- WantContinuousButtonPressed = true;
-
- Added += (s, e) => CreateBottomRightCorner (e.SuperView);
- Initialized += ScrollBarView_Initialized;
- }
-
- ///
- /// Initializes a new instance of the class.
- ///
- /// The view that will host this scrollbar.
- /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
- ///
- /// If set to true (default) will have the other scrollbar, otherwise will
- /// have only one.
- ///
- public ScrollBarView (View host, bool isVertical, bool showBothScrollIndicator = true)
- {
- if (host is null)
- {
- throw new ArgumentNullException ("The host parameter can't be null.");
- }
-
- if (host.SuperView is null)
- {
- throw new ArgumentNullException ("The host SuperView parameter can't be null.");
- }
-
- _hosted = true;
- IsVertical = isVertical;
- ColorScheme = host.ColorScheme;
- X = isVertical ? Pos.Right (host) - 1 : Pos.Left (host);
- Y = isVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
- Host = host;
- CanFocus = false;
- Enabled = host.Enabled;
- Visible = host.Visible;
- Initialized += ScrollBarView_Initialized;
-
- //Host.CanFocusChanged += Host_CanFocusChanged;
- Host.EnabledChanged += Host_EnabledChanged;
- Host.VisibleChanged += Host_VisibleChanged;
- Host.SuperView.Add (this);
- AutoHideScrollBars = true;
-
- if (showBothScrollIndicator)
- {
- OtherScrollBarView = new ScrollBarView
- {
- IsVertical = !isVertical,
- ColorScheme = host.ColorScheme,
- Host = host,
- CanFocus = false,
- Enabled = host.Enabled,
- Visible = host.Visible,
- OtherScrollBarView = this
- };
- OtherScrollBarView._hosted = true;
- OtherScrollBarView.X = OtherScrollBarView.IsVertical ? Pos.Right (host) - 1 : Pos.Left (host);
- OtherScrollBarView.Y = OtherScrollBarView.IsVertical ? Pos.Top (host) : Pos.Bottom (host) - 1;
- OtherScrollBarView.Host.SuperView.Add (OtherScrollBarView);
- OtherScrollBarView.ShowScrollIndicator = true;
- }
-
- ShowScrollIndicator = true;
- CreateBottomRightCorner (Host);
- }
-
- /// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
- public bool AutoHideScrollBars
- {
- get => _autoHideScrollBars;
- set
- {
- if (_autoHideScrollBars != value)
- {
- _autoHideScrollBars = value;
- SetNeedsDraw ();
- }
- }
- }
-
- // BUGBUG: v2 - for consistency this should be named "Parent" not "Host"
- /// Get or sets the view that host this
- public View Host { get; internal set; }
-
- /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal.
- public bool IsVertical
- {
- get => _vertical;
- set
- {
- _vertical = value;
-
- if (IsInitialized)
- {
- SetWidthHeight ();
- }
- }
- }
-
- /// Get or sets if the view-port is kept always visible in the area of this
- public bool KeepContentAlwaysInViewport
- {
- get => _keepContentAlwaysInViewport;
- set
- {
- if (_keepContentAlwaysInViewport != value)
- {
- _keepContentAlwaysInViewport = value;
- var pos = 0;
-
- if (value && !_vertical && _position + Host.Viewport.Width > _size)
- {
- pos = _size - Host.Viewport.Width + (_showBothScrollIndicator ? 1 : 0);
- }
-
- if (value && _vertical && _position + Host.Viewport.Height > _size)
- {
- pos = _size - Host.Viewport.Height + (_showBothScrollIndicator ? 1 : 0);
- }
-
- if (pos != 0)
- {
- Position = pos;
- }
-
- if (OtherScrollBarView is { } && OtherScrollBarView._keepContentAlwaysInViewport != value)
- {
- OtherScrollBarView.KeepContentAlwaysInViewport = value;
- }
-
- if (pos == 0)
- {
- Refresh ();
- }
- }
- }
- }
-
- /// Represent a vertical or horizontal ScrollBarView other than this.
- public ScrollBarView OtherScrollBarView
- {
- get => _otherScrollBarView;
- set
- {
- if (value is { } && ((value.IsVertical && _vertical) || (!value.IsVertical && !_vertical)))
- {
- throw new ArgumentException (
- $"There is already a {(_vertical ? "vertical" : "horizontal")} ScrollBarView."
- );
- }
-
- _otherScrollBarView = value;
- }
- }
-
- /// The position, relative to , to set the scrollbar at.
- /// The position.
- public int Position
- {
- get => _position;
- set
- {
- if (_position == value)
- {
- return;
- }
-
- SetPosition (value);
- }
- }
-
- // BUGBUG: v2 - Why can't we get rid of this and just use Visible?
- /// Gets or sets the visibility for the vertical or horizontal scroll indicator.
- /// true if show vertical or horizontal scroll indicator; otherwise, false.
- public bool ShowScrollIndicator
- {
- get => _showScrollIndicator && Visible;
- set
- {
- //if (value == showScrollIndicator) {
- // return;
- //}
-
- _showScrollIndicator = value;
-
- if (IsInitialized)
- {
- SetNeedsLayout ();
-
- if (value)
- {
- Visible = true;
- }
- else
- {
- Visible = false;
- Position = 0;
- }
-
- SetWidthHeight ();
- }
- }
- }
-
- /// The size of content the scrollbar represents.
- /// The size.
- ///
- /// The is typically the size of the virtual content. E.g. when a Scrollbar is part of a
- /// the Size is set to the appropriate dimension of .
- ///
- public int Size
- {
- get => _size;
- set
- {
- _size = value;
-
- if (IsInitialized)
- {
- SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
- ShowHideScrollBars (false);
- SetNeedsLayout ();
- }
- }
- }
-
- private bool _showBothScrollIndicator => OtherScrollBarView?.ShowScrollIndicator == true && ShowScrollIndicator;
-
- /// This event is raised when the position on the scrollbar has changed.
- public event EventHandler ChangedPosition;
-
- ///
- protected override bool OnMouseEvent (MouseEventArgs mouseEvent)
- {
- if (mouseEvent.Flags != MouseFlags.Button1Pressed
- && mouseEvent.Flags != MouseFlags.Button1DoubleClicked
- && !mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition)
- && mouseEvent.Flags != MouseFlags.Button1Released
- && mouseEvent.Flags != MouseFlags.WheeledDown
- && mouseEvent.Flags != MouseFlags.WheeledUp
- && mouseEvent.Flags != MouseFlags.WheeledRight
- && mouseEvent.Flags != MouseFlags.WheeledLeft
- && mouseEvent.Flags != MouseFlags.Button1TripleClicked)
- {
- return false;
- }
-
- if (!Host.CanFocus)
- {
- return true;
- }
-
- if (Host?.HasFocus == false)
- {
- Host.SetFocus ();
- }
-
- int location = _vertical ? mouseEvent.Position.Y : mouseEvent.Position.X;
- int barsize = _vertical ? Viewport.Height : Viewport.Width;
- int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1;
- int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1;
- barsize -= 2;
- int pos = Position;
-
- if (mouseEvent.Flags != MouseFlags.Button1Released && (Application.MouseGrabView is null || Application.MouseGrabView != this))
- {
- Application.GrabMouse (this);
- }
- else if (mouseEvent.Flags == MouseFlags.Button1Released && Application.MouseGrabView is { } && Application.MouseGrabView == this)
- {
- _lastLocation = -1;
- Application.UngrabMouse ();
-
- return true;
- }
-
- if (ShowScrollIndicator
- && (mouseEvent.Flags == MouseFlags.WheeledDown
- || mouseEvent.Flags == MouseFlags.WheeledUp
- || mouseEvent.Flags == MouseFlags.WheeledRight
- || mouseEvent.Flags == MouseFlags.WheeledLeft))
- {
- return Host.NewMouseEvent (mouseEvent) == true;
- }
-
- if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == 0)
- {
- if (pos > 0)
- {
- Position = pos - 1;
- }
- }
- else if (mouseEvent.Flags == MouseFlags.Button1Pressed && location == barsize + 1)
- {
- if (CanScroll (1, out _, _vertical))
- {
- Position = pos + 1;
- }
- }
- else if (location > 0 && location < barsize + 1)
- {
- //var b1 = pos * (Size > 0 ? barsize / Size : 0);
- //var b2 = Size > 0
- // ? (KeepContentAlwaysInViewport ? Math.Min (((pos + barsize) * barsize / Size) + 1, barsize - 1) : (pos + barsize) * barsize / Size)
- // : 0;
- //if (KeepContentAlwaysInViewport && b1 == b2) {
- // b1 = Math.Max (b1 - 1, 0);
- //}
-
- if (_lastLocation > -1
- || (location >= posTopLeftTee
- && location <= posBottomRightTee
- && mouseEvent.Flags.HasFlag (
- MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition
- )))
- {
- if (_lastLocation == -1)
- {
- _lastLocation = location;
-
- _posBarOffset = _keepContentAlwaysInViewport
- ? Math.Max (location - posTopLeftTee, 1)
- : 0;
-
- return true;
- }
-
- if (location > _lastLocation)
- {
- if (location - _posBarOffset < barsize)
- {
- int np = (location - _posBarOffset) * Size / barsize + Size / barsize;
-
- if (CanScroll (np - pos, out int nv, _vertical))
- {
- Position = pos + nv;
- }
- }
- else if (CanScroll (Size - pos, out int nv, _vertical))
- {
- Position = Math.Min (pos + nv, Size);
- }
- }
- else if (location < _lastLocation)
- {
- if (location - _posBarOffset > 0)
- {
- int np = (location - _posBarOffset) * Size / barsize - Size / barsize;
-
- if (CanScroll (np - pos, out int nv, _vertical))
- {
- Position = pos + nv;
- }
- }
- else
- {
- Position = 0;
- }
- }
- else if (location - _posBarOffset >= barsize && posBottomRightTee - posTopLeftTee >= 3 && CanScroll (Size - pos, out int nv, _vertical))
- {
- Position = Math.Min (pos + nv, Size);
- }
- else if (location - _posBarOffset >= barsize - 1 && posBottomRightTee - posTopLeftTee <= 3 && CanScroll (Size - pos, out nv, _vertical))
- {
- Position = Math.Min (pos + nv, Size);
- }
- else if (location - _posBarOffset <= 0 && posBottomRightTee - posTopLeftTee <= 3)
- {
- Position = 0;
- }
- }
- else if (location > posBottomRightTee)
- {
- if (CanScroll (barsize, out int nv, _vertical))
- {
- Position = pos + nv;
- }
- }
- else if (location < posTopLeftTee)
- {
- if (CanScroll (-barsize, out int nv, _vertical))
- {
- Position = pos + nv;
- }
- }
- else if (location == 1 && posTopLeftTee <= 3)
- {
- Position = 0;
- }
- else if (location == barsize)
- {
- if (CanScroll (Size - pos, out int nv, _vertical))
- {
- Position = Math.Min (pos + nv, Size);
- }
- }
- }
-
- return true;
- }
-
- /// Virtual method to invoke the action event.
- public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); }
-
- ///
- protected override bool OnDrawingContent ()
- {
- if (ColorScheme is null || ((!ShowScrollIndicator || Size == 0) && AutoHideScrollBars && Visible))
- {
- if ((!ShowScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)
- {
- ShowHideScrollBars (false);
- }
-
- return false;
- }
-
- if (Size == 0 || (_vertical && Viewport.Height == 0) || (!_vertical && Viewport.Width == 0))
- {
- return false;
- }
-
- SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
-
- if (_vertical)
- {
- if (Viewport.Right < Viewport.Width - 1)
- {
- return true;
- }
-
- int col = Viewport.Width - 1;
- int bh = Viewport.Height;
- Rune special;
-
- if (bh < 4)
- {
- int by1 = _position * bh / Size;
- int by2 = (_position + bh) * bh / Size;
-
- Move (col, 0);
-
- if (Viewport.Height == 1)
- {
- Driver.AddRune (Glyphs.Diamond);
- }
- else
- {
- Driver.AddRune (Glyphs.UpArrow);
- }
-
- if (Viewport.Height == 3)
- {
- Move (col, 1);
- Driver.AddRune (Glyphs.Diamond);
- }
-
- if (Viewport.Height > 1)
- {
- Move (col, Viewport.Height - 1);
- Driver.AddRune (Glyphs.DownArrow);
- }
- }
- else
- {
- bh -= 2;
-
- int by1 = KeepContentAlwaysInViewport
- ? _position * bh / Size
- : _position * bh / (Size + bh);
-
- int by2 = KeepContentAlwaysInViewport
- ? Math.Min ((_position + bh) * bh / Size + 1, bh - 1)
- : (_position + bh) * bh / (Size + bh);
-
- if (KeepContentAlwaysInViewport && by1 == by2)
- {
- by1 = Math.Max (by1 - 1, 0);
- }
-
- AddRune (col, 0, Glyphs.UpArrow);
-
- var hasTopTee = false;
- var hasDiamond = false;
- var hasBottomTee = false;
-
- for (var y = 0; y < bh; y++)
- {
-
- if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee)))
- {
- special = Glyphs.Stipple;
- }
- else
- {
- if (y != by2 && y > 1 && by2 - by1 == 0 && by1 < bh - 1 && hasTopTee && !hasDiamond)
- {
- hasDiamond = true;
- special = Glyphs.Diamond;
- }
- else
- {
- if (y == by1 && !hasTopTee)
- {
- hasTopTee = true;
- _posTopTee = y;
- special = Glyphs.TopTee;
- }
- else if (((_position == 0 && y == bh - 1) || y >= by2 || by2 == 0) && !hasBottomTee)
- {
- hasBottomTee = true;
- _posBottomTee = y;
- special = Glyphs.BottomTee;
- }
- else
- {
- special = Glyphs.VLine;
- }
- }
- }
-
- AddRune (col, y + 1, special);
- }
-
- if (!hasTopTee)
- {
- AddRune (col, Viewport.Height - 2, Glyphs.TopTee);
- }
-
- AddRune (col, Viewport.Height - 1, Glyphs.DownArrow);
- }
- }
- else
- {
- if (Viewport.Bottom < Viewport.Height - 1)
- {
- return true;
- }
-
- int row = Viewport.Height - 1;
- int bw = Viewport.Width;
- Rune special;
-
- if (bw < 4)
- {
- int bx1 = _position * bw / Size;
- int bx2 = (_position + bw) * bw / Size;
-
- Move (0, row);
- Driver.AddRune (Glyphs.LeftArrow);
- Driver.AddRune (Glyphs.RightArrow);
- }
- else
- {
- bw -= 2;
-
- int bx1 = KeepContentAlwaysInViewport
- ? _position * bw / Size
- : _position * bw / (Size + bw);
-
- int bx2 = KeepContentAlwaysInViewport
- ? Math.Min ((_position + bw) * bw / Size + 1, bw - 1)
- : (_position + bw) * bw / (Size + bw);
-
- if (KeepContentAlwaysInViewport && bx1 == bx2)
- {
- bx1 = Math.Max (bx1 - 1, 0);
- }
-
- Move (0, row);
- Driver.AddRune (Glyphs.LeftArrow);
-
- var hasLeftTee = false;
- var hasDiamond = false;
- var hasRightTee = false;
-
- for (var x = 0; x < bw; x++)
- {
- if ((x < bx1 || x >= bx2 + 1) && ((_position > 0 && !hasLeftTee) || (hasLeftTee && hasRightTee)))
- {
- special = Glyphs.Stipple;
- }
- else
- {
- if (x != bx2 && x > 1 && bx2 - bx1 == 0 && bx1 < bw - 1 && hasLeftTee && !hasDiamond)
- {
- hasDiamond = true;
- special = Glyphs.Diamond;
- }
- else
- {
- if (x == bx1 && !hasLeftTee)
- {
- hasLeftTee = true;
- _posLeftTee = x;
- special = Glyphs.LeftTee;
- }
- else if (((_position == 0 && x == bw - 1) || x >= bx2 || bx2 == 0) && !hasRightTee)
- {
- hasRightTee = true;
- _posRightTee = x;
- special = Glyphs.RightTee;
- }
- else
- {
- special = Glyphs.HLine;
- }
- }
- }
-
- Driver.AddRune (special);
- }
-
- if (!hasLeftTee)
- {
- Move (Viewport.Width - 2, row);
- Driver.AddRune (Glyphs.LeftTee);
- }
-
- Driver.AddRune (Glyphs.RightArrow);
- }
- }
-
- return false;
- }
-
-
- /// Only used for a hosted view that will update and redraw the scrollbars.
- public virtual void Refresh () { ShowHideScrollBars (); }
-
- internal bool CanScroll (int n, out int max, bool isVertical = false)
- {
- if (Host?.Viewport.IsEmpty != false)
- {
- max = 0;
-
- return false;
- }
-
- int s = GetBarsize (isVertical);
- int newSize = Math.Max (Math.Min (_size - s, _position + n), 0);
- max = _size > s + newSize ? newSize == 0 ? -_position : n : _size - (s + _position) - 1;
-
- if (_size >= s + newSize && max != 0)
- {
- return true;
- }
-
- return false;
- }
-
- private bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false)
- {
- int barsize = scrollBarView._vertical ? scrollBarView.Viewport.Height : scrollBarView.Viewport.Width;
-
- if (barsize == 0 || barsize >= scrollBarView._size)
- {
- if (scrollBarView.ShowScrollIndicator)
- {
- scrollBarView.ShowScrollIndicator = false;
- }
-
- if (scrollBarView.Visible)
- {
- scrollBarView.Visible = false;
- }
- }
- else if (barsize > 0 && barsize == scrollBarView._size && scrollBarView.OtherScrollBarView is { } && pending)
- {
- if (scrollBarView.ShowScrollIndicator)
- {
- scrollBarView.ShowScrollIndicator = false;
- }
-
- if (scrollBarView.Visible)
- {
- scrollBarView.Visible = false;
- }
-
- if (scrollBarView.OtherScrollBarView is { } && scrollBarView._showBothScrollIndicator)
- {
- scrollBarView.OtherScrollBarView.ShowScrollIndicator = false;
- }
-
- if (scrollBarView.OtherScrollBarView.Visible)
- {
- scrollBarView.OtherScrollBarView.Visible = false;
- }
- }
- else if (barsize > 0 && barsize == _size && scrollBarView.OtherScrollBarView is { } && !pending)
- {
- pending = true;
- }
- else
- {
- if (scrollBarView.OtherScrollBarView is { } && pending)
- {
- if (!scrollBarView._showBothScrollIndicator)
- {
- scrollBarView.OtherScrollBarView.ShowScrollIndicator = true;
- }
-
- if (!scrollBarView.OtherScrollBarView.Visible)
- {
- scrollBarView.OtherScrollBarView.Visible = true;
- }
- }
-
- if (!scrollBarView.ShowScrollIndicator)
- {
- scrollBarView.ShowScrollIndicator = true;
- }
-
- if (!scrollBarView.Visible)
- {
- scrollBarView.Visible = true;
- }
- }
-
- return pending;
- }
-
- private void ContentBottomRightCorner_DrawContent (object sender, DrawEventArgs e)
- {
- SetAttribute (Host.HasFocus ? ColorScheme.Focus : GetNormalColor ());
-
- // I'm forced to do this here because the Clear method is
- // changing the color attribute and is different of this one
- Driver.FillRect (Driver.Clip.GetBounds());
- e.Cancel = true;
- }
-
- //private void Host_CanFocusChanged ()
- //{
- // CanFocus = Host.CanFocus;
- // if (otherScrollBarView is { }) {
- // otherScrollBarView.CanFocus = CanFocus;
- // }
- //}
-
- private void ContentBottomRightCorner_MouseClick (object sender, MouseEventArgs me)
- {
- if (me.Flags == MouseFlags.WheeledDown
- || me.Flags == MouseFlags.WheeledUp
- || me.Flags == MouseFlags.WheeledRight
- || me.Flags == MouseFlags.WheeledLeft)
- {
- NewMouseEvent (me);
- }
- else if (me.Flags == MouseFlags.Button1Clicked)
- {
- Host.SetFocus ();
- }
-
- me.Handled = true;
- }
-
- private void CreateBottomRightCorner (View host)
- {
- if (Host is null)
- {
- Host = host;
- }
-
- if (Host != null
- && ((_contentBottomRightCorner is null && OtherScrollBarView is null)
- || (_contentBottomRightCorner is null && OtherScrollBarView is { } && OtherScrollBarView._contentBottomRightCorner is null)))
- {
- _contentBottomRightCorner = new ContentBottomRightCorner { Visible = Host.Visible };
-
- if (_hosted)
- {
- Host.SuperView.Add (_contentBottomRightCorner);
- _contentBottomRightCorner.X = Pos.Right (Host) - 1;
- _contentBottomRightCorner.Y = Pos.Bottom (Host) - 1;
- }
- else
- {
- Host.Add (_contentBottomRightCorner);
- _contentBottomRightCorner.X = Pos.AnchorEnd (1);
- _contentBottomRightCorner.Y = Pos.AnchorEnd (1);
- }
-
- _contentBottomRightCorner.Width = 1;
- _contentBottomRightCorner.Height = 1;
- _contentBottomRightCorner.MouseClick += ContentBottomRightCorner_MouseClick;
- _contentBottomRightCorner.DrawingContent += ContentBottomRightCorner_DrawContent;
- }
- }
-
- private int GetBarsize (bool isVertical)
- {
- if (Host?.Viewport.IsEmpty != false)
- {
- return 0;
- }
-
- return isVertical ? KeepContentAlwaysInViewport
- ? Host.Viewport.Height + (_showBothScrollIndicator ? -2 : -1)
- : 0 :
- KeepContentAlwaysInViewport ? Host.Viewport.Width + (_showBothScrollIndicator ? -2 : -1) : 0;
- }
-
- private void Host_EnabledChanged (object sender, EventArgs e)
- {
- Enabled = Host.Enabled;
-
- if (_otherScrollBarView is { })
- {
- _otherScrollBarView.Enabled = Enabled;
- }
-
- _contentBottomRightCorner.Enabled = Enabled;
- }
-
- private void Host_VisibleChanged (object sender, EventArgs e)
- {
- if (!Host.Visible)
- {
- Visible = Host.Visible;
-
- if (_otherScrollBarView is { })
- {
- _otherScrollBarView.Visible = Visible;
- }
-
- _contentBottomRightCorner.Visible = Visible;
- }
- else
- {
- ShowHideScrollBars ();
- }
- }
-
- private void ScrollBarView_Initialized (object sender, EventArgs e)
- {
- SetWidthHeight ();
- SetRelativeLayout (SuperView?.Frame.Size ?? Host?.Frame.Size ?? Frame.Size);
-
- if (OtherScrollBarView is null)
- {
- // Only do this once if both scrollbars are enabled
- ShowHideScrollBars ();
- }
-
- SetPosition (Position);
- }
-
- // Helper to assist Initialized event handler
- private void SetPosition (int newPosition)
- {
- if (!IsInitialized)
- {
- // We're not initialized so we can't do anything fancy. Just cache value.
- _position = newPosition;
-
- return;
- }
-
- if (newPosition < 0)
- {
- _position = 0;
- SetNeedsDraw ();
-
- return;
- }
- else if (CanScroll (newPosition - _position, out int max, _vertical))
- {
- if (max == newPosition - _position)
- {
- _position = newPosition;
- }
- else
- {
- _position = Math.Max (_position + max, 0);
- }
- }
- else if (max < 0)
- {
- _position = Math.Max (_position + max, 0);
- }
- else
- {
- _position = Math.Max (newPosition, 0);
- }
-
- OnChangedPosition ();
- SetNeedsDraw ();
- }
-
- // BUGBUG: v2 - rationalize this with View.SetMinWidthHeight
- private void SetWidthHeight ()
- {
- // BUGBUG: v2 - If Host is also the ScrollBarView's superview, this is all bogus because it's not
- // supported that a view can reference it's superview's Dims. This code also assumes the host does
- // not have a margin/borderframe/padding.
- if (!IsInitialized || _otherScrollBarView is { IsInitialized: false })
- {
- return;
- }
-
- if (_showBothScrollIndicator)
- {
- Width = _vertical ? 1 :
- Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
- Height = _vertical ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1 : 1;
-
- _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 :
- Host != SuperView ? Dim.Width (Host) - 1 : Dim.Fill () - 1;
-
- _otherScrollBarView.Height = _otherScrollBarView._vertical
- ? Host != SuperView ? Dim.Height (Host) - 1 : Dim.Fill () - 1
- : 1;
- }
- else if (ShowScrollIndicator)
- {
- Width = _vertical ? 1 :
- Host != SuperView ? Dim.Width (Host) : Dim.Fill ();
- Height = _vertical ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () : 1;
- }
- else if (_otherScrollBarView?.ShowScrollIndicator == true)
- {
- _otherScrollBarView.Width = _otherScrollBarView._vertical ? 1 :
- Host != SuperView ? Dim.Width (Host) : Dim.Fill () - 0;
-
- _otherScrollBarView.Height = _otherScrollBarView._vertical
- ? Host != SuperView ? Dim.Height (Host) : Dim.Fill () - 0
- : 1;
- }
- }
-
- private void ShowHideScrollBars (bool redraw = true)
- {
- if (!_hosted || (_hosted && !_autoHideScrollBars))
- {
- if (_contentBottomRightCorner is { } && _contentBottomRightCorner.Visible)
- {
- _contentBottomRightCorner.Visible = false;
- }
- else if (_otherScrollBarView != null
- && _otherScrollBarView._contentBottomRightCorner != null
- && _otherScrollBarView._contentBottomRightCorner.Visible)
- {
- _otherScrollBarView._contentBottomRightCorner.Visible = false;
- }
-
- return;
- }
-
- bool pending = CheckBothScrollBars (this);
-
- if (_otherScrollBarView is { })
- {
- CheckBothScrollBars (_otherScrollBarView, pending);
- }
-
- SetWidthHeight ();
- SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
-
- if (_otherScrollBarView is { })
- {
- OtherScrollBarView.SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size);
- }
-
- if (_showBothScrollIndicator)
- {
- if (_contentBottomRightCorner is { })
- {
- _contentBottomRightCorner.Visible = true;
- }
- else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { })
- {
- _otherScrollBarView._contentBottomRightCorner.Visible = true;
- }
- }
- else if (!ShowScrollIndicator)
- {
- if (_contentBottomRightCorner is { })
- {
- _contentBottomRightCorner.Visible = false;
- }
- else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { })
- {
- _otherScrollBarView._contentBottomRightCorner.Visible = false;
- }
-
- if (Application.MouseGrabView is { } && Application.MouseGrabView == this)
- {
- Application.UngrabMouse ();
- }
- }
- else if (_contentBottomRightCorner is { })
- {
- _contentBottomRightCorner.Visible = false;
- }
- else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { })
- {
- _otherScrollBarView._contentBottomRightCorner.Visible = false;
- }
-
- if (Host?.Visible == true && ShowScrollIndicator && !Visible)
- {
- Visible = true;
- }
-
- if (Host?.Visible == true && _otherScrollBarView?.ShowScrollIndicator == true && !_otherScrollBarView.Visible)
- {
- _otherScrollBarView.Visible = true;
- }
-
- if (!redraw)
- {
- return;
- }
-
- if (ShowScrollIndicator)
- {
- Draw ();
- }
-
- if (_otherScrollBarView is { } && _otherScrollBarView.ShowScrollIndicator)
- {
- _otherScrollBarView.Draw ();
- }
-
- if (_contentBottomRightCorner is { } && _contentBottomRightCorner.Visible)
- {
- _contentBottomRightCorner.Draw ();
- }
- else if (_otherScrollBarView is { } && _otherScrollBarView._contentBottomRightCorner is { } && _otherScrollBarView._contentBottomRightCorner.Visible)
- {
- _otherScrollBarView._contentBottomRightCorner.Draw ();
- }
- }
-
- internal class ContentBottomRightCorner : View
- {
- public ContentBottomRightCorner ()
- {
- ColorScheme = ColorScheme;
- }
- }
-}
diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs
deleted file mode 100644
index aeae539add..0000000000
--- a/Terminal.Gui/Views/ScrollView.cs
+++ /dev/null
@@ -1,774 +0,0 @@
-//
-// ScrollView.cs: ScrollView view.
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
-//
-// TODO:
-// - focus in scrollview
-// - focus handling in scrollview to auto scroll to focused view
-// - Raise events
-// - Perhaps allow an option to not display the scrollbar arrow indicators?
-
-using System.ComponentModel;
-
-namespace Terminal.Gui;
-
-///
-/// Scrollviews are views that present a window into a virtual space where subviews are added. Similar to the iOS
-/// UIScrollView.
-///
-///
-///
-/// The subviews that are added to this are offset by the
-/// property. The view itself is a window into the space represented by the
-/// .
-///
-/// Use the
-///
-public class ScrollView : View
-{
- private readonly ContentView _contentView;
- private readonly ScrollBarView _horizontal;
- private readonly ScrollBarView _vertical;
- private bool _autoHideScrollBars = true;
- private View _contentBottomRightCorner;
- private Point _contentOffset;
- private bool _keepContentAlwaysInViewport = true;
- private bool _showHorizontalScrollIndicator;
- private bool _showVerticalScrollIndicator;
-
- ///
- /// Initializes a new instance of the class.
- ///
- public ScrollView ()
- {
- _contentView = new ContentView ();
-
- _vertical = new ScrollBarView
- {
- X = Pos.AnchorEnd (1),
- Y = 0,
- Width = 1,
- Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0),
- Size = 1,
- IsVertical = true,
- Host = this
- };
-
- _horizontal = new ScrollBarView
- {
- X = 0,
- Y = Pos.AnchorEnd (1),
- Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0),
- Height = 1,
- Size = 1,
- IsVertical = false,
- Host = this
- };
-
- _vertical.OtherScrollBarView = _horizontal;
- _horizontal.OtherScrollBarView = _vertical;
- base.Add (_contentView);
- CanFocus = true;
- TabStop = TabBehavior.TabGroup;
-
- MouseEnter += View_MouseEnter;
- MouseLeave += View_MouseLeave;
- _contentView.MouseEnter += View_MouseEnter;
- _contentView.MouseLeave += View_MouseLeave;
-
- Application.UnGrabbedMouse += Application_UnGrabbedMouse;
-
- // Things this view knows how to do
- AddCommand (Command.ScrollUp, () => ScrollUp (1));
- AddCommand (Command.ScrollDown, () => ScrollDown (1));
- AddCommand (Command.ScrollLeft, () => ScrollLeft (1));
- AddCommand (Command.ScrollRight, () => ScrollRight (1));
- AddCommand (Command.PageUp, () => ScrollUp (Viewport.Height));
- AddCommand (Command.PageDown, () => ScrollDown (Viewport.Height));
- AddCommand (Command.PageLeft, () => ScrollLeft (Viewport.Width));
- AddCommand (Command.PageRight, () => ScrollRight (Viewport.Width));
- AddCommand (Command.Start, () => ScrollUp (GetContentSize ().Height));
- AddCommand (Command.End, () => ScrollDown (GetContentSize ().Height));
- AddCommand (Command.LeftStart, () => ScrollLeft (GetContentSize ().Width));
- AddCommand (Command.RightEnd, () => ScrollRight (GetContentSize ().Width));
-
- // Default keybindings for this view
- KeyBindings.Add (Key.CursorUp, Command.ScrollUp);
- KeyBindings.Add (Key.CursorDown, Command.ScrollDown);
- KeyBindings.Add (Key.CursorLeft, Command.ScrollLeft);
- KeyBindings.Add (Key.CursorRight, Command.ScrollRight);
-
- KeyBindings.Add (Key.PageUp, Command.PageUp);
- KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
-
- KeyBindings.Add (Key.PageDown, Command.PageDown);
- KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
-
- KeyBindings.Add (Key.PageUp.WithCtrl, Command.PageLeft);
- KeyBindings.Add (Key.PageDown.WithCtrl, Command.PageRight);
- KeyBindings.Add (Key.Home, Command.Start);
- KeyBindings.Add (Key.End, Command.End);
- KeyBindings.Add (Key.Home.WithCtrl, Command.LeftStart);
- KeyBindings.Add (Key.End.WithCtrl, Command.RightEnd);
-
- Initialized += (s, e) =>
- {
- if (!_vertical.IsInitialized)
- {
- _vertical.BeginInit ();
- _vertical.EndInit ();
- }
-
- if (!_horizontal.IsInitialized)
- {
- _horizontal.BeginInit ();
- _horizontal.EndInit ();
- }
-
- SetContentOffset (_contentOffset);
- _contentView.Frame = new Rectangle (ContentOffset, GetContentSize ());
-
- // PERF: How about calls to Point.Offset instead?
- _vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); };
- _horizontal.ChangedPosition += delegate { ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); };
- };
- ContentSizeChanged += ScrollViewContentSizeChanged;
- }
-
- private void ScrollViewContentSizeChanged (object sender, SizeChangedEventArgs e)
- {
- if (e.Size is null)
- {
- return;
- }
- _contentView.Frame = new Rectangle (ContentOffset, e.Size.Value with { Width = e.Size.Value.Width - 1, Height = e.Size.Value.Height - 1 });
- _vertical.Size = e.Size.Value.Height;
- _horizontal.Size = e.Size.Value.Width;
- }
-
- private void Application_UnGrabbedMouse (object sender, ViewEventArgs e)
- {
- var parent = e.View is Adornment adornment ? adornment.Parent : e.View;
-
- if (parent is { })
- {
- var supView = parent.SuperView;
-
- while (supView is { })
- {
- if (supView == _contentView)
- {
- Application.GrabMouse (this);
-
- break;
- }
-
- supView = supView.SuperView;
- }
- }
- }
-
- /// If true the vertical/horizontal scroll bars won't be showed if it's not needed.
- public bool AutoHideScrollBars
- {
- get => _autoHideScrollBars;
- set
- {
- if (_autoHideScrollBars != value)
- {
- _autoHideScrollBars = value;
-
- if (Subviews.Contains (_vertical))
- {
- _vertical.AutoHideScrollBars = value;
- }
-
- if (Subviews.Contains (_horizontal))
- {
- _horizontal.AutoHideScrollBars = value;
- }
-
- SetNeedsDraw ();
- }
- }
- }
-
- /// Represents the top left corner coordinate that is displayed by the scrollview
- /// The content offset.
- public Point ContentOffset
- {
- get => _contentOffset;
- set
- {
- if (!IsInitialized)
- {
- // We're not initialized so we can't do anything fancy. Just cache value.
- _contentOffset = new Point (-Math.Abs (value.X), -Math.Abs (value.Y));
-
- return;
- }
-
- SetContentOffset (value);
- }
- }
-
- ///// Represents the contents of the data shown inside the scrollview
- ///// The size of the content.
- //public new Size ContentSize
- //{
- // get => ContentSize;
- // set
- // {
- // if (GetContentSize () != value)
- // {
- // ContentSize = value;
- // _contentView.Frame = new Rectangle (_contentOffset, value);
- // _vertical.Size = GetContentSize ().Height;
- // _horizontal.Size = GetContentSize ().Width;
- // SetNeedsDraw ();
- // }
- // }
- //}
-
- /// Get or sets if the view-port is kept always visible in the area of this
- public bool KeepContentAlwaysInViewport
- {
- get => _keepContentAlwaysInViewport;
- set
- {
- if (_keepContentAlwaysInViewport != value)
- {
- _keepContentAlwaysInViewport = value;
- _vertical.OtherScrollBarView.KeepContentAlwaysInViewport = value;
- _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value;
- Point p = default;
-
- if (value && -_contentOffset.X + Viewport.Width > GetContentSize ().Width)
- {
- p = new Point (
- GetContentSize ().Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0),
- -_contentOffset.Y
- );
- }
-
- if (value && -_contentOffset.Y + Viewport.Height > GetContentSize ().Height)
- {
- if (p == default (Point))
- {
- p = new Point (
- -_contentOffset.X,
- GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0)
- );
- }
- else
- {
- p.Y = GetContentSize ().Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0);
- }
- }
-
- if (p != default (Point))
- {
- ContentOffset = p;
- }
- }
- }
- }
-
- /// Gets or sets the visibility for the horizontal scroll indicator.
- /// true if show horizontal scroll indicator; otherwise, false.
- public bool ShowHorizontalScrollIndicator
- {
- get => _showHorizontalScrollIndicator;
- set
- {
- if (value != _showHorizontalScrollIndicator)
- {
- _showHorizontalScrollIndicator = value;
- SetNeedsLayout ();
-
- if (value)
- {
- _horizontal.OtherScrollBarView = _vertical;
- base.Add (_horizontal);
- _horizontal.ShowScrollIndicator = value;
- _horizontal.AutoHideScrollBars = _autoHideScrollBars;
- _horizontal.OtherScrollBarView.ShowScrollIndicator = value;
- _horizontal.MouseEnter += View_MouseEnter;
- _horizontal.MouseLeave += View_MouseLeave;
- }
- else
- {
- base.Remove (_horizontal);
- _horizontal.OtherScrollBarView = null;
- _horizontal.MouseEnter -= View_MouseEnter;
- _horizontal.MouseLeave -= View_MouseLeave;
- }
- }
-
- _vertical.Height = Dim.Fill (_showHorizontalScrollIndicator ? 1 : 0);
- }
- }
-
- /// Gets or sets the visibility for the vertical scroll indicator.
- /// true if show vertical scroll indicator; otherwise, false.
- public bool ShowVerticalScrollIndicator
- {
- get => _showVerticalScrollIndicator;
- set
- {
- if (value != _showVerticalScrollIndicator)
- {
- _showVerticalScrollIndicator = value;
- SetNeedsLayout ();
-
- if (value)
- {
- _vertical.OtherScrollBarView = _horizontal;
- base.Add (_vertical);
- _vertical.ShowScrollIndicator = value;
- _vertical.AutoHideScrollBars = _autoHideScrollBars;
- _vertical.OtherScrollBarView.ShowScrollIndicator = value;
- _vertical.MouseEnter += View_MouseEnter;
- _vertical.MouseLeave += View_MouseLeave;
- }
- else
- {
- Remove (_vertical);
- _vertical.OtherScrollBarView = null;
- _vertical.MouseEnter -= View_MouseEnter;
- _vertical.MouseLeave -= View_MouseLeave;
- }
- }
-
- _horizontal.Width = Dim.Fill (_showVerticalScrollIndicator ? 1 : 0);
- }
- }
-
- /// Adds the view to the scrollview.
- /// The view to add to the scrollview.
- public override View Add (View view)
- {
- if (view is ScrollBarView.ContentBottomRightCorner)
- {
- _contentBottomRightCorner = view;
- base.Add (view);
- }
- else
- {
- if (!IsOverridden (view, "OnMouseEvent"))
- {
- view.MouseEnter += View_MouseEnter;
- view.MouseLeave += View_MouseLeave;
- }
-
- _contentView.Add (view);
- }
-
- SetNeedsLayout ();
- return view;
- }
-
- ///
- protected override bool OnDrawingContent ()
- {
- SetViewsNeedsDraw ();
-
- // TODO: It's bad practice for views to always clear a view. It negates clipping.
- ClearViewport ();
-
- if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0)
- {
- Region? saved = ClipFrame();
- _contentView.Draw ();
- View.SetClip (saved);
- }
-
- DrawScrollBars ();
-
- return true;
- }
-
- ///
- protected override bool OnKeyDown (Key a)
- {
- if (base.OnKeyDown (a))
- {
- return true;
- }
-
- bool? result = InvokeCommands (a, KeyBindingScope.HotKey | KeyBindingScope.Focused);
-
- if (result is { })
- {
- return (bool)result;
- }
-
- return false;
- }
-
- ///
- protected override bool OnMouseEvent (MouseEventArgs me)
- {
- if (!Enabled)
- {
- // A disabled view should not eat mouse events
- return false;
- }
-
- if (me.Flags == MouseFlags.WheeledDown && ShowVerticalScrollIndicator)
- {
- return ScrollDown (1);
- }
- else if (me.Flags == MouseFlags.WheeledUp && ShowVerticalScrollIndicator)
- {
- return ScrollUp (1);
- }
- else if (me.Flags == MouseFlags.WheeledRight && _showHorizontalScrollIndicator)
- {
- return ScrollRight (1);
- }
- else if (me.Flags == MouseFlags.WheeledLeft && ShowVerticalScrollIndicator)
- {
- return ScrollLeft (1);
- }
- else if (me.Position.X == _vertical.Frame.X && ShowVerticalScrollIndicator)
- {
- _vertical.NewMouseEvent (me);
- }
- else if (me.Position.Y == _horizontal.Frame.Y && ShowHorizontalScrollIndicator)
- {
- _horizontal.NewMouseEvent (me);
- }
- else if (IsOverridden (me.View, "OnMouseEvent"))
- {
- Application.UngrabMouse ();
- }
-
- return me.Handled;
- }
-
- ///
- public override Point? PositionCursor ()
- {
- if (InternalSubviews.Count == 0)
- {
- Move (0, 0);
-
- return null; // Don't show the cursor
- }
- return base.PositionCursor ();
- }
-
- /// Removes the view from the scrollview.
- /// The view to remove from the scrollview.
- public override View Remove (View view)
- {
- if (view is null)
- {
- return view;
- }
-
- SetNeedsDraw ();
- View container = view?.SuperView;
-
- if (container == this)
- {
- base.Remove (view);
- }
- else
- {
- container?.Remove (view);
- }
-
- if (_contentView.InternalSubviews.Count < 1)
- {
- CanFocus = false;
- }
-
- return view;
- }
-
- /// Removes all widgets from this container.
- public override void RemoveAll () { _contentView.RemoveAll (); }
-
- /// Scrolls the view down.
- /// true, if left was scrolled, false otherwise.
- /// Number of lines to scroll.
- public bool ScrollDown (int lines)
- {
- if (_vertical.CanScroll (lines, out _, true))
- {
- ContentOffset = new Point (_contentOffset.X, _contentOffset.Y - lines);
-
- return true;
- }
-
- return false;
- }
-
- /// Scrolls the view to the left
- /// true, if left was scrolled, false otherwise.
- /// Number of columns to scroll by.
- public bool ScrollLeft (int cols)
- {
- if (_contentOffset.X < 0)
- {
- ContentOffset = new Point (Math.Min (_contentOffset.X + cols, 0), _contentOffset.Y);
-
- return true;
- }
-
- return false;
- }
-
- /// Scrolls the view to the right.
- /// true, if right was scrolled, false otherwise.
- /// Number of columns to scroll by.
- public bool ScrollRight (int cols)
- {
- if (_horizontal.CanScroll (cols, out _))
- {
- ContentOffset = new Point (_contentOffset.X - cols, _contentOffset.Y);
-
- return true;
- }
-
- return false;
- }
-
- /// Scrolls the view up.
- /// true, if left was scrolled, false otherwise.
- /// Number of lines to scroll.
- public bool ScrollUp (int lines)
- {
- if (_contentOffset.Y < 0)
- {
- ContentOffset = new Point (_contentOffset.X, Math.Min (_contentOffset.Y + lines, 0));
-
- return true;
- }
-
- return false;
- }
-
- ///
- protected override void Dispose (bool disposing)
- {
- if (!_showVerticalScrollIndicator)
- {
- // It was not added to SuperView, so it won't get disposed automatically
- _vertical?.Dispose ();
- }
-
- if (!_showHorizontalScrollIndicator)
- {
- // It was not added to SuperView, so it won't get disposed automatically
- _horizontal?.Dispose ();
- }
-
- Application.UnGrabbedMouse -= Application_UnGrabbedMouse;
-
- base.Dispose (disposing);
- }
-
- private void DrawScrollBars ()
- {
- if (_autoHideScrollBars)
- {
- ShowHideScrollBars ();
- }
- else
- {
- if (ShowVerticalScrollIndicator)
- {
- Region? saved = View.SetClipToScreen ();
- _vertical.Draw ();
- View.SetClip (saved);
- }
-
- if (ShowHorizontalScrollIndicator)
- {
- Region? saved = View.SetClipToScreen ();
- _horizontal.Draw ();
- View.SetClip (saved);
- }
-
- if (ShowVerticalScrollIndicator && ShowHorizontalScrollIndicator)
- {
- SetContentBottomRightCornerVisibility ();
- Region? saved = View.SetClipToScreen ();
- _contentBottomRightCorner.Draw ();
- View.SetClip (saved);
- }
- }
- }
-
- private void SetContentBottomRightCornerVisibility ()
- {
- if (_showHorizontalScrollIndicator && _showVerticalScrollIndicator)
- {
- _contentBottomRightCorner.Visible = true;
- }
- else if (_horizontal.IsAdded || _vertical.IsAdded)
- {
- _contentBottomRightCorner.Visible = false;
- }
- }
-
- private void SetContentOffset (Point offset)
- {
- // INTENT: Unclear intent. How about a call to Offset?
- _contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y));
- _contentView.Frame = new Rectangle (_contentOffset, GetContentSize ());
- int p = Math.Max (0, -_contentOffset.Y);
-
- if (_vertical.Position != p)
- {
- _vertical.Position = Math.Max (0, -_contentOffset.Y);
- }
-
- p = Math.Max (0, -_contentOffset.X);
-
- if (_horizontal.Position != p)
- {
- _horizontal.Position = Math.Max (0, -_contentOffset.X);
- }
- SetNeedsDraw ();
- }
-
- private void SetViewsNeedsDraw ()
- {
- foreach (View view in _contentView.Subviews)
- {
- view.SetNeedsDraw ();
- }
- }
-
- private void ShowHideScrollBars ()
- {
- bool v = false, h = false;
- var p = false;
-
- if (GetContentSize () is { } && (Viewport.Height == 0 || Viewport.Height > GetContentSize ().Height))
- {
- if (ShowVerticalScrollIndicator)
- {
- ShowVerticalScrollIndicator = false;
- }
-
- v = false;
- }
- else if (GetContentSize () is { } && Viewport.Height > 0 && Viewport.Height == GetContentSize ().Height)
- {
- p = true;
- }
- else
- {
- if (!ShowVerticalScrollIndicator)
- {
- ShowVerticalScrollIndicator = true;
- }
-
- v = true;
- }
-
- if (GetContentSize () is { } && (Viewport.Width == 0 || Viewport.Width > GetContentSize ().Width))
- {
- if (ShowHorizontalScrollIndicator)
- {
- ShowHorizontalScrollIndicator = false;
- }
-
- h = false;
- }
- else if (GetContentSize () is { } && Viewport.Width > 0 && Viewport.Width == GetContentSize ().Width && p)
- {
- if (ShowHorizontalScrollIndicator)
- {
- ShowHorizontalScrollIndicator = false;
- }
-
- h = false;
-
- if (ShowVerticalScrollIndicator)
- {
- ShowVerticalScrollIndicator = false;
- }
-
- v = false;
- }
- else
- {
- if (p)
- {
- if (!ShowVerticalScrollIndicator)
- {
- ShowVerticalScrollIndicator = true;
- }
-
- v = true;
- }
-
- if (!ShowHorizontalScrollIndicator)
- {
- ShowHorizontalScrollIndicator = true;
- }
-
- h = true;
- }
-
- Dim dim = Dim.Fill (h ? 1 : 0);
-
- if (!_vertical.Height.Equals (dim))
- {
- _vertical.Height = dim;
- }
-
- dim = Dim.Fill (v ? 1 : 0);
-
- if (!_horizontal.Width.Equals (dim))
- {
- _horizontal.Width = dim;
- }
-
- if (v)
- {
- _vertical.SetRelativeLayout (Viewport.Size);
- _vertical.Draw ();
- }
-
- if (h)
- {
- _horizontal.SetRelativeLayout (Viewport.Size);
- _horizontal.Draw ();
- }
-
- SetContentBottomRightCornerVisibility ();
-
- if (v && h)
- {
- _contentBottomRightCorner.SetRelativeLayout (Viewport.Size);
- _contentBottomRightCorner.Draw ();
- }
- }
-
- private void View_MouseEnter (object sender, CancelEventArgs e) { Application.GrabMouse (this); }
-
- private void View_MouseLeave (object sender, EventArgs e)
- {
- if (Application.MouseGrabView is { } && Application.MouseGrabView != this && Application.MouseGrabView != _vertical && Application.MouseGrabView != _horizontal)
- {
- Application.UngrabMouse ();
- }
- }
-
- // The ContentView is the view that contains the subviews and content that are being scrolled
- // The ContentView is the size of the ContentSize and is offset by the ContentOffset
- private class ContentView : View
- {
- public ContentView () { CanFocus = true; }
- }
-}
diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs
index 27d17fa6bc..ff37d513c0 100644
--- a/Terminal.Gui/Views/Slider.cs
+++ b/Terminal.Gui/Views/Slider.cs
@@ -47,7 +47,7 @@ private void SetInitialProperties (
_options = options ?? new List> ();
- _orientationHelper = new (this);
+ _orientationHelper = new (this); // Do not use object initializer!
_orientationHelper.Orientation = _config._sliderOrientation = orientation;
_orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs
deleted file mode 100644
index d7e181eae1..0000000000
--- a/UICatalog/Scenarios/ASCIICustomButton.cs
+++ /dev/null
@@ -1,400 +0,0 @@
-using System;
-using System.Collections.Generic;
-using System.Diagnostics;
-using System.Text;
-using JetBrains.Annotations;
-using Terminal.Gui;
-
-namespace UICatalog.Scenarios;
-
-[ScenarioMetadata ("ASCIICustomButtonTest", "ASCIICustomButton sample")]
-[ScenarioCategory ("Controls")]
-public class ASCIICustomButtonTest : Scenario
-{
- private static bool _smallerWindow;
- private MenuItem _miSmallerWindow;
- private ScrollViewTestWindow _scrollViewTestWindow;
-
- public override void Main ()
- {
- _smallerWindow = false;
-
- Application.Init ();
- Toplevel top = new ();
-
- var menu = new MenuBar
- {
- Menus =
- [
- new MenuBarItem (
- "_Window Size",
- new []
- {
- _miSmallerWindow =
- new MenuItem (
- "Smaller Window",
- "",
- ChangeWindowSize
- )
- {
- CheckType = MenuItemCheckStyle
- .Checked
- },
- null,
- new MenuItem (
- "Quit",
- "",
- () => Application.RequestStop (),
- null,
- null,
- (KeyCode)Application.QuitKey
- )
- }
- )
- ]
- };
-
- _scrollViewTestWindow = new ScrollViewTestWindow { Y = Pos.Bottom (menu) };
-
- top.Add (menu, _scrollViewTestWindow);
- Application.Run (top);
- top.Dispose ();
-
- Application.Shutdown ();
-
- return;
-
- void ChangeWindowSize ()
- {
- _smallerWindow = (bool)(_miSmallerWindow.Checked = !_miSmallerWindow.Checked);
- top.Remove (_scrollViewTestWindow);
- _scrollViewTestWindow.Dispose ();
-
- _scrollViewTestWindow = new ScrollViewTestWindow ();
- top.Add (_scrollViewTestWindow);
- }
- }
-
- public class ASCIICustomButton : Button
- {
- private FrameView _border;
- private Label _fill;
- public string Description => $"Description of: {Id}";
-
- public void CustomInitialize ()
- {
- _border = new FrameView { Width = Width, Height = Height };
-
- var fillText = new StringBuilder ();
-
- for (var i = 0; i < Viewport.Height; i++)
- {
- if (i > 0)
- {
- fillText.AppendLine ("");
- }
-
- for (var j = 0; j < Viewport.Width; j++)
- {
- fillText.Append ("█");
- }
- }
-
- _fill = new Label { Visible = false, CanFocus = false, Text = fillText.ToString () };
-
- var title = new Label { X = Pos.Center (), Y = Pos.Center (), Text = Text };
-
- _border.MouseClick += This_MouseClick;
- _fill.MouseClick += This_MouseClick;
- title.MouseClick += This_MouseClick;
-
- Add (_border, _fill, title);
- }
-
- protected override void OnHasFocusChanged (bool newHasFocus, [CanBeNull] View previousFocusedView, [CanBeNull] View focusedView)
- {
- if (newHasFocus)
- {
- _border.Visible = false;
- _fill.Visible = true;
- PointerEnter?.Invoke (this);
- }
- else
- {
- _border.Visible = true;
- _fill.Visible = false;
- }
- }
-
- public event Action PointerEnter;
- private void This_MouseClick (object sender, MouseEventArgs obj) { NewMouseEvent (obj); }
- }
-
- public class ScrollViewTestWindow : Window
- {
- private const int BUTTON_HEIGHT = 3;
- private const int BUTTON_WIDTH = 25;
- private const int BUTTONS_ON_PAGE = 7;
-
- private readonly List