diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 897d95b33..f6f597ee0 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -88,7 +88,7 @@ public static partial class Application #if DEBUG_IDISPOSABLE // Don't dispose the toplevels. It's up to caller dispose them - Debug.Assert (t.WasDisposed); + //Debug.Assert (t.WasDisposed); #endif } @@ -1495,7 +1495,7 @@ public static partial class Application View = view }; - if (MouseGrabView.Viewport.Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false) + if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false) { // The mouse has moved outside the Viewport of the view that // grabbed the mouse, so we tell the view that last got @@ -1551,7 +1551,7 @@ public static partial class Application View = view }; } - else if (view.ViewportToScreen (view.Viewport).Contains (a.MouseEvent.X, a.MouseEvent.Y)) + else if (view.ViewportToScreen (Rectangle.Empty with {Size = view.Viewport.Size}).Contains (a.MouseEvent.X, a.MouseEvent.Y)) { Point viewportLocation = view.ScreenToViewport (a.MouseEvent.X, a.MouseEvent.Y); @@ -1591,10 +1591,16 @@ public static partial class Application //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}"); - if (view.OnMouseEvent (me)) + while (view is {} && !view.OnMouseEvent (me)) { - // Should we bubble up the event, if it is not handled? - //return; + if (view is Adornment ad) + { + view = ad.Parent.SuperView; + } + else + { + view = view.SuperView; + } } BringOverlappedTopToFront (); diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 5e970d86f..db13b5930 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -202,14 +202,14 @@ public class TextFormatter /// Causes the text to be formatted (references ). Sets to /// false. /// - /// Specifies the screen-relative location and maximum size for drawing the text. + /// Specifies the screen-relative location and maximum size for drawing the text. /// The color to use for all text except the hotkey /// The color to use to draw the hotkey /// Specifies the screen-relative location and maximum container size. /// The console driver currently used by the application. /// public void Draw ( - Rectangle viewport, + Rectangle screen, Attribute normalColor, Attribute hotColor, Rectangle maximum = default, @@ -240,46 +240,46 @@ public class TextFormatter } bool isVertical = IsVerticalDirection (Direction); - Rectangle maxViewport = viewport; + Rectangle maxScreen = screen; if (driver is { }) { // INTENT: What, exactly, is the intent of this? - maxViewport = maximum == default (Rectangle) - ? viewport + maxScreen = maximum == default (Rectangle) + ? screen : new ( - Math.Max (maximum.X, viewport.X), - Math.Max (maximum.Y, viewport.Y), + Math.Max (maximum.X, screen.X), + Math.Max (maximum.Y, screen.Y), Math.Max ( - Math.Min (maximum.Width, maximum.Right - viewport.Left), + Math.Min (maximum.Width, maximum.Right - screen.Left), 0 ), Math.Max ( Math.Min ( maximum.Height, - maximum.Bottom - viewport.Top + maximum.Bottom - screen.Top ), 0 ) ); } - if (maxViewport.Width == 0 || maxViewport.Height == 0) + if (maxScreen.Width == 0 || maxScreen.Height == 0) { return; } - int lineOffset = !isVertical && viewport.Y < 0 ? Math.Abs (viewport.Y) : 0; + int lineOffset = !isVertical && screen.Y < 0 ? Math.Abs (screen.Y) : 0; for (int line = lineOffset; line < linesFormatted.Count; line++) { - if ((isVertical && line > viewport.Width) || (!isVertical && line > viewport.Height)) + if ((isVertical && line > screen.Width) || (!isVertical && line > screen.Height)) { continue; } - if ((isVertical && line >= maxViewport.Left + maxViewport.Width) - || (!isVertical && line >= maxViewport.Top + maxViewport.Height + lineOffset)) + if ((isVertical && line >= maxScreen.Left + maxScreen.Width) + || (!isVertical && line >= maxScreen.Top + maxScreen.Height + lineOffset)) { break; } @@ -305,14 +305,14 @@ public class TextFormatter if (isVertical) { int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth); - x = viewport.Right - runesWidth; - CursorPosition = viewport.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); + x = screen.Right - runesWidth; + CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); } else { int runesWidth = StringExtensions.ToString (runes).GetColumns (); - x = viewport.Right - runesWidth; - CursorPosition = viewport.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); + x = screen.Right - runesWidth; + CursorPosition = screen.Width - runesWidth + (_hotKeyPos > -1 ? _hotKeyPos : 0); } } else if (Alignment is TextAlignment.Left or TextAlignment.Justified) @@ -322,11 +322,11 @@ public class TextFormatter int runesWidth = line > 0 ? GetWidestLineLength (linesFormatted, 0, line, TabWidth) : 0; - x = viewport.Left + runesWidth; + x = screen.Left + runesWidth; } else { - x = viewport.Left; + x = screen.Left; } CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0; @@ -336,16 +336,16 @@ public class TextFormatter if (isVertical) { int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth); - x = viewport.Left + line + (viewport.Width - runesWidth) / 2; + x = screen.Left + line + (screen.Width - runesWidth) / 2; - CursorPosition = (viewport.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); + CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); } else { int runesWidth = StringExtensions.ToString (runes).GetColumns (); - x = viewport.Left + (viewport.Width - runesWidth) / 2; + x = screen.Left + (screen.Width - runesWidth) / 2; - CursorPosition = (viewport.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); + CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); } } else @@ -358,35 +358,35 @@ public class TextFormatter { if (isVertical) { - y = viewport.Bottom - runes.Length; + y = screen.Bottom - runes.Length; } else { - y = viewport.Bottom - linesFormatted.Count + line; + y = screen.Bottom - linesFormatted.Count + line; } } else if (VerticalAlignment is VerticalTextAlignment.Top or VerticalTextAlignment.Justified) { if (isVertical) { - y = viewport.Top; + y = screen.Top; } else { - y = viewport.Top + line; + y = screen.Top + line; } } else if (VerticalAlignment == VerticalTextAlignment.Middle) { if (isVertical) { - int s = (viewport.Height - runes.Length) / 2; - y = viewport.Top + s; + int s = (screen.Height - runes.Length) / 2; + y = screen.Top + s; } else { - int s = (viewport.Height - linesFormatted.Count) / 2; - y = viewport.Top + line + s; + int s = (screen.Height - linesFormatted.Count) / 2; + y = screen.Top + line + s; } } else @@ -394,9 +394,9 @@ public class TextFormatter throw new ArgumentOutOfRangeException ($"{nameof (VerticalAlignment)}"); } - int colOffset = viewport.X < 0 ? Math.Abs (viewport.X) : 0; - int start = isVertical ? viewport.Top : viewport.Left; - int size = isVertical ? viewport.Height : viewport.Width; + int colOffset = screen.X < 0 ? Math.Abs (screen.X) : 0; + int start = isVertical ? screen.Top : screen.Left; + int size = isVertical ? screen.Height : screen.Width; int current = start + colOffset; List lastZeroWidthPos = null; Rune rune = default; @@ -422,8 +422,8 @@ public class TextFormatter break; } - if ((!isVertical && current - start > maxViewport.Left + maxViewport.Width - viewport.X + colOffset) - || (isVertical && idx > maxViewport.Top + maxViewport.Height - viewport.Y)) + if ((!isVertical && current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset) + || (isVertical && idx > maxScreen.Top + maxScreen.Height - screen.Y)) { break; } diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs index 46401dbca..aff0bb308 100644 --- a/Terminal.Gui/View/Adornment/Adornment.cs +++ b/Terminal.Gui/View/Adornment/Adornment.cs @@ -267,7 +267,7 @@ public class Adornment : View if (!Parent.CanFocus || !Parent.Arrangement.HasFlag (ViewArrangement.Movable)) { - return true; + return false; } // BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312 diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index fc63e9031..be0051360 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -186,6 +186,11 @@ public class Border : Adornment /// public override void OnDrawContent (Rectangle viewport) { + if (Parent?.Title == "Title") + { + + } + base.OnDrawContent (viewport); if (Thickness == Thickness.Empty) diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index cf23a7a14..f5a2c21a8 100644 --- a/Terminal.Gui/View/Layout/ViewLayout.cs +++ b/Terminal.Gui/View/Layout/ViewLayout.cs @@ -1,4 +1,5 @@ using System.Diagnostics; +using System.IO.Compression; namespace Terminal.Gui; @@ -73,12 +74,9 @@ public partial class View _height = _frame.Height; // TODO: Figure out if the below can be optimized. - if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) + if (IsInitialized) { - LayoutAdornments (); - SetTextFormatterSize (); - SetNeedsLayout (); - SetNeedsDisplay (); + OnResizeNeeded (); } } } @@ -102,12 +100,11 @@ public partial class View } Point viewportOffset = super.GetViewportOffset (); - viewportOffset.Offset (super.Frame.X, super.Frame.Y); + viewportOffset.Offset (super.Frame.X - super.Viewport.X, super.Frame.Y - super.Viewport.Y); ret.X += viewportOffset.X; ret.Y += viewportOffset.Y; super = super.SuperView; } - return ret; } @@ -120,15 +117,18 @@ public partial class View /// Screen-relative row. public virtual Point ScreenToFrame (int x, int y) { - Point superViewBoundsOffset = SuperView?.GetViewportOffset () ?? Point.Empty; + Point superViewViewportOffset = SuperView?.GetViewportOffset () ?? Point.Empty; + if (SuperView is null) { - superViewBoundsOffset.Offset (x - Frame.X, y - Frame.Y); - return superViewBoundsOffset; - } + superViewViewportOffset.Offset (x - Frame.X, y - Frame.Y); - var frame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y); + return superViewViewportOffset; + } + superViewViewportOffset.Offset (-SuperView.Viewport.X, -SuperView.Viewport.Y); + Point frame = SuperView.ScreenToFrame (x - superViewViewportOffset.X, y - superViewViewportOffset.Y); frame.Offset (-Frame.X, -Frame.Y); + return frame; } @@ -284,126 +284,6 @@ public partial class View #endregion Frame - #region Viewport - - private Point _viewportOffset; - - /// - /// Gets or sets the rectangle describing the portion of the View's content that is visible to the user. - /// The viewport Location is relative to the top-left corner of the inner rectangle of the s. - /// If the viewport Size is the sames as the the Location will be 0, 0. - /// Non-zero values for the location indicate the visible area is offset into the View's virtual . - /// - /// The rectangle describing the location and size of the viewport into the View's virtual content, described by . - /// - /// - /// If is the value of Viewport is indeterminate until - /// the view has been initialized ( is true) and has been - /// called. - /// - /// - /// Updates to the Viewport Size updates , and has the same impact as updating the - /// . - /// - /// - /// Altering the Viewport Size will eventually (when the view is next laid out) cause the - /// and methods to be called. - /// - /// - public virtual Rectangle Viewport - { - get - { -#if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) - { - Debug.WriteLine ( - $"WARNING: Viewport is being accessed before the View has been initialized. This is likely a bug in {this}" - ); - } -#endif // DEBUG - - if (Margin is null || Border is null || Padding is null) - { - // CreateAdornments has not been called yet. - return new (_viewportOffset, Frame.Size); - } - - Thickness totalThickness = GetAdornmentsThickness (); - - return new (_viewportOffset, - new (Math.Max (0, Frame.Size.Width - totalThickness.Horizontal), - Math.Max (0, Frame.Size.Height - totalThickness.Vertical))); - } - set - { - _viewportOffset = value.Location; - - Thickness totalThickness = GetAdornmentsThickness (); - - Frame = Frame with - { - Size = new ( - value.Size.Width + totalThickness.Horizontal, - value.Size.Height + totalThickness.Vertical) - }; - } - } - - /// Converts a -relative rectangle to a screen-relative rectangle. - public Rectangle ViewportToScreen (in Rectangle viewport) - { - // Translate bounds to Frame (our SuperView's Viewport-relative coordinates) - Rectangle screen = FrameToScreen (); - Point viewportOffset = GetViewportOffset (); - screen.Offset (viewportOffset.X + viewport.X, viewportOffset.Y + viewport.Y); - - if (SuperView is { }) - { - screen.Offset(-SuperView.Viewport.X, -SuperView.Viewport.Y); - } - - - return new (screen.Location, viewport.Size); - } - - /// Converts a screen-relative coordinate to a Viewport-relative coordinate. - /// The coordinate relative to this view's . - /// Screen-relative column. - /// Screen-relative row. - public Point ScreenToViewport (int x, int y) - { - Point viewportOffset = GetViewportOffset (); - Point screen = ScreenToFrame (x, y); - screen.Offset (-viewportOffset.X + Viewport.X, -viewportOffset.Y + Viewport.Y); - - return screen; - } - - /// - /// Helper to get the X and Y offset of the Viewport from the Frame. This is the sum of the Left and Top properties - /// of , and . - /// - public Point GetViewportOffset () - { - return Padding is null ? Point.Empty : Padding.Thickness.GetInside (Padding.Frame).Location; - } - - private Size _contentSize; - /// - /// Gets or sets the size of the View's content. If the value is Size.Empty the size of the content is - /// the same as the size of the , and Viewport.Location will always be 0, 0. - /// If a positive size is provided, describes the portion of the content currently visible - /// to the view. This enables virtual scrolling. - /// - public Size ContentSize - { - get => _contentSize == Size.Empty ? Viewport.Size : _contentSize; - set => _contentSize = value; - } - - #endregion Viewport - #region AutoSize private bool _autoSize; @@ -657,25 +537,20 @@ public partial class View #endregion Layout Engine - internal bool LayoutNeeded { get; private set; } = true; - /// - /// Indicates whether the specified SuperView-relative coordinates are within the View's . + /// Indicates whether the specified SuperView-relative coordinates are within the View's . /// /// SuperView-relative X coordinate. /// SuperView-relative Y coordinate. /// if the specified SuperView-relative coordinates are within the View. - public virtual bool Contains (int x, int y) - { - return Frame.Contains (x, y); - } + public virtual bool Contains (int x, int y) { return Frame.Contains (x, y); } #nullable enable /// Finds the first Subview of that is visible at the provided location. /// - /// - /// Used to determine what view the mouse is over. - /// + /// + /// Used to determine what view the mouse is over. + /// /// /// The view to scope the search by. /// .SuperView-relative X coordinate. @@ -725,10 +600,10 @@ public partial class View { View nextStart = start.InternalSubviews [i]; - if (nextStart.Visible && nextStart.Contains (startOffsetX, startOffsetY)) + if (nextStart.Visible && nextStart.Contains (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)) { // TODO: Remove recursion - return FindDeepestView (nextStart, startOffsetX, startOffsetY) ?? nextStart; + return FindDeepestView (nextStart, startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y) ?? nextStart; } } } @@ -767,6 +642,7 @@ public partial class View { int maxDimension; View superView; + statusBar = null; if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) { @@ -801,7 +677,8 @@ public partial class View } //System.Diagnostics.Debug.WriteLine ($"nx:{nx}, rWidth:{rWidth}"); - bool menuVisible, statusVisible; + bool menuVisible = false; + bool statusVisible = false; if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) { @@ -811,12 +688,15 @@ public partial class View { View t = viewToMove.SuperView; - while (t is not Toplevel) + while (t is { } and not Toplevel) { t = t.SuperView; } - menuVisible = ((Toplevel)t).MenuBar?.Visible == true; + if (t is Toplevel toplevel) + { + menuVisible = toplevel.MenuBar?.Visible == true; + } } if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) @@ -839,13 +719,16 @@ public partial class View { View t = viewToMove.SuperView; - while (t is not Toplevel) + while (t is { } and not Toplevel) { t = t.SuperView; } - statusVisible = ((Toplevel)t).StatusBar?.Visible == true; - statusBar = ((Toplevel)t).StatusBar; + if (t is Toplevel toplevel) + { + statusVisible = toplevel.StatusBar?.Visible == true; + statusBar = toplevel.StatusBar; + } } if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) @@ -854,7 +737,7 @@ public partial class View } else { - maxDimension = statusVisible ? viewToMove.SuperView.Frame.Height - 1 : viewToMove.SuperView.Frame.Height; + maxDimension = statusVisible ? viewToMove.SuperView.Viewport.Height - 1 : viewToMove.SuperView.Viewport.Height; } if (superView.Margin is { } && superView == viewToMove.SuperView) @@ -935,7 +818,7 @@ public partial class View foreach (View v in ordered) { - LayoutSubview (v, new (GetViewportOffset (), ContentSize)); + LayoutSubview (v, ContentSize); } // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case. @@ -944,7 +827,7 @@ public partial class View { foreach ((View from, View to) in edges) { - LayoutSubview (to, from.Frame); + LayoutSubview (to, from.ContentSize); } } @@ -952,6 +835,12 @@ public partial class View OnLayoutComplete (new () { OldViewport = oldViewport }); } + private void LayoutSubview (View v, Size contentSize) + { + v.SetRelativeLayout (contentSize); + v.LayoutSubviews (); + v.LayoutNeeded = false; + } /// Indicates that the view does not need to be laid out. protected void ClearLayoutNeeded () { LayoutNeeded = false; } @@ -985,8 +874,8 @@ public partial class View // First try SuperView.Viewport, then Application.Top, then Driver.Viewport. // Finally, if none of those are valid, use int.MaxValue (for Unit tests). Size contentSize = SuperView is { IsInitialized: true } ? SuperView.ContentSize : - Application.Top is { } && Application.Top.IsInitialized ? Application.Top.ContentSize : - Application.Driver?.Viewport.Size ?? new (int.MaxValue, int.MaxValue); + Application.Top is { } && Application.Top.IsInitialized ? Application.Top.ContentSize : + Application.Driver?.Viewport.Size ?? new (int.MaxValue, int.MaxValue); SetRelativeLayout (contentSize); // TODO: Determine what, if any of the below is actually needed here. @@ -1003,6 +892,7 @@ public partial class View SetNeedsLayout (); } } + internal bool LayoutNeeded { get; private set; } = true; /// /// Sets the internal flag for this View and all of it's subviews and it's SuperView. @@ -1027,13 +917,20 @@ public partial class View } /// - /// Applies the view's position (, ) and dimension (, and - /// ) to , given the SuperView's ContentSize (nominally the - /// same as this.SuperView.ContentSize). + /// Adjusts given the SuperView's ContentSize (nominally the same as + /// this.SuperView.ContentSize) + /// and the position (, ) and dimension (, and + /// ). /// + /// + /// + /// If , , , or are + /// absolute, they will be updated to reflect the new size and position of the view. Otherwise, they + /// are left unchanged. + /// + /// /// - /// The size of the SuperView's content (nominally the same as - /// this.SuperView.ContentSize). + /// The size of the SuperView's content (nominally the same as this.SuperView.ContentSize). /// internal void SetRelativeLayout (Size superviewContentSize) { @@ -1423,17 +1320,6 @@ public partial class View return result; } // TopologicalSort - private void LayoutSubview (View v, Rectangle viewport) - { - //if (v.LayoutStyle == LayoutStyle.Computed) { - v.SetRelativeLayout (viewport.Size); - - //} - - v.LayoutSubviews (); - v.LayoutNeeded = false; - } - #region Diagnostics // Diagnostics to highlight when Width or Height is read before the view has been initialized diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 6e1f61466..f6718ccbf 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -85,7 +85,30 @@ public partial class View /// Clears with the normal background. /// - public void Clear () { Clear (Viewport); } + public void Clear () { Clear (new (Point.Empty, Viewport.Size)); } + + /// Clears the portion of the content that is visible with the normal background. If the content does not fill the Viewport, + /// the area not filled will be cleared with DarkGray. + /// + public void ClearVisibleContent () + { + if (Driver is null) + { + return; + } + + 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 + 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); + } /// Clears the specified -relative rectangle with the normal background. /// @@ -100,7 +123,7 @@ public partial class View Attribute prev = Driver.SetAttribute (GetNormalColor ()); // Clamp the region to the bounds of the view - viewport = Rectangle.Intersect (viewport, Viewport); + viewport = Rectangle.Intersect (viewport, new (Point.Empty, Viewport.Size)); Driver.FillRect (ViewportToScreen (viewport)); Driver.SetAttribute (prev); } @@ -124,7 +147,7 @@ public partial class View } Rectangle previous = Driver.Clip; - Driver.Clip = Rectangle.Intersect (previous, ViewportToScreen (Viewport)); + Driver.Clip = Rectangle.Intersect (previous, ViewportToScreen (Viewport with { Location = Point.Empty })); return previous; } @@ -321,12 +344,12 @@ public partial class View /// Moves the drawing cursor to the specified view-relative column and row in the view. /// - /// - /// If the provided coordinates are outside the visible content area, this method does nothing. - /// - /// - /// The top-left corner of the visible content area is ViewPort.Location. - /// + /// + /// If the provided coordinates are outside the visible content area, this method does nothing. + /// + /// + /// The top-left corner of the visible content area is ViewPort.Location. + /// /// /// Column (viewport-relative). /// Row (viewport-relative). @@ -406,7 +429,7 @@ public partial class View { if (SuperView is { }) { - Clear (viewport); + ClearVisibleContent (); } if (!string.IsNullOrEmpty (TextFormatter.Text)) @@ -419,8 +442,13 @@ public partial class View // This should NOT clear // TODO: If the output is not in the Viewport, do nothing + if (Viewport.Y != 0) + { } + + var drawRect = new Rectangle (ContentToScreen (Point.Empty), ContentSize); + TextFormatter?.Draw ( - ViewportToScreen (new Rectangle(Point.Empty, ContentSize)), + drawRect, HasFocus ? GetFocusColor () : GetNormalColor (), HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), Rectangle.Empty @@ -609,7 +637,7 @@ public partial class View foreach (View subview in Subviews) { - subview.ClearNeedsDisplay(); + subview.ClearNeedsDisplay (); } } } diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs index 6ef2c783c..5b16579b7 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/ViewMouse.cs @@ -104,7 +104,7 @@ public partial class View { if (!Enabled) { - return true; + return false; } if (!CanBeVisible (this)) diff --git a/Terminal.Gui/View/ViewScrolling.cs b/Terminal.Gui/View/ViewScrolling.cs new file mode 100644 index 000000000..1821ae9f0 --- /dev/null +++ b/Terminal.Gui/View/ViewScrolling.cs @@ -0,0 +1,261 @@ +using System.Diagnostics; + +namespace Terminal.Gui; + +/// +/// Controls the scrolling behavior of a view. +/// +[Flags] +public enum ScrollSettings +{ + /// + /// Default settings. + /// + Default = 0, + + /// + /// If set, does not restrict vertical scrolling to the content size. + /// + NoRestrictVertical = 1, + + /// + /// If set, does not restrict horizontal scrolling to the content size. + /// + NoRestrictHorizontal = 2, + + /// + /// If set, does not restrict either vertical or horizontal scrolling to the content size. + /// + NoRestrict = NoRestrictVertical | NoRestrictHorizontal +} + +public partial class View +{ + #region Content Area + + private Size _contentSize; + + /// + /// Gets or sets the size of the View's content. If the value is Size.Empty the size of the content is + /// the same as the size of the , and Viewport.Location will always be 0, 0. + /// If a positive size is provided, describes the portion of the content currently visible + /// to the view. This enables virtual scrolling. + /// + public Size ContentSize + { + get => _contentSize == Size.Empty ? Viewport.Size : _contentSize; + set => _contentSize = value; + } + + /// + /// Converts a content-relative location to a screen-relative location. + /// + /// + /// The screen-relative location. + public Point ContentToScreen (in Point location) + { + // Translate to Viewport + Point viewportOffset = GetViewportOffset (); + Point contentRelativeToViewport = location; + contentRelativeToViewport.Offset (-Viewport.X, -Viewport.Y); + + // Translate to Frame (our SuperView's Viewport-relative coordinates) + Rectangle screen = ViewportToScreen (new (contentRelativeToViewport, Size.Empty)); + + return screen.Location; + } + + /// Converts a screen-relative coordinate to a Content-relative coordinate. + /// + /// Content-relative means relative to the top-left corner of the view's Content. + /// + /// Column relative to the left side of the Content. + /// Row relative to the top of the Content + /// The coordinate relative to this view's Content. + public Point ScreenToContent (int x, int y) + { + Point viewportOffset = GetViewportOffset (); + Point screen = ScreenToFrame (x, y); + screen.Offset (Viewport.X - viewportOffset.X, Viewport.Y - viewportOffset.Y); + + return screen; + } + + #endregion Content Area + + #region Viewport + + /// + /// Gets or sets the scrolling behavior of the view. + /// + public ScrollSettings ScrollSettings { get; set; } + + private Point _viewportOffset; + + /// + /// Gets or sets the rectangle describing the portion of the View's content that is visible to the user. + /// The viewport Location is relative to the top-left corner of the inner rectangle of the s. + /// If the viewport Size is the sames as the the Location will be 0, 0. + /// Non-zero values for the location indicate the visible area is offset into the View's virtual + /// . + /// + /// + /// The rectangle describing the location and size of the viewport into the View's virtual content, described by + /// . + /// + /// + /// + /// If is the value of Viewport is indeterminate until + /// the view has been initialized ( is true) and has been + /// called. + /// + /// + /// Updates to the Viewport Size updates , and has the same impact as updating the + /// . + /// + /// + /// Altering the Viewport Size will eventually (when the view is next laid out) cause the + /// and methods to be called. + /// + /// + public virtual Rectangle Viewport + { + get + { +#if DEBUG + if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) + { + Debug.WriteLine ( + $"WARNING: Viewport is being accessed before the View has been initialized. This is likely a bug in {this}" + ); + } +#endif // DEBUG + + if (Margin is null || Border is null || Padding is null) + { + // CreateAdornments has not been called yet. + return new (_viewportOffset, Frame.Size); + } + + Thickness totalThickness = GetAdornmentsThickness (); + + return new ( + _viewportOffset, + new ( + Math.Max (0, Frame.Size.Width - totalThickness.Horizontal), + Math.Max (0, Frame.Size.Height - totalThickness.Vertical))); + } + set + { + _viewportOffset = value.Location; + + Thickness totalThickness = GetAdornmentsThickness (); + Size newSize = new (value.Size.Width + totalThickness.Horizontal, + value.Size.Height + totalThickness.Vertical); + if (newSize == Frame.Size) + { + SetNeedsLayout (); + return; + } + + Frame = Frame with + { + Size = newSize + }; + } + } + + /// + /// Converts a -relative location to a screen-relative location. + /// + /// + /// Viewport-relative means relative to the top-left corner of the inner rectangle of the . + /// + public Rectangle ViewportToScreen (in Rectangle location) + { + // Translate bounds to Frame (our SuperView's Viewport-relative coordinates) + Rectangle screen = FrameToScreen (); + Point viewportOffset = GetViewportOffset (); + screen.Offset (viewportOffset.X + location.X, viewportOffset.Y + location.Y); + + return new (screen.Location, location.Size); + } + + /// Converts a screen-relative coordinate to a Viewport-relative coordinate. + /// The coordinate relative to this view's . + /// + /// Viewport-relative means relative to the top-left corner of the inner rectangle of the . + /// + /// Column relative to the left side of the Viewport. + /// Row relative to the top of the Viewport + public Point ScreenToViewport (int x, int y) + { + Point viewportOffset = GetViewportOffset (); + Point screen = ScreenToFrame (x, y); + screen.Offset (-viewportOffset.X, -viewportOffset.Y); + + return screen; + } + + /// + /// Helper to get the X and Y offset of the Viewport from the Frame. This is the sum of the Left and Top properties + /// of , and . + /// + public Point GetViewportOffset () { return Padding is null ? Point.Empty : Padding.Thickness.GetInside (Padding.Frame).Location; } + + /// + /// Scrolls the view vertically by the specified number of rows. + /// + /// + /// + /// + /// + /// + /// if the was changed. + public bool? ScrollVertical (int rows) + { + if (ContentSize == Size.Empty || ContentSize == Viewport.Size) + { + return false; + } + + if (!ScrollSettings.HasFlag (ScrollSettings.NoRestrictVertical) + && (Viewport.Y + rows > ContentSize.Height - Viewport.Height || Viewport.Y + rows < 0)) + { + return false; + } + + Viewport = Viewport with { Y = Viewport.Y + rows }; + + return true; + } + + /// + /// Scrolls the view horizontally by the specified number of columns. + /// + /// + /// + /// + /// + /// + /// if the was changed. + public bool? ScrollHorizontal (int cols) + { + if (ContentSize == Size.Empty || ContentSize == Viewport.Size) + { + return false; + } + + if (!ScrollSettings.HasFlag (ScrollSettings.NoRestrictHorizontal) + && (Viewport.X + cols > ContentSize.Width - Viewport.Width || Viewport.X + cols < 0)) + { + return false; + } + + Viewport = Viewport with { X = Viewport.X + cols }; + + return true; + } + + #endregion Viewport +} diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index b9cacc711..a6c7b0c5e 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -468,7 +468,7 @@ public partial class View { return true; } - + return false; } diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 7292afef5..1eb626dce 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -324,7 +324,7 @@ public partial class View return false; } - sizeRequired = Viewport.Size; + sizeRequired = ContentSize; if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) { @@ -338,9 +338,9 @@ public partial class View // TODO: v2 - This uses frame.Width; it should only use Viewport if (_frame.Width < colWidth - && (Width is null || (Viewport.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth))) + && (Width is null || (ContentSize.Width >= 0 && Width is Dim.DimAbsolute && Width.Anchor (0) >= 0 && Width.Anchor (0) < colWidth))) { - sizeRequired = new (colWidth, Viewport.Height); + sizeRequired = new (colWidth, ContentSize.Height); return true; } @@ -349,7 +349,7 @@ public partial class View default: if (_frame.Height < 1 && (Height is null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0))) { - sizeRequired = new (Viewport.Width, 1); + sizeRequired = new (ContentSize.Width, 1); return true; } diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index 09ffcc3d3..f9fe50c23 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -100,7 +100,7 @@ public class ColorPicker : View if (me.X > Viewport.Width || me.Y > Viewport.Height) { - return true; + return false; } Cursor = new Point (me.X / _boxWidth, me.Y / _boxHeight); diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index 65c47eb47..22161404a 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -194,7 +194,6 @@ public class ScrollView : View { // 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; } @@ -205,7 +204,7 @@ public class ScrollView : View /// Represents the contents of the data shown inside the scrollview /// The size of the content. - public Size ContentSize + public new Size ContentSize { get => _contentSize; set diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index dcca0a57e..b747dc048 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -260,7 +260,7 @@ public partial class Toplevel : View { //Driver.SetAttribute (GetNormalColor ()); // TODO: It's bad practice for views to always clear. Defeats the purpose of clipping etc... - Clear (); + ClearVisibleContent (); LayoutSubviews (); PositionToplevels (); diff --git a/UICatalog/Scenarios/Adornments.cs b/UICatalog/Scenarios/Adornments.cs index 02778702e..b60d09cee 100644 --- a/UICatalog/Scenarios/Adornments.cs +++ b/UICatalog/Scenarios/Adornments.cs @@ -82,8 +82,6 @@ public class Adornments : Scenario //BorderStyle = LineStyle.None, }; - view.X = 36; - view.Y = 0; view.Width = Dim.Percent (60); view.Height = Dim.Percent (80); @@ -452,15 +450,13 @@ public class Adornments : Scenario Add (_diagCheckBox); _viewToEdit.X = Pos.Right (rbBorderStyle); _viewToEdit.Y = 0; - _viewToEdit.Width = Dim.Fill (); - _viewToEdit.Height = Dim.Fill (); Add (_viewToEdit); _viewToEdit.LayoutComplete += (s, e) => { if (ckbTitle.Checked == true) { - //_viewToEdit.Title = _origTitle; + _viewToEdit.Title = _origTitle; } else { diff --git a/UICatalog/Scenarios/VirtualContentScrolling.cs b/UICatalog/Scenarios/VirtualContentScrolling.cs index 98bd53eb1..096dcc6bb 100644 --- a/UICatalog/Scenarios/VirtualContentScrolling.cs +++ b/UICatalog/Scenarios/VirtualContentScrolling.cs @@ -12,18 +12,27 @@ public class VirtualScrolling : Scenario { private ViewDiagnosticFlags _diagnosticFlags; - public class VirtualDemoView : Window + public class VirtualDemoView : View { public VirtualDemoView () { Text = "Virtual Demo View Text. This is long text.\nThe second line.\n3\n4\n5th line."; - Arrangement = ViewArrangement.Fixed; - ContentSize = new Size (100, 50); + CanFocus = true; + Arrangement = ViewArrangement.Movable; + ColorScheme = Colors.ColorSchemes ["Toplevel"]; + BorderStyle = LineStyle.Rounded; + + // TODO: Add a way to set the scroll settings in the Scenario + ContentSize = new Size (100, 60); + //ScrollSettings = ScrollSettings.NoRestrict; // Things this view knows how to do AddCommand (Command.ScrollDown, () => ScrollVertical (1)); AddCommand (Command.ScrollUp, () => ScrollVertical (-1)); + AddCommand (Command.ScrollRight, () => ScrollHorizontal (1)); + AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1)); + //AddCommand (Command.PageUp, () => PageUp ()); //AddCommand (Command.PageDown, () => PageDown ()); //AddCommand (Command.TopHome, () => Home ()); @@ -32,51 +41,66 @@ public class VirtualScrolling : Scenario // Default keybindings for all ListViews 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.PageDown, Command.PageDown); //KeyBindings.Add (Key.Home, Command.TopHome); //KeyBindings.Add (Key.End, Command.BottomEnd); + Border.Add (new Label () { X = 23 }); LayoutComplete += VirtualDemoView_LayoutComplete; + + MouseEvent += VirtualDemoView_MouseEvent; + } + + private void VirtualDemoView_MouseEvent (object sender, MouseEventEventArgs e) + { + if (e.MouseEvent.Flags == MouseFlags.WheeledDown) + { + ScrollVertical (1); + return; + } + if (e.MouseEvent.Flags == MouseFlags.WheeledUp) + { + ScrollVertical (-1); + + return; + } + + if (e.MouseEvent.Flags == MouseFlags.WheeledRight) + { + ScrollHorizontal (1); + return; + } + if (e.MouseEvent.Flags == MouseFlags.WheeledLeft) + { + ScrollHorizontal (-1); + + return; + } + } private void VirtualDemoView_LayoutComplete (object sender, LayoutEventArgs e) { - Title = Viewport.ToString (); + var status = Border.Subviews.OfType