diff --git a/Terminal.Gui/Drawing/Region.cs b/Terminal.Gui/Drawing/Region.cs index c3ef1d61b2..abd06dcbae 100644 --- a/Terminal.Gui/Drawing/Region.cs +++ b/Terminal.Gui/Drawing/Region.cs @@ -1,21 +1,25 @@ -/// +#nullable enable + +namespace Terminal.Gui; + +/// /// Represents a region composed of one or more rectangles, providing methods for union, intersection, exclusion, and /// complement operations. /// public class Region : IDisposable { - private List _rectangles; + private List? _rectangles; /// /// Initializes a new instance of the class. /// - public Region () { _rectangles = new (); } + public Region () { _rectangles = []; } /// /// Initializes a new instance of the class with the specified rectangle. /// /// The initial rectangle for the region. - public Region (Rectangle rectangle) { _rectangles = new () { rectangle }; } + public Region (Rectangle rectangle) { _rectangles = [rectangle]; } /// /// Adds the specified rectangle to the region. @@ -23,7 +27,7 @@ public class Region : IDisposable /// The rectangle to add to the region. public void Union (Rectangle rectangle) { - _rectangles.Add (rectangle); + _rectangles!.Add (rectangle); _rectangles = MergeRectangles (_rectangles); } @@ -31,10 +35,13 @@ public void Union (Rectangle rectangle) /// Adds the specified region to this region. /// /// The region to add to this region. - public void Union (Region region) + public void Union (Region? region) { - _rectangles.AddRange (region._rectangles); - _rectangles = MergeRectangles (_rectangles); + if (region is { }) + { + _rectangles!.AddRange (region._rectangles!); + _rectangles = MergeRectangles (_rectangles); + } } /// @@ -43,20 +50,23 @@ public void Union (Region region) /// The rectangle to intersect with the region. public void Intersect (Rectangle rectangle) { - _rectangles = _rectangles.Select (r => Rectangle.Intersect (r, rectangle)).Where (r => !r.IsEmpty).ToList (); + _rectangles = _rectangles!.Select (r => Rectangle.Intersect (r, rectangle)).Where (r => !r.IsEmpty).ToList (); } /// /// Updates the region to be the intersection of itself with the specified region. /// /// The region to intersect with this region. - public void Intersect (Region region) + public void Intersect (Region? region) { - List intersections = new List (); + List intersections = []; + + // Null is same as empty region + region ??= new (); - foreach (Rectangle rect1 in _rectangles) + foreach (Rectangle rect1 in _rectangles!) { - foreach (Rectangle rect2 in region._rectangles) + foreach (Rectangle rect2 in region!._rectangles!) { Rectangle intersected = Rectangle.Intersect (rect1, rect2); @@ -74,17 +84,20 @@ public void Intersect (Region region) /// Removes the specified rectangle from the region. /// /// The rectangle to exclude from the region. - public void Exclude (Rectangle rectangle) { _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rectangle)).ToList (); } + public void Exclude (Rectangle rectangle) { _rectangles = _rectangles!.SelectMany (r => SubtractRectangle (r, rectangle)).ToList (); } /// /// Removes the portion of the specified region from this region. /// /// The region to exclude from this region. - public void Exclude (Region region) + public void Exclude (Region? region) { - foreach (Rectangle rect in region._rectangles) + // Null is same as empty region + region ??= new (); + + foreach (Rectangle rect in region._rectangles!) { - _rectangles = _rectangles.SelectMany (r => SubtractRectangle (r, rect)).ToList (); + _rectangles = _rectangles!.SelectMany (r => SubtractRectangle (r, rect)).ToList (); } } @@ -94,14 +107,14 @@ public void Exclude (Region region) /// The bounding rectangle to use for complementing the region. public void Complement (Rectangle bounds) { - if (bounds.IsEmpty || _rectangles.Count == 0) + if (bounds.IsEmpty || _rectangles!.Count == 0) { - _rectangles.Clear (); + _rectangles!.Clear (); return; } - List complementRectangles = new List { bounds }; + List complementRectangles = [bounds]; foreach (Rectangle rect in _rectangles) { @@ -118,7 +131,7 @@ public void Complement (Rectangle bounds) public Region Clone () { var clone = new Region (); - clone._rectangles = new (_rectangles); + clone._rectangles = [.. _rectangles!]; return clone; } @@ -129,7 +142,7 @@ public Region Clone () /// A that bounds the region. public Rectangle GetBounds () { - if (_rectangles.Count == 0) + if (_rectangles!.Count == 0) { return Rectangle.Empty; } @@ -146,7 +159,7 @@ public Rectangle GetBounds () /// Determines whether the region is empty. /// /// true if the region is empty; otherwise, false. - public bool IsEmpty () { return !_rectangles.Any (); } + public bool IsEmpty () { return !_rectangles!.Any (); } /// /// Determines whether the specified point is contained within the region. @@ -154,20 +167,20 @@ public Rectangle GetBounds () /// The x-coordinate of the point. /// The y-coordinate of the point. /// true if the point is contained within the region; otherwise, false. - public bool Contains (int x, int y) { return _rectangles.Any (r => r.Contains (x, y)); } + public bool Contains (int x, int y) { return _rectangles!.Any (r => r.Contains (x, y)); } /// /// Determines whether the specified rectangle is contained within the region. /// /// The rectangle to check for containment. /// true if the rectangle is contained within the region; otherwise, false. - public bool Contains (Rectangle rectangle) { return _rectangles.Any (r => r.Contains (rectangle)); } + public bool Contains (Rectangle rectangle) { return _rectangles!.Any (r => r.Contains (rectangle)); } /// /// Returns an array of rectangles that represent the region. /// /// An array of objects that make up the region. - public Rectangle [] GetRegionScans () { return _rectangles.ToArray (); } + public Rectangle [] GetRegionScans () { return _rectangles!.ToArray (); } /// /// Offsets all rectangles in the region by the specified amounts. @@ -176,10 +189,10 @@ public Rectangle GetBounds () /// The amount to offset along the y-axis. public void Offset (int offsetX, int offsetY) { - for (int i = 0; i < _rectangles.Count; i++) + for (var i = 0; i < _rectangles!.Count; i++) { - var rect = _rectangles [i]; - _rectangles [i] = new Rectangle (rect.Left + offsetX, rect.Top + offsetY, rect.Width, rect.Height); + Rectangle rect = _rectangles [i]; + _rectangles [i] = new (rect.Left + offsetX, rect.Top + offsetY, rect.Width, rect.Height); } } @@ -188,11 +201,11 @@ public void Offset (int offsetX, int offsetY) /// /// The list of rectangles to merge. /// A list of merged rectangles. - private List MergeRectangles (List rectangles) + private static List MergeRectangles (List rectangles) { // Simplified merging logic: this does not handle all edge cases for merging overlapping rectangles. // For a full implementation, a plane sweep algorithm or similar would be needed. - List merged = new List (rectangles); + List merged = [.. rectangles]; bool mergedAny; do @@ -230,7 +243,7 @@ private List MergeRectangles (List rectangles) /// The original rectangle. /// The rectangle to subtract from the original. /// An enumerable collection of resulting rectangles after subtraction. - private IEnumerable SubtractRectangle (Rectangle original, Rectangle subtract) + private static IEnumerable SubtractRectangle (Rectangle original, Rectangle subtract) { if (!original.IntersectsWith (subtract)) { @@ -279,5 +292,5 @@ private IEnumerable SubtractRectangle (Rectangle original, Rectangle /// /// Releases all resources used by the . /// - public void Dispose () { _rectangles.Clear (); } + public void Dispose () { _rectangles!.Clear (); } } diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 6aa2088e5a..ed73b3913c 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -235,6 +235,19 @@ public Rectangle GetInside (Rectangle rect) return new (x, y, width, height); } + /// + /// Returns a region describing the thickness. + /// + /// The source rectangle + /// + public Region AsRegion (Rectangle rect) + { + Region region = new Region (rect); + region.Exclude (GetInside (rect)); + + return region; + } + /// /// 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. diff --git a/Terminal.Gui/View/View.Drawing.Clipping.cs b/Terminal.Gui/View/View.Drawing.Clipping.cs index 30f8b703c6..509854f99a 100644 --- a/Terminal.Gui/View/View.Drawing.Clipping.cs +++ b/Terminal.Gui/View/View.Drawing.Clipping.cs @@ -86,7 +86,7 @@ public static void SetClip (Region? region) /// /// The current Clip, which can be then re-applied /// - internal Region? ClipFrame () + internal Region? AddFrameToClip () { if (Driver is null) { @@ -133,7 +133,7 @@ public static void SetClip (Region? region) /// /// The current Clip, which can be then re-applied /// - public Region? ClipViewport () + public Region? AddViewportToClip () { if (Driver is null) { diff --git a/Terminal.Gui/View/View.Drawing.Primitives.cs b/Terminal.Gui/View/View.Drawing.Primitives.cs index a7f1c8522d..153f12e644 100644 --- a/Terminal.Gui/View/View.Drawing.Primitives.cs +++ b/Terminal.Gui/View/View.Drawing.Primitives.cs @@ -137,7 +137,7 @@ public void FillRect (Rectangle rect, Color? color = null) return; } - Region prevClip = ClipViewport (); + Region prevClip = AddViewportToClip (); Rectangle toClear = ViewportToScreen (rect); Attribute prev = SetAttribute (new (color ?? GetNormalColor ().Background)); Driver.FillRect (toClear); @@ -155,7 +155,7 @@ public void FillRect (Rectangle rect, Rune rune) return; } - Region prevClip = ClipViewport (); + Region prevClip = AddViewportToClip (); Rectangle toClear = ViewportToScreen (rect); Driver.FillRect (toClear, rune); SetClip (prevClip); diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs index adea35c36a..b5e2486a69 100644 --- a/Terminal.Gui/View/View.Drawing.cs +++ b/Terminal.Gui/View/View.Drawing.cs @@ -1,5 +1,6 @@ #nullable enable using System.ComponentModel; +using Microsoft.CodeAnalysis.CSharp.Syntax; namespace Terminal.Gui; @@ -47,27 +48,38 @@ public void Draw () return; } - Region? saved = GetClip (); + Region? originalClip = GetClip (); + + Region? contentClip = null; // TODO: This can be further optimized by checking NeedsDraw below and only clearing, drawing text, drawing content, etc. if it is true. - if (NeedsDraw || SubViewNeedsDraw) + if (NeedsDraw || SubViewNeedsDraw || ViewportSettings.HasFlag(ViewportSettings.Transparent)) { // Draw the Border and Padding. - // We clip to the frame to prevent drawing outside the frame. - saved = ClipFrame (); - + if (this is Adornment) + { + AddFrameToClip (); + } + else + { + // Set the clip to be just the thicknesses of the adornments + // TODO: Put this union logic in a method on View? + Region? clipAdornments = Margin!.Thickness.AsRegion (Margin!.FrameToScreen ()); + clipAdornments?.Union (Border!.Thickness.AsRegion (Border!.FrameToScreen ())); + clipAdornments?.Union (Padding!.Thickness.AsRegion (Padding!.FrameToScreen ())); + clipAdornments?.Intersect (originalClip); + SetClip (clipAdornments); + } DoDrawBorderAndPadding (); - SetClip (saved); + SetClip (originalClip); - // Draw the content within the Viewport + // Clear the Viewport // By default, we clip to the viewport preventing drawing outside the viewport // We also clip to the content, but if a developer wants to draw outside the viewport, they can do // so via settings. SetClip honors the ViewportSettings.DisableVisibleContentClipping flag. // Get our Viewport in screen coordinates + originalClip = AddViewportToClip (); - saved = ClipViewport (); - - // Clear the viewport // TODO: Simplify/optimize SetAttribute system. DoSetAttribute (); DoClearViewport (); @@ -87,11 +99,28 @@ public void Draw () DoSetAttribute (); DoDrawContent (); + // TODO: This flag may not be needed. Just returning true from OnClearViewport may be sufficient. + if (ViewportSettings.HasFlag (ViewportSettings.Transparent) && _subviews is { Count: > 0 }) + { + contentClip = new Region (); + + contentClip.Union(ViewportToScreen (new Rectangle(Viewport.Location, TextFormatter.FormatAndGetSize()))); + // TODO: Move this into DrawSubviews somehow + foreach (View view in _subviews?.Where (view => view.Visible).Reverse ()) + { + contentClip.Union (view.FrameToScreen ()); + } + } + else + { + contentClip = new (ViewportToScreen (new Rectangle (Point.Empty, Viewport.Size))); + } + // Restore the clip before rendering the line canvas and adornment subviews // because they may draw outside the viewport. - SetClip (saved); + SetClip (originalClip); - saved = ClipFrame (); + originalClip = AddFrameToClip (); // Draw the line canvas DoRenderLineCanvas (); @@ -113,9 +142,6 @@ public void Draw () // We're done drawing DoDrawComplete (); - // QUESTION: Should this go before DoDrawComplete? What is more correct? - SetClip (saved); - // Exclude this view (not including Margin) from the Clip if (this is not Adornment) { @@ -126,7 +152,24 @@ public void Draw () borderFrame = Border.FrameToScreen (); } - ExcludeFromClip (borderFrame); + if (ViewportSettings.HasFlag (ViewportSettings.Transparent) && contentClip is { }) + { + Region? saved = originalClip!.Clone (); + + saved.Exclude (Border!.Thickness.AsRegion (Border!.FrameToScreen ())); + saved.Exclude (Padding!.Thickness.AsRegion (Padding!.FrameToScreen ())); + saved.Exclude (contentClip); + SetClip (saved); + } + else + { + SetClip (originalClip); + ExcludeFromClip (borderFrame); + } + } + else + { + SetClip (originalClip); } } @@ -144,10 +187,10 @@ private void DoDrawBorderAndPaddingSubViews () subview.SetNeedsDraw (); } - LineCanvas.Exclude (new (subview.FrameToScreen())); + LineCanvas.Exclude (new (subview.FrameToScreen ())); } - Region? saved = Border?.ClipFrame (); + Region? saved = Border?.AddFrameToClip (); Border?.DoDrawSubviews (); SetClip (saved); } @@ -159,7 +202,7 @@ private void DoDrawBorderAndPaddingSubViews () subview.SetNeedsDraw (); } - Region? saved = Padding?.ClipFrame (); + Region? saved = Padding?.AddFrameToClip (); Padding?.DoDrawSubviews (); SetClip (saved); } @@ -170,6 +213,7 @@ private void DoDrawBorderAndPadding () if (Margin?.NeedsLayout == true) { Margin.NeedsLayout = false; + // BUGBUG: This should not use ClearFrame as that clears the insides too Margin?.ClearFrame (); Margin?.Parent?.SetSubViewNeedsDraw (); } @@ -293,6 +337,11 @@ public void SetNormalAttribute () private void DoClearViewport () { + if (ViewportSettings.HasFlag (ViewportSettings.Transparent)) + { + return; + } + if (OnClearingViewport ()) { return; diff --git a/Terminal.Gui/View/ViewportSettings.cs b/Terminal.Gui/View/ViewportSettings.cs index db2c23f4fe..c687da7513 100644 --- a/Terminal.Gui/View/ViewportSettings.cs +++ b/Terminal.Gui/View/ViewportSettings.cs @@ -13,7 +13,7 @@ public enum ViewportSettings /// /// No settings. /// - None = 0, + None = 0b_0000, /// /// If set, .X can be set to negative values enabling scrolling beyond the left of @@ -23,7 +23,7 @@ public enum ViewportSettings /// When not set, .X is constrained to positive values. /// /// - AllowNegativeX = 1, + AllowNegativeX = 0b_0001, /// /// If set, .Y can be set to negative values enabling scrolling beyond the top of the @@ -32,7 +32,7 @@ public enum ViewportSettings /// When not set, .Y is constrained to positive values. /// /// - AllowNegativeY = 2, + AllowNegativeY = 0b_0010, /// /// If set, .Size can be set to negative coordinates enabling scrolling beyond the @@ -58,7 +58,7 @@ public enum ViewportSettings /// The practical effect of this is that the last column of the content will always be visible. /// /// - AllowXGreaterThanContentWidth = 4, + AllowXGreaterThanContentWidth = 0b_0100, /// /// If set, .Y can be set values greater than @@ -74,7 +74,7 @@ public enum ViewportSettings /// The practical effect of this is that the last row of the content will always be visible. /// /// - AllowYGreaterThanContentHeight = 8, + AllowYGreaterThanContentHeight = 0b_1000, /// /// If set, .Location can be set values greater than @@ -102,7 +102,7 @@ public enum ViewportSettings /// This can be useful in infinite scrolling scenarios. /// /// - AllowNegativeXWhenWidthGreaterThanContentWidth = 16, + AllowNegativeXWhenWidthGreaterThanContentWidth = 0b_0001_0000, /// /// If set and .Height is greater than @@ -117,7 +117,7 @@ public enum ViewportSettings /// This can be useful in infinite scrolling scenarios. /// /// - AllowNegativeYWhenHeightGreaterThanContentHeight = 32, + AllowNegativeYWhenHeightGreaterThanContentHeight = 0b_0010_0000, /// /// The combination of and @@ -129,7 +129,7 @@ public enum ViewportSettings /// By default, clipping is applied to the . Setting this flag will cause clipping to be /// applied to the visible content area. /// - ClipContentOnly = 64, + ClipContentOnly = 0b_0100_0000, /// /// If set will clear only the portion of the content @@ -138,5 +138,11 @@ public enum ViewportSettings /// must be set for this setting to work (clipping beyond the visible area must be /// disabled). /// - ClearContentOnly = 128 + ClearContentOnly = 0b_1000_0000, + + /// + /// If set, any will not be cleared when the View is drawn and the clip region + /// will be set to clip the View's and . + /// + Transparent = 0b_0001_0000_0000, } diff --git a/Terminal.Gui/Views/CharMap/CharMap.cs b/Terminal.Gui/Views/CharMap/CharMap.cs index b49c8c4e6f..a42d39c69e 100644 --- a/Terminal.Gui/Views/CharMap/CharMap.cs +++ b/Terminal.Gui/Views/CharMap/CharMap.cs @@ -97,8 +97,8 @@ public CharMap () }; // Set up the vertical scrollbar. Turn off AutoShow since it's always visible. - VerticalScrollBar.AutoShow = false; - VerticalScrollBar.Visible = true; // Force always visible + VerticalScrollBar.AutoShow = true; + VerticalScrollBar.Visible = false; // Force always visible VerticalScrollBar.X = Pos.AnchorEnd (); VerticalScrollBar.Y = HEADER_HEIGHT; // Header } diff --git a/UICatalog/Scenarios/AllViewsTester.cs b/UICatalog/Scenarios/AllViewsTester.cs index d982b2b899..c6f9136796 100644 --- a/UICatalog/Scenarios/AllViewsTester.cs +++ b/UICatalog/Scenarios/AllViewsTester.cs @@ -18,10 +18,9 @@ public class AllViewsTester : Scenario private Dictionary? _viewClasses; private ListView? _classListView; private AdornmentsEditor? _adornmentsEditor; - private ArrangementEditor? _arrangementEditor; - private LayoutEditor? _layoutEditor; + private ViewportSettingsEditor? _viewportSettingsEditor; private FrameView? _settingsPane; private RadioGroup? _orientation; private string _demoText = "This, that, and the other thing."; @@ -133,13 +132,27 @@ public override void Main () AutoSelectAdornments = false, SuperViewRendersLineCanvas = true }; - _layoutEditor.Border!.Thickness = new (1); + _layoutEditor.Border!.Thickness = new (1, 1, 1, 0); + + _viewportSettingsEditor = new () + { + Title = "ViewportSettings [_5]", + X = Pos.Right (_arrangementEditor) - 1, + Y = Pos.Bottom (_layoutEditor) - Pos.Func (() => _layoutEditor.Frame.Height == 1 ? 0 : 1), + Width = Dim.Width (_layoutEditor), + Height = Dim.Auto (), + CanFocus = true, + AutoSelectViewToEdit = false, + AutoSelectAdornments = false, + SuperViewRendersLineCanvas = true + }; + _viewportSettingsEditor.Border!.Thickness = new (1, 1, 1, 1); _settingsPane = new () { - Title = "Settings [_5]", + Title = "Misc Settings [_6]", X = Pos.Right (_adornmentsEditor) - 1, - Y = Pos.Bottom (_layoutEditor) - Pos.Func (() => _layoutEditor.Frame.Height == 1 ? 0 : 1), + Y = Pos.Bottom (_viewportSettingsEditor) - Pos.Func (() => _viewportSettingsEditor.Frame.Height == 1 ? 0 : 1), Width = Dim.Width (_layoutEditor), Height = Dim.Auto (), CanFocus = true, @@ -237,7 +250,7 @@ public override void Main () _hostPane.Padding.Diagnostics = ViewDiagnosticFlags.Ruler; _hostPane.Padding.ColorScheme = app.ColorScheme; - app.Add (_classListView, _adornmentsEditor, _arrangementEditor, _layoutEditor, _settingsPane, _eventLog, _hostPane); + app.Add (_classListView, _adornmentsEditor, _arrangementEditor, _layoutEditor, _viewportSettingsEditor, _settingsPane, _eventLog, _hostPane); app.Initialized += App_Initialized; @@ -306,6 +319,7 @@ private void CreateCurrentView (Type type) _hostPane!.Add (_curView); _layoutEditor!.ViewToEdit = _curView; + _viewportSettingsEditor!.ViewToEdit = _curView; _arrangementEditor!.ViewToEdit = _curView; _curView.SetNeedsLayout (); } @@ -318,6 +332,7 @@ private void DisposeCurrentView () _curView.SubviewsLaidOut -= CurrentView_LayoutComplete; _hostPane!.Remove (_curView); _layoutEditor!.ViewToEdit = null; + _viewportSettingsEditor!.ViewToEdit = null; _arrangementEditor!.ViewToEdit = null; _curView.Dispose (); diff --git a/UICatalog/Scenarios/Arrangement.cs b/UICatalog/Scenarios/Arrangement.cs index 33ac9ee84c..b8d06b1827 100644 --- a/UICatalog/Scenarios/Arrangement.cs +++ b/UICatalog/Scenarios/Arrangement.cs @@ -185,6 +185,29 @@ public override void Main () }; testFrame.Add (datePicker); + TransparentView transparentView = new () + { + Id = "transparentView", + X = 5, + Y = 11, + Width = 35, + Height = 15 + }; + + transparentView.Add ( + new TransparentView () + { + Title = "Transparent SubView", + Text = "Transparent SubView", + Id = "transparentSubView", + X = 10, + Y = 10, + Width = 10, + Height = 5 + + }); + testFrame.Add (transparentView); + adornmentsEditor.AutoSelectSuperView = testFrame; arrangementEditor.AutoSelectSuperView = testFrame; @@ -299,3 +322,26 @@ public override List GetDemoKeyStrokes () return keys; } } + +public class TransparentView : FrameView +{ + public TransparentView () + { + Title = "Transparent"; + base.Text = "Text"; + Arrangement = ViewArrangement.Overlapped | ViewArrangement.Resizable | ViewArrangement.Movable; + ViewportSettings |= Terminal.Gui.ViewportSettings.Transparent; + base.Add ( + new Button () + { + Title = "_Hi", + X = Pos.Center (), + Y = Pos.Center (), + ShadowStyle = ShadowStyle.None, + ColorScheme = Colors.ColorSchemes ["Toplevel"], + }); + } + + /// + protected override bool OnMouseEvent (MouseEventArgs mouseEvent) { return false; } +} diff --git a/UICatalog/Scenarios/Editors/ViewportSettingsEditor.cs b/UICatalog/Scenarios/Editors/ViewportSettingsEditor.cs new file mode 100644 index 0000000000..011ca212fc --- /dev/null +++ b/UICatalog/Scenarios/Editors/ViewportSettingsEditor.cs @@ -0,0 +1,371 @@ +#nullable enable +using System; +using Terminal.Gui; + +namespace UICatalog.Scenarios; + +/// +/// Provides an editor UI for View.ViewportSettings. +/// +public sealed class ViewportSettingsEditor : EditorBase +{ + public ViewportSettingsEditor () + { + Title = "ViewportSettingsEditor"; + TabStop = TabBehavior.TabGroup; + + Initialized += ViewportSettingsEditor_Initialized; + } + + protected override void OnUpdateSettings () + { + foreach (View subview in Subviews) + { + subview.Enabled = ViewToEdit is not Adornment; + } + + if (ViewToEdit is null) + { } + } + + protected override void OnViewToEditChanged () + { + if (ViewToEdit is { } and not Adornment) + { + //ViewToEdit.VerticalScrollBar.AutoShow = true; + //ViewToEdit.HorizontalScrollBar.AutoShow = true; + + _contentSizeWidth!.Value = ViewToEdit.GetContentSize ().Width; + _contentSizeHeight!.Value = ViewToEdit.GetContentSize ().Height; + + _cbAllowNegativeX!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowNegativeX) + ? CheckState.Checked + : CheckState.UnChecked; + + _cbAllowNegativeY!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowNegativeY) + ? CheckState.Checked + : CheckState.UnChecked; + + _cbAllowXGreaterThanContentWidth!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth) + ? CheckState.Checked + : CheckState.UnChecked; + + _cbAllowYGreaterThanContentHeight!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight) + ? CheckState.Checked + : CheckState.UnChecked; + + _cbClearContentOnly!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.ClearContentOnly) + ? CheckState.Checked + : CheckState.UnChecked; + + _cbClipContentOnly!.CheckedState = ViewToEdit.ViewportSettings.HasFlag (Terminal.Gui.ViewportSettings.ClipContentOnly) + ? CheckState.Checked + : CheckState.UnChecked; + + _cbTransparent!.CheckedState = ViewToEdit.ViewportSettings.HasFlag(Terminal.Gui.ViewportSettings.Transparent) + ? CheckState.Checked + : CheckState.UnChecked; + + _cbVerticalScrollBar!.CheckedState = ViewToEdit.VerticalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked; + _cbAutoShowVerticalScrollBar!.CheckedState = ViewToEdit.VerticalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked; + _cbHorizontalScrollBar!.CheckedState = ViewToEdit.HorizontalScrollBar.Visible ? CheckState.Checked : CheckState.UnChecked; + _cbAutoShowHorizontalScrollBar!.CheckedState = ViewToEdit.HorizontalScrollBar.AutoShow ? CheckState.Checked : CheckState.UnChecked; + } + } + + private CheckBox? _cbAllowNegativeX; + private CheckBox? _cbAllowNegativeY; + private CheckBox? _cbAllowXGreaterThanContentWidth; + private CheckBox? _cbAllowYGreaterThanContentHeight; + private NumericUpDown? _contentSizeWidth; + private NumericUpDown? _contentSizeHeight; + private CheckBox? _cbClearContentOnly; + private CheckBox? _cbClipContentOnly; + private CheckBox? _cbTransparent; + private CheckBox? _cbVerticalScrollBar; + private CheckBox? _cbAutoShowVerticalScrollBar; + private CheckBox? _cbHorizontalScrollBar; + private CheckBox? _cbAutoShowHorizontalScrollBar; + + private void ViewportSettingsEditor_Initialized (object? s, EventArgs e) + { + _cbAllowNegativeX = new() + { + Title = "Allow X < 0", + CanFocus = true + }; + + Add (_cbAllowNegativeX); + + _cbAllowNegativeY = new() + { + Title = "Allow Y < 0", + CanFocus = true + }; + + Add (_cbAllowNegativeY); + + _cbAllowXGreaterThanContentWidth = new() + { + Title = "Allow X > Content Width", + Y = Pos.Bottom (_cbAllowNegativeX), + CanFocus = true + }; + + _cbAllowNegativeX.CheckedStateChanging += AllowNegativeXToggle; + _cbAllowXGreaterThanContentWidth.CheckedStateChanging += AllowXGreaterThanContentWidthToggle; + + Add (_cbAllowXGreaterThanContentWidth); + + void AllowNegativeXToggle (object? sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowNegativeX; + } + else + { + ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowNegativeX; + } + } + + void AllowXGreaterThanContentWidthToggle (object? sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth; + } + else + { + ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowXGreaterThanContentWidth; + } + } + + _cbAllowYGreaterThanContentHeight = new() + { + Title = "Allow Y > Content Height", + X = Pos.Right (_cbAllowXGreaterThanContentWidth) + 1, + Y = Pos.Bottom (_cbAllowNegativeX), + CanFocus = true + }; + + _cbAllowNegativeY.CheckedStateChanging += AllowNegativeYToggle; + + _cbAllowYGreaterThanContentHeight.CheckedStateChanging += AllowYGreaterThanContentHeightToggle; + + Add (_cbAllowYGreaterThanContentHeight); + + void AllowNegativeYToggle (object? sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowNegativeY; + } + else + { + ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowNegativeY; + } + } + + void AllowYGreaterThanContentHeightToggle (object? sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight; + } + else + { + ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.AllowYGreaterThanContentHeight; + } + } + + _cbAllowNegativeY.X = Pos.Left (_cbAllowYGreaterThanContentHeight); + + var labelContentSize = new Label + { + Title = "ContentSize:", + Y = Pos.Bottom (_cbAllowYGreaterThanContentHeight) + }; + + _contentSizeWidth = new() + { + X = Pos.Right (labelContentSize) + 1, + Y = Pos.Top (labelContentSize), + CanFocus = true + }; + _contentSizeWidth.ValueChanging += ContentSizeWidthValueChanged; + + void ContentSizeWidthValueChanged (object? sender, CancelEventArgs e) + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + // BUGBUG: set_ContentSize is supposed to be `protected`. + ViewToEdit!.SetContentSize (ViewToEdit.GetContentSize () with { Width = e.NewValue }); + } + + var labelComma = new Label + { + Title = ",", + X = Pos.Right (_contentSizeWidth), + Y = Pos.Top (labelContentSize) + }; + + _contentSizeHeight = new() + { + X = Pos.Right (labelComma) + 1, + Y = Pos.Top (labelContentSize), + CanFocus = true + }; + _contentSizeHeight.ValueChanging += ContentSizeHeightValueChanged; + + void ContentSizeHeightValueChanged (object? sender, CancelEventArgs e) + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + // BUGBUG: set_ContentSize is supposed to be `protected`. + ViewToEdit?.SetContentSize (ViewToEdit.GetContentSize () with { Height = e.NewValue }); + } + + _cbClearContentOnly = new() + { + Title = "ClearContentOnly", + X = 0, + Y = Pos.Bottom (labelContentSize), + CanFocus = true + }; + _cbClearContentOnly.CheckedStateChanging += ClearContentOnlyToggle; + + void ClearContentOnlyToggle (object? sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.ClearContentOnly; + } + else + { + ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.ClearContentOnly; + } + } + + _cbClipContentOnly = new() + { + Title = "ClipContentOnly", + X = Pos.Right (_cbClearContentOnly) + 1, + Y = Pos.Bottom (labelContentSize), + CanFocus = true + }; + _cbClipContentOnly.CheckedStateChanging += ClipContentOnlyToggle; + + void ClipContentOnlyToggle (object? sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.ClipContentOnly; + } + else + { + ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.ClipContentOnly; + } + } + + _cbTransparent = new () + { + Title = "Transparent", + X = Pos.Right (_cbClipContentOnly) + 1, + Y = Pos.Bottom (labelContentSize), + CanFocus = true + }; + _cbTransparent.CheckedStateChanging += TransparentToggle; + + void TransparentToggle (object? sender, CancelEventArgs e) + { + if (e.NewValue == CheckState.Checked) + { + ViewToEdit!.ViewportSettings |= Terminal.Gui.ViewportSettings.Transparent; + } + else + { + ViewToEdit!.ViewportSettings &= ~Terminal.Gui.ViewportSettings.Transparent; + } + } + + _cbVerticalScrollBar = new() + { + Title = "VerticalScrollBar", + X = 0, + Y = Pos.Bottom (_cbClearContentOnly), + CanFocus = false + }; + _cbVerticalScrollBar.CheckedStateChanging += VerticalScrollBarToggle; + + void VerticalScrollBarToggle (object? sender, CancelEventArgs e) + { + ViewToEdit!.VerticalScrollBar.Visible = e.NewValue == CheckState.Checked; + } + + _cbAutoShowVerticalScrollBar = new() + { + Title = "AutoShow", + X = Pos.Right (_cbVerticalScrollBar) + 1, + Y = Pos.Top (_cbVerticalScrollBar), + CanFocus = false + }; + _cbAutoShowVerticalScrollBar.CheckedStateChanging += AutoShowVerticalScrollBarToggle; + + void AutoShowVerticalScrollBarToggle (object? sender, CancelEventArgs e) + { + ViewToEdit!.VerticalScrollBar.AutoShow = e.NewValue == CheckState.Checked; + } + + _cbHorizontalScrollBar = new() + { + Title = "HorizontalScrollBar", + X = 0, + Y = Pos.Bottom (_cbVerticalScrollBar), + CanFocus = false + }; + _cbHorizontalScrollBar.CheckedStateChanging += HorizontalScrollBarToggle; + + void HorizontalScrollBarToggle (object? sender, CancelEventArgs e) + { + ViewToEdit!.HorizontalScrollBar.Visible = e.NewValue == CheckState.Checked; + } + + _cbAutoShowHorizontalScrollBar = new() + { + Title = "AutoShow ", + X = Pos.Right (_cbHorizontalScrollBar) + 1, + Y = Pos.Top (_cbHorizontalScrollBar), + CanFocus = false + }; + _cbAutoShowHorizontalScrollBar.CheckedStateChanging += AutoShowHorizontalScrollBarToggle; + + void AutoShowHorizontalScrollBarToggle (object? sender, CancelEventArgs e) + { + ViewToEdit!.HorizontalScrollBar.AutoShow = e.NewValue == CheckState.Checked; + } + + Add ( + labelContentSize, + _contentSizeWidth, + labelComma, + _contentSizeHeight, + _cbClearContentOnly, + _cbClipContentOnly, + _cbTransparent, + _cbVerticalScrollBar, + _cbHorizontalScrollBar, + _cbAutoShowVerticalScrollBar, + _cbAutoShowHorizontalScrollBar); + } +} diff --git a/UnitTests/View/Draw/DrawTests.cs b/UnitTests/View/Draw/DrawTests.cs index 90d2ecb520..666e5b8adc 100644 --- a/UnitTests/View/Draw/DrawTests.cs +++ b/UnitTests/View/Draw/DrawTests.cs @@ -968,7 +968,7 @@ public void SetClip_ClipVisibleContentOnly_VisibleContentIsClipped () Assert.Equal (view.Frame, View.GetClip ()!.GetBounds ()); // Act - view.ClipViewport (); + view.AddViewportToClip (); // Assert Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ()); @@ -1002,7 +1002,7 @@ public void SetClip_Default_ClipsToViewport () view.Viewport = view.Viewport with { X = 1, Y = 1 }; // Act - view.ClipViewport (); + view.AddViewportToClip (); // Assert Assert.Equal (expectedClip, View.GetClip ()!.GetBounds ()); diff --git a/UnitTests/View/ViewTests.cs b/UnitTests/View/ViewTests.cs index c169013e99..9a76f8e83e 100644 --- a/UnitTests/View/ViewTests.cs +++ b/UnitTests/View/ViewTests.cs @@ -163,7 +163,7 @@ public void Clear_Viewport_Can_Use_Driver_AddRune_Or_AddStr_Methods () view.DrawingContent += (s, e) => { - Region savedClip = view.ClipViewport (); + Region savedClip = view.AddViewportToClip (); for (var row = 0; row < view.Viewport.Height; row++) { @@ -226,7 +226,7 @@ public void Clear_Can_Use_Driver_AddRune_Or_AddStr_Methods () view.DrawingContent += (s, e) => { - Region savedClip = view.ClipViewport (); + Region savedClip = view.AddViewportToClip (); for (var row = 0; row < view.Viewport.Height; row++) {