diff --git a/Terminal.Gui/Views/Scroll/Scroll.cs b/Terminal.Gui/Views/Scroll/Scroll.cs
index 42a3da02c..118e75e71 100644
--- a/Terminal.Gui/Views/Scroll/Scroll.cs
+++ b/Terminal.Gui/Views/Scroll/Scroll.cs
@@ -1,7 +1,6 @@
#nullable enable
using System.ComponentModel;
-using System.Drawing;
namespace Terminal.Gui;
@@ -39,8 +38,7 @@ public class Scroll : View, IOrientation, IDesignable
OnOrientationChanged (Orientation);
}
-
- ///
+ ///
protected override void OnSubviewLayout (LayoutEventArgs args)
{
if (ViewportDimension < 1)
@@ -49,10 +47,12 @@ public class Scroll : View, IOrientation, IDesignable
return;
}
- _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size)), 1, ViewportDimension);
+
+ _slider.Size = (int)Math.Clamp (Math.Floor ((double)ViewportDimension * ViewportDimension / (Size - 2)), 1, ViewportDimension);
}
#region IOrientation members
+
private readonly OrientationHelper _orientationHelper;
///
@@ -133,16 +133,16 @@ public class Scroll : View, IOrientation, IDesignable
/// Raised when has changed.
public event EventHandler>? SizeChanged;
- private int _sliderPosition;
-
+ #region SliderPosition
private void OnSliderOnFrameChanged (object? sender, EventArgs args)
{
if (ViewportDimension == 0)
{
return;
}
+
int framePos = Orientation == Orientation.Vertical ? args.CurrentValue.Y : args.CurrentValue.X;
- RaisePositionChangeEvents (_sliderPosition, framePos);
+ SliderPosition = framePos;
}
///
@@ -150,57 +150,23 @@ public class Scroll : View, IOrientation, IDesignable
///
public int SliderPosition
{
- get => _sliderPosition;
- set => RaisePositionChangeEvents (_sliderPosition, value);
+ get => CalculateSliderPosition (_contentPosition);
+ set => RaiseSliderPositionChangeEvents (value);
}
- ///
- /// Gets or sets the position of the ScrollSlider within the range of 0....
- ///
- public int ContentPosition
+ private void RaiseSliderPositionChangeEvents (int newSliderPosition)
{
- get
+ int currentSliderPosition = CalculateSliderPosition (_contentPosition);
+
+ if (newSliderPosition > Size - ViewportDimension)
{
- if (ViewportDimension - _slider.Size == 0)
- {
- return 0;
- }
- return (int)Math.Round (GetCurrentContentPosition ());
+ return;
}
- set
- {
- if (Size - ViewportDimension == 0)
- {
- SliderPosition = 0;
- return;
- }
- double newContentPos = (double)value / (ViewportDimension - _slider.Size) * (Size - ViewportDimension);
- double newSliderPos = (double)value / (Size - ViewportDimension) * (ViewportDimension - _slider.Size);
-
- int newSliderPosition = (int)Math.Ceiling (newSliderPos);
- if (newContentPos >= GetCurrentContentPosition ())
- {
- newSliderPosition = (int)Math.Floor (newSliderPos);
- }
-
- if (SliderPosition != newSliderPosition)
- {
- SliderPosition = newSliderPosition;
- }
- }
- }
-
- private double GetCurrentContentPosition ()
- {
- return (double)SliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension);
- }
-
- private void RaisePositionChangeEvents (int currentSliderPosition, int newSliderPosition)
- {
- if (OnPositionChanging (currentSliderPosition, newSliderPosition))
+ if (OnSliderPositionChanging (currentSliderPosition, newSliderPosition))
{
_slider.Position = currentSliderPosition;
+
return;
}
@@ -210,30 +176,28 @@ public class Scroll : View, IOrientation, IDesignable
if (args.Cancel)
{
_slider.Position = currentSliderPosition;
+
return;
}
// This sets the slider position and clamps the value
_slider.Position = newSliderPosition;
- if (_slider.Position == _sliderPosition)
+ if (_slider.Position == currentSliderPosition)
{
return;
}
- _sliderPosition = newSliderPosition;
+ ContentPosition = (int)Math.Round ((double)newSliderPosition / (ViewportDimension - _slider.Size) * (Size - ViewportDimension));
- OnPositionChanged (_sliderPosition);
+ OnSliderPositionChanged (newSliderPosition);
SliderPositionChanged?.Invoke (this, new (in newSliderPosition));
}
///
/// Called when is changing. Return true to cancel the change.
///
- protected virtual bool OnPositionChanging (int currentPos, int newPos)
- {
- return false;
- }
+ protected virtual bool OnSliderPositionChanging (int currentSliderPosition, int newSliderPosition) { return false; }
///
/// Raised when the is changing. Set to
@@ -242,11 +206,88 @@ public class Scroll : View, IOrientation, IDesignable
public event EventHandler>? SliderPositionChanging;
/// Called when has changed.
- protected virtual void OnPositionChanged (int position) { }
+ 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, 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 ()
@@ -256,7 +297,7 @@ public class Scroll : View, IOrientation, IDesignable
return true;
}
- ///
+ ///
protected override bool OnMouseClick (MouseEventArgs args)
{
if (!args.IsSingleClicked)
@@ -285,7 +326,6 @@ public class Scroll : View, IOrientation, IDesignable
SliderPosition = args.Position.X;
}
-
return true;
}
@@ -306,29 +346,31 @@ public class Scroll : View, IOrientation, IDesignable
{
if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledDown))
{
- SliderPosition++;
+ ContentPosition++;
}
+
if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledUp))
{
- SliderPosition--;
+ ContentPosition--;
}
}
else
{
if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledRight))
{
- SliderPosition++;
+ ContentPosition++;
}
+
if (mouseEvent.Flags.HasFlag (MouseFlags.WheeledLeft))
{
- SliderPosition--;
+ ContentPosition--;
}
}
return true;
}
- ///
+ ///
public bool EnableForDesign ()
{
OrientationChanged += (sender, args) =>
@@ -348,9 +390,7 @@ public class Scroll : View, IOrientation, IDesignable
Width = 1;
Height = Dim.Fill ();
Size = 1000;
- SliderPosition = 10;
-
-
+ ContentPosition = 10;
return true;
}
diff --git a/Terminal.Gui/Views/Scroll/ScrollBar.cs b/Terminal.Gui/Views/Scroll/ScrollBar.cs
index 334cd5cf2..1ee3abc01 100644
--- a/Terminal.Gui/Views/Scroll/ScrollBar.cs
+++ b/Terminal.Gui/Views/Scroll/ScrollBar.cs
@@ -29,6 +29,8 @@ public class ScrollBar : View, IOrientation, IDesignable
_scroll = new ();
_scroll.SliderPositionChanging += OnScrollOnSliderPositionChanging;
_scroll.SliderPositionChanged += OnScrollOnSliderPositionChanged;
+ _scroll.ContentPositionChanging += OnScrollOnContentPositionChanging;
+ _scroll.ContentPositionChanged += OnScrollOnContentPositionChanged;
_scroll.SizeChanged += OnScrollOnSizeChanged;
_decreaseButton = new ()
@@ -64,13 +66,13 @@ public class ScrollBar : View, IOrientation, IDesignable
void OnDecreaseButtonOnAccept (object? s, CommandEventArgs e)
{
- _scroll.ContentPosition--;
+ ContentPosition -= Increment;
e.Cancel = true;
}
void OnIncreaseButtonOnAccept (object? s, CommandEventArgs e)
{
- _scroll.ContentPosition++;
+ ContentPosition += Increment;
e.Cancel = true;
}
}
@@ -188,8 +190,8 @@ public class ScrollBar : View, IOrientation, IDesignable
set => _scroll.SliderPosition = value;
}
- private void OnScrollOnSliderPositionChanged (object? sender, EventArgs e) { SliderPositionChanged?.Invoke (this, e); }
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
@@ -219,6 +221,18 @@ public class ScrollBar : View, IOrientation, IDesignable
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;
@@ -228,6 +242,14 @@ public class ScrollBar : View, IOrientation, IDesignable
SizeChanged?.Invoke (this, e);
}
+ ///
+ /// Gets or sets the amount each click of the increment/decrement buttons will incremenet/decrement the .
+ ///
+ ///
+ /// The default is 1.
+ ///
+ public int Increment { get; set; } = 1;
+
///
protected override void OnSubviewLayout (LayoutEventArgs args) { PositionSubviews (); }
diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs
index f069ca457..5f513f3d6 100644
--- a/UICatalog/Scenarios/CharacterMap.cs
+++ b/UICatalog/Scenarios/CharacterMap.cs
@@ -10,9 +10,7 @@ using System.Text;
using System.Text.Json;
using System.Text.Unicode;
using System.Threading.Tasks;
-using JetBrains.Annotations;
using Terminal.Gui;
-using static System.Runtime.InteropServices.JavaScript.JSType;
using static Terminal.Gui.SpinnerStyle;
namespace UICatalog.Scenarios;
@@ -303,7 +301,7 @@ public class CharacterMap : Scenario
public override List GetDemoKeyStrokes ()
{
- List keys = new List ();
+ List keys = new ();
for (var i = 0; i < 200; i++)
{
@@ -345,15 +343,17 @@ internal class CharMap : View, IDesignable
ColorScheme = Colors.ColorSchemes ["Dialog"];
CanFocus = true;
CursorVisibility = CursorVisibility.Default;
+
//ViewportSettings = ViewportSettings.AllowLocationGreaterThanContentSize;
- SetContentSize (new (COLUMN_WIDTH * 16, (_maxCodePoint / 16) * _rowHeight)); // +1 for Header
+ SetContentSize (new (COLUMN_WIDTH * 16 + RowLabelWidth, _maxCodePoint / 16 * _rowHeight)); // +1 for Header
AddCommand (
Command.Up,
() =>
{
SelectedCodePoint -= 16;
+
return true;
}
);
@@ -363,6 +363,7 @@ internal class CharMap : View, IDesignable
() =>
{
SelectedCodePoint += 16;
+
return true;
}
);
@@ -372,6 +373,7 @@ internal class CharMap : View, IDesignable
() =>
{
SelectedCodePoint--;
+
return true;
}
);
@@ -380,7 +382,8 @@ internal class CharMap : View, IDesignable
Command.Right,
() =>
{
- SelectedCodePoint++;
+ SelectedCodePoint++;
+
return true;
}
);
@@ -455,11 +458,12 @@ internal class CharMap : View, IDesignable
_hScrollBar = new ()
{
AutoHide = false,
- X = RowLabelWidth + 1,
+ X = RowLabelWidth,
Y = Pos.AnchorEnd (),
Orientation = Orientation.Horizontal,
Width = Dim.Fill (1),
- Size = GetContentSize ().Width
+ Size = GetContentSize ().Width - RowLabelWidth,
+ Increment = COLUMN_WIDTH
};
_vScrollBar = new ()
@@ -473,26 +477,31 @@ internal class CharMap : View, IDesignable
Padding.Add (_vScrollBar, _hScrollBar);
- _vScrollBar.SliderPositionChanged += (sender, args) =>
- {
- if (Viewport.Height > 0)
- {
- Viewport = Viewport with { Y = _vScrollBar.ContentPosition };
- }
- };
+ _vScrollBar.ContentPositionChanged += (sender, args) =>
+ {
+ if (Viewport.Height > 0)
+ {
+ Viewport = Viewport with
+ {
+ Y = Math.Min (args.CurrentValue, GetContentSize ().Height - (Viewport.Height - 2))
+ };
+ }
+ };
- _hScrollBar.SliderPositionChanged += (sender, args) =>
- {
- if (Viewport.Width > 0)
- {
- Viewport = Viewport with { X = _hScrollBar.ContentPosition };
- }
- };
+ _hScrollBar.ContentPositionChanged += (sender, args) =>
+ {
+ if (Viewport.Width > 0)
+ {
+ Viewport = Viewport with
+ {
+ X = Math.Min (args.CurrentValue, GetContentSize ().Width - Viewport.Width)
+ };
+ }
+ };
FrameChanged += (sender, args) =>
{
- int width = Viewport.Width / COLUMN_WIDTH * COLUMN_WIDTH - RowLabelWidth;
- if (width < GetContentSize ().Width)
+ if (Viewport.Width < GetContentSize ().Width)
{
Padding.Thickness = Padding.Thickness with { Bottom = 1 };
}
@@ -500,6 +509,7 @@ internal class CharMap : View, IDesignable
{
Padding.Thickness = Padding.Thickness with { Bottom = 0 };
}
+
_hScrollBar.ContentPosition = Viewport.X;
_vScrollBar.ContentPosition = Viewport.Y;
};
@@ -527,7 +537,8 @@ internal class CharMap : View, IDesignable
if (e.Flags == MouseFlags.WheeledRight)
{
- _hScrollBar.SliderPosition++;
+ ScrollHorizontal (1);
+ _hScrollBar.ContentPosition = Viewport.X;
e.Handled = true;
return;
@@ -535,7 +546,8 @@ internal class CharMap : View, IDesignable
if (e.Flags == MouseFlags.WheeledLeft)
{
- _hScrollBar.SliderPosition--;
+ ScrollHorizontal (-1);
+ _hScrollBar.ContentPosition = Viewport.X;
e.Handled = true;
}
}
@@ -545,8 +557,8 @@ internal class CharMap : View, IDesignable
{
get
{
- int row = SelectedCodePoint / 16 * _rowHeight - Viewport.Y + 1; // + 1 for header
- int col = SelectedCodePoint % 16 * COLUMN_WIDTH - Viewport.X + RowLabelWidth + 1; // + 1 for padding between label and first column
+ int row = SelectedCodePoint / 16 * _rowHeight + 1 - Viewport.Y; // + 1 for header
+ int col = SelectedCodePoint % 16 * COLUMN_WIDTH + RowLabelWidth + 1 - Viewport.X; // + 1 for padding between label and first column
return new (col, row);
}
@@ -556,7 +568,8 @@ internal class CharMap : View, IDesignable
public static int _maxCodePoint = UnicodeRange.Ranges.Max (r => r.End);
///
- /// Gets or sets the currently selected codepoint. Causes the Viewport to scroll to make the selected code point visible.
+ /// Gets or sets the currently selected codepoint. Causes the Viewport to scroll to make the selected code point
+ /// visible.
///
public int SelectedCodePoint
{
@@ -571,58 +584,48 @@ internal class CharMap : View, IDesignable
Point prevCursor = Cursor;
int newSelectedCodePoint = Math.Clamp (value, 0, _maxCodePoint);
- ScrollToMakeRowVisible (newSelectedCodePoint / 16 * _rowHeight, prevCursor.Y);
- ScrollToMakeColumnVisible (newSelectedCodePoint % 16 * COLUMN_WIDTH, prevCursor.X);
-
- if (_selected != newSelectedCodePoint)
+ Point newCursor = new ()
{
- _selected = newSelectedCodePoint;
- SetNeedsDraw ();
- SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint, null));
- }
+ X = newSelectedCodePoint % 16 * COLUMN_WIDTH + RowLabelWidth + 1 - Viewport.X,
+ Y = newSelectedCodePoint / 16 * _rowHeight + 1 - Viewport.Y
+ };
+
+ // Ensure the new cursor position is visible
+ EnsureCursorIsVisible (newCursor);
+
+ _selected = newSelectedCodePoint;
+ SetNeedsDraw ();
+ SelectedCodePointChanged?.Invoke (this, new (SelectedCodePoint, null));
}
}
- private void ScrollToMakeRowVisible (int row, int prevCursorY)
+ private void EnsureCursorIsVisible (Point newCursor)
{
- int height = Viewport.Height - 1; // Header
- int delta = row - (Viewport.Y + height);
- int scroll = Viewport.Height - (prevCursorY - delta);
+ // Adjust vertical scrolling
+ if (newCursor.Y < 1) // Header is at Y = 0
+ {
+ ScrollVertical (newCursor.Y - 1);
+ }
+ else if (newCursor.Y >= Viewport.Height)
+ {
+ ScrollVertical (newCursor.Y - Viewport.Height + 1);
+ }
- if (row - Viewport.Y < 0)
+ _vScrollBar.ContentPosition = Viewport.Y;
+
+ // Adjust horizontal scrolling
+ if (newCursor.X < RowLabelWidth + 1)
{
- // Moving up.
- Viewport = Viewport with { Y = Viewport.Y + (row - Viewport.Y) };
+ ScrollHorizontal (newCursor.X - (RowLabelWidth + 1));
}
- else if (row - Viewport.Y >= height)
+ else if (newCursor.X >= Viewport.Width)
{
- // Moving down.
- Viewport = Viewport with { Y = Math.Min (Viewport.Y + scroll, GetContentSize ().Height - height ) };
+ ScrollHorizontal (newCursor.X - Viewport.Width + 1);
}
- _vScrollBar.ContentPosition = row;
+
+ _hScrollBar.ContentPosition = Viewport.X;
}
- private void ScrollToMakeColumnVisible (int col, int prevCursorX)
- {
- int width = Viewport.Width - RowLabelWidth;
- int delta = col - (Viewport.X + width);
- int scroll = Viewport.Width - (prevCursorX - delta);
-
- if (col - Viewport.X < 0)
- {
- // Moving left.
- Viewport = Viewport with { X = Viewport.X + (col - Viewport.X) };
- _hScrollBar.ContentPosition = col;
- }
- else if (col - Viewport.X >= width)
- {
- // Moving right.
- Viewport = Viewport with { X = Math.Min (Viewport.X + scroll, GetContentSize ().Width - width) };
- _hScrollBar.ContentPosition = col;
- }
- }
-
-
public bool ShowGlyphWidths
{
get => _rowHeight == 2;
@@ -997,7 +1000,7 @@ internal class CharMap : View, IDesignable
document.RootElement,
new
JsonSerializerOptions
- { WriteIndented = true }
+ { WriteIndented = true }
);
}