diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index 4270a8d8f..1420367c0 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -649,6 +649,7 @@ public partial class View start = nextStart; x = startOffsetX; y = startOffsetY; + break; } @@ -664,7 +665,7 @@ public partial class View } } - return null; + return start; } #nullable restore diff --git a/Terminal.Gui/View/ViewContent.cs b/Terminal.Gui/View/ViewContent.cs index a65a54e86..dbd7f031a 100644 --- a/Terminal.Gui/View/ViewContent.cs +++ b/Terminal.Gui/View/ViewContent.cs @@ -3,10 +3,10 @@ namespace Terminal.Gui; /// -/// Settings for how scrolling the on the View's Content Area is handled. +/// Settings for how the behaves relative to the View's Content area. /// [Flags] -public enum ScrollSettings +public enum ViewportSettings { /// /// No settings. @@ -22,7 +22,7 @@ public enum ScrollSettings /// When not set, is constrained to the bounds of the Content Area rectangle in the horizontal direction. /// /// - AllowViewportOutsideContentHorizontal = 1, + AllowNegativeX = 1, /// /// If set, can be set to a rectangle that does not perfectly intersect with the Content Area @@ -33,7 +33,7 @@ public enum ScrollSettings /// When not set, is constrained to the bounds of the Content Area rectangle in the vertical direction. /// /// - AllowViewportOutsideContentVertical = 2, + AllowNegativeY = 2, /// /// If set, can be set to a rectangle that does not perfectly intersect with the Content Area @@ -44,7 +44,13 @@ public enum ScrollSettings /// When not set, is constrained to the bounds of the Content Area rectangle. /// /// - AllowViewportOutsideContent = AllowViewportOutsideContentHorizontal | AllowViewportOutsideContentVertical + AllowNegativeLocation = AllowNegativeX | AllowNegativeY, + + AllowXGreaterThanContentWidth = 4, + AllowYGreaterThanContentHeight = 8, + AllowLocationCreaterThanContentSize = AllowXGreaterThanContentWidth | AllowYGreaterThanContentHeight, + + ClearVisibleContentOnly = 16, } public partial class View @@ -147,22 +153,22 @@ public partial class View #region Viewport - private ScrollSettings _scrollSettings; + private ViewportSettings _viewportSettings; /// /// Gets or sets how scrolling the on the View's Content Area is handled. /// - public ScrollSettings ScrollSettings + public ViewportSettings ViewportSettings { - get => _scrollSettings; + get => _viewportSettings; set { - if (_scrollSettings == value) + if (_viewportSettings == value) { return; } - _scrollSettings = value; + _viewportSettings = value; // Force set Viewport to cause settings to be applied as needed SetViewport (Viewport); @@ -195,7 +201,7 @@ public partial class View /// . This enables virtual zoom. /// /// - /// The property controls how scrolling is handled. If is + /// The property controls how scrolling is handled. If is /// /// /// If is the value of Viewport is indeterminate until @@ -274,33 +280,41 @@ public partial class View }; - void ApplySettings (ref Rectangle location) + void ApplySettings (ref Rectangle newViewport) { - if (!ScrollSettings.HasFlag (ScrollSettings.AllowViewportOutsideContentHorizontal)) + if (!ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX)) { - if (location.Y + Viewport.Height > ContentSize.Height) - { - location.Y = ContentSize.Height - Viewport.Height; - } - if (location.Y < 0) + if (newViewport.X < 0) { - location.Y = 0; + newViewport.X = 0; } } - if (!ScrollSettings.HasFlag (ScrollSettings.AllowViewportOutsideContentVertical)) + if (!ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth)) { - if (location.X + Viewport.Width > ContentSize.Width) + if (newViewport.X >= ContentSize.Width) { - location.X = ContentSize.Width - Viewport.Width; - } - - if (location.X < 0) - { - location.X = 0; + newViewport.X = ContentSize.Width - 1; } } + + if (!ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY)) + { + if (newViewport.Y < 0) + { + newViewport.Y = 0; + } + } + + if (!ViewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight)) + { + if (newViewport.Y >= ContentSize.Height) + { + newViewport.Y = ContentSize.Height - 1; + } + } + } } diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 3ef5a5825..cb07b673f 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -99,13 +99,6 @@ public partial class View Rectangle toClear = new (-Viewport.Location.X, -Viewport.Location.Y, ContentSize.Width, ContentSize.Height); - // If toClear does not fill the Viewport, we need to clear the area outside toClear with DarkGray. - // TODO: Need a configurable color for this - // PERF: Put an if around this if toClear is not smaller than Viewport - Attribute prev = Driver.SetAttribute (new Attribute (ColorName.DarkGray, ColorName.DarkGray)); - Rectangle viewport = new (Point.Empty, Viewport.Size); - Driver.FillRect (ViewportToScreen (viewport)); - Driver.SetAttribute (prev); Clear (toClear); } @@ -433,7 +426,14 @@ public partial class View { if (SuperView is { }) { - ClearVisibleContent (); + if (ViewportSettings.HasFlag(ViewportSettings.ClearVisibleContentOnly)) + { + ClearVisibleContent (); + } + else + { + Clear (); + } } if (!string.IsNullOrEmpty (TextFormatter.Text)) diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 9c8ff6f7f..507dcc852 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -1,4 +1,5 @@ using System.Collections; +using static Terminal.Gui.SpinnerStyle; namespace Terminal.Gui; @@ -106,13 +107,12 @@ public class ListView : View public ListView () { CanFocus = true; - ScrollSettings = ScrollSettings.AllowViewportOutsideContent; // Things this view knows how to do AddCommand (Command.LineUp, () => MoveUp ()); AddCommand (Command.LineDown, () => MoveDown ()); - AddCommand (Command.ScrollUp, () => ScrollUp (1)); - AddCommand (Command.ScrollDown, () => ScrollDown (1)); + AddCommand (Command.ScrollUp, () => ScrollVertical (-1)); + AddCommand (Command.ScrollDown, () => ScrollVertical (1)); AddCommand (Command.PageUp, () => MovePageUp ()); AddCommand (Command.PageDown, () => MovePageDown ()); AddCommand (Command.TopHome, () => MoveHome ()); @@ -121,6 +121,9 @@ public class ListView : View AddCommand (Command.OpenSelectedItem, () => OnOpenSelectedItem ()); AddCommand (Command.Select, () => MarkUnmarkRow ()); + AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1)); + AddCommand (Command.ScrollRight, () => ScrollHorizontal (1)); + // Default keybindings for all ListViews KeyBindings.Add (Key.CursorUp, Command.LineUp); KeyBindings.Add (Key.P.WithCtrl, Command.LineUp); @@ -261,7 +264,7 @@ public class ListView : View } _source = value; - ContentSize = new Size (Viewport.Width, _source?.Count ?? 0); + ContentSize = new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width); Viewport = Viewport with { Y = 0 }; KeystrokeNavigator.Collection = _source?.ToList (); _selected = -1; @@ -383,28 +386,28 @@ public class ListView : View if (me.Flags == MouseFlags.WheeledDown) { - ScrollDown (1); + ScrollVertical (1); return true; } if (me.Flags == MouseFlags.WheeledUp) { - ScrollUp (1); + ScrollVertical (-1); return true; } if (me.Flags == MouseFlags.WheeledRight) { - ScrollRight (1); + ScrollHorizontal (1); return true; } if (me.Flags == MouseFlags.WheeledLeft) { - ScrollLeft (1); + ScrollHorizontal(-1); return true; } @@ -792,46 +795,6 @@ public class ListView : View /// This event is invoked when this is being drawn before rendering. public event EventHandler RowRender; - /// Scrolls the view down by items. - /// Number of items to scroll down. - public virtual bool ScrollDown (int items) - { - Viewport = Viewport with { Y = Math.Max (Math.Min (Viewport.Y + items, _source.Count - 1), 0) }; - SetNeedsDisplay (); - - return true; - } - - /// Scrolls the view left. - /// Number of columns to scroll left. - public virtual bool ScrollLeft (int cols) - { - Viewport = Viewport with { X = Math.Max (Viewport.X - cols, 0) }; - SetNeedsDisplay (); - - return true; - } - - /// Scrolls the view right. - /// Number of columns to scroll right. - public virtual bool ScrollRight (int cols) - { - Viewport = Viewport with { X = Math.Max (Math.Min (Viewport.X + cols, MaxLength - 1), 0) }; - SetNeedsDisplay (); - - return true; - } - - /// Scrolls the view up by items. - /// Number of items to scroll up. - public virtual bool ScrollUp (int items) - { - Viewport = Viewport with { Y = Math.Max (Viewport.Y - items, 0) }; - SetNeedsDisplay (); - - return true; - } - /// This event is raised when the selected item in the has changed. public event EventHandler SelectedItemChanged; @@ -1040,8 +1003,8 @@ public class ListWrapper : IListDataSource private void RenderUstr (ConsoleDriver driver, string ustr, int col, int line, int width, int start = 0) { - string u = TextFormatter.ClipAndJustify (ustr, width, TextAlignment.Left); - driver.AddStr (u); + string str = start > ustr.GetColumns () ? string.Empty : ustr.Substring (start); + string u = TextFormatter.ClipAndJustify (str, width, TextAlignment.Left); driver.AddStr (u); width -= u.GetColumns (); while (width-- > 0) diff --git a/UICatalog/Scenarios/VirtualContentScrolling.cs b/UICatalog/Scenarios/VirtualContentScrolling.cs index 35918506b..81aaecd59 100644 --- a/UICatalog/Scenarios/VirtualContentScrolling.cs +++ b/UICatalog/Scenarios/VirtualContentScrolling.cs @@ -25,7 +25,7 @@ public class VirtualScrolling : Scenario // TODO: Add a way to set the scroll settings in the Scenario ContentSize = new Size (60, 40); - ScrollSettings = ScrollSettings.AllowViewportOutsideContent; + ViewportSettings |= ViewportSettings.ClearVisibleContentOnly; // Things this view knows how to do AddCommand (Command.ScrollDown, () => ScrollVertical (1)); @@ -104,60 +104,107 @@ public class VirtualScrolling : Scenario var view = new VirtualDemoView { Title = "Virtual Scrolling" }; // Add Scroll Setting UI to Padding - view.Padding.Thickness = new (0, 2, 0, 0); + view.Padding.Thickness = new (0, 3, 0, 0); view.Padding.ColorScheme = Colors.ColorSchemes["Error"]; - var cbAllowXBeyondContent = new CheckBox () + var cbAllowNegativeX = new CheckBox () { - Title = "Allow Viewport._X Beyond Content", + Title = "Allow _X < 0", Y = 0, CanFocus = false }; - cbAllowXBeyondContent.Checked = view.ScrollSettings.HasFlag (ScrollSettings.AllowViewportOutsideContentVertical); - cbAllowXBeyondContent.Toggled += NoRestrictHorizontal_Toggled; + cbAllowNegativeX.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX); + cbAllowNegativeX.Toggled += AllowNegativeX_Toggled; - void NoRestrictHorizontal_Toggled (object sender, StateEventArgs e) + void AllowNegativeX_Toggled (object sender, StateEventArgs e) { if (e.NewValue == true) { - view.ScrollSettings = view.ScrollSettings | ScrollSettings.AllowViewportOutsideContentVertical; + view.ViewportSettings |= ViewportSettings.AllowNegativeX; } else { - view.ScrollSettings = view.ScrollSettings & ~ScrollSettings.AllowViewportOutsideContentVertical; + view.ViewportSettings &= ~ViewportSettings.AllowNegativeX; } } - view.Padding.Add (cbAllowXBeyondContent); + view.Padding.Add (cbAllowNegativeX); - var cbAllowYBeyondContent = new CheckBox () + var cbAllowNegativeY = new CheckBox () { - Title = "Allow Viewport._Y Beyond Content", - X = Pos.Right (cbAllowXBeyondContent) + 1, + Title = "Allow _Y < 0", + X = Pos.Right (cbAllowNegativeX) + 1, Y = 0, CanFocus = false }; - cbAllowYBeyondContent.Checked = view.ScrollSettings.HasFlag (ScrollSettings.AllowViewportOutsideContentHorizontal); - cbAllowYBeyondContent.Toggled += NoRestrictVertical_Toggled; + cbAllowNegativeY.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY); + cbAllowNegativeY.Toggled += AllowNegativeY_Toggled; - void NoRestrictVertical_Toggled (object sender, StateEventArgs e) + void AllowNegativeY_Toggled (object sender, StateEventArgs e) { if (e.NewValue == true) { - view.ScrollSettings = view.ScrollSettings | ScrollSettings.AllowViewportOutsideContentHorizontal; + view.ViewportSettings |= ViewportSettings.AllowNegativeY; } else { - view.ScrollSettings = view.ScrollSettings & ~ScrollSettings.AllowViewportOutsideContentHorizontal; + view.ViewportSettings &= ~ViewportSettings.AllowNegativeY; } } - view.Padding.Add (cbAllowYBeyondContent); + view.Padding.Add (cbAllowNegativeY); + + var cbAllowXGreaterThanContentWidth = new CheckBox () + { + Title = "Allow X > Content", + Y = Pos.Bottom(cbAllowNegativeX), + CanFocus = false + }; + cbAllowXGreaterThanContentWidth.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth); + cbAllowXGreaterThanContentWidth.Toggled += AllowXGreaterThanContentWidth_Toggled; + + void AllowXGreaterThanContentWidth_Toggled (object sender, StateEventArgs e) + { + if (e.NewValue == true) + { + view.ViewportSettings |= ViewportSettings.AllowXGreaterThanContentWidth; + } + else + { + view.ViewportSettings &= ~ViewportSettings.AllowXGreaterThanContentWidth; + } + } + + view.Padding.Add (cbAllowXGreaterThanContentWidth); + + var cbAllowYGreaterThanContentHeight = new CheckBox () + { + Title = "Allow Y > Content", + X = Pos.Right (cbAllowXGreaterThanContentWidth) + 1, + Y = Pos.Bottom (cbAllowNegativeX), + CanFocus = false + }; + cbAllowYGreaterThanContentHeight.Checked = view.ViewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight); + cbAllowYGreaterThanContentHeight.Toggled += AllowYGreaterThanContentHeight_Toggled; + + void AllowYGreaterThanContentHeight_Toggled (object sender, StateEventArgs e) + { + if (e.NewValue == true) + { + view.ViewportSettings |= ViewportSettings.AllowYGreaterThanContentHeight; + } + else + { + view.ViewportSettings &= ~ViewportSettings.AllowYGreaterThanContentHeight; + } + } + + view.Padding.Add (cbAllowYGreaterThanContentHeight); var labelContentSize = new Label () { Title = "_ContentSize:", - Y = 1, + Y = Pos.Bottom(cbAllowYGreaterThanContentHeight), }; var contentSizeWidth = new Buttons.NumericUpDown() diff --git a/UnitTests/View/Layout/ToScreenTests.cs b/UnitTests/View/Layout/ToScreenTests.cs index e2152c476..7eddeb1e4 100644 --- a/UnitTests/View/Layout/ToScreenTests.cs +++ b/UnitTests/View/Layout/ToScreenTests.cs @@ -630,7 +630,7 @@ public class ToScreenTests (ITestOutputHelper output) { Width = 10, Height = 10, - ScrollSettings = ScrollSettings.AllowViewportOutsideContent + ViewportSettings = ViewportSettings.AllowNegativeLocation }; Rectangle testRect = new Rectangle (0, 0, 1, 1); diff --git a/UnitTests/View/Layout/ViewportTests.cs b/UnitTests/View/Layout/ViewportTests.cs index 12d930290..5c0e0e603 100644 --- a/UnitTests/View/Layout/ViewportTests.cs +++ b/UnitTests/View/Layout/ViewportTests.cs @@ -161,7 +161,7 @@ public class ViewportTests (ITestOutputHelper output) { Width = 10, Height = 10, - ScrollSettings = ScrollSettings.AllowViewportOutsideContent + ViewportSettings = ViewportSettings.AllowNegativeLocation }; Assert.Equal (new Rectangle (0, 0, 10, 10), view.Frame); diff --git a/UnitTests/Views/ListViewTests.cs b/UnitTests/Views/ListViewTests.cs index 252efc537..611cc719f 100644 --- a/UnitTests/Views/ListViewTests.cs +++ b/UnitTests/Views/ListViewTests.cs @@ -78,7 +78,7 @@ public class ListViewTests _output ); - Assert.True (lv.ScrollDown (10)); + Assert.True (lv.ScrollVertical(10)); lv.Draw (); Assert.Equal (-1, lv.SelectedItem); @@ -141,7 +141,7 @@ public class ListViewTests _output ); - Assert.True (lv.ScrollUp (20)); + Assert.True (lv.ScrollVertical (-20)); lv.Draw (); Assert.Equal (19, lv.SelectedItem); @@ -183,7 +183,7 @@ public class ListViewTests _output ); - Assert.True (lv.ScrollUp (20)); + Assert.True (lv.ScrollVertical (-20)); lv.Draw (); Assert.Equal (19, lv.SelectedItem); @@ -246,7 +246,7 @@ public class ListViewTests _output ); - Assert.True (lv.ScrollDown (20)); + Assert.True (lv.ScrollVertical (20)); lv.Draw (); Assert.Equal (0, lv.SelectedItem); @@ -671,7 +671,7 @@ Item 6", private class NewListDataSource : IListDataSource { public int Count => 0; - public int Length => throw new NotImplementedException (); + public int Length => 0; public bool IsMarked (int item) { throw new NotImplementedException (); } public void Render (