diff --git a/README.md b/README.md index 8ace962338..6ffb641f54 100644 --- a/README.md +++ b/README.md @@ -1,6 +1,6 @@ -![Terminal.Gui](https://socialify.git.ci/gui-cs/Terminal.GuiV2Docs/image?description=1&font=Rokkitt&forks=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fgui-cs%2FTerminal.Gui%2Fdevelop%2Fdocfx%2Fimages%2Flogo.png&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Auto) -![.NET Core](https://github.com/gui-cs/Terminal.GuiV2Docs/workflows/.NET%20Core/badge.svg?branch=develop) -![Code scanning - action](https://github.com/gui-cs/Terminal.GuiV2Docs/workflows/Code%20scanning%20-%20action/badge.svg) +![Terminal.Gui](https://socialify.git.ci/gui-cs/Terminal.Gui/image?description=1&font=Rokkitt&forks=1&language=1&logo=https%3A%2F%2Fraw.githubusercontent.com%2Fgui-cs%2FTerminal.Gui%2Fdevelop%2Fdocfx%2Fimages%2Flogo.png&name=1&owner=1&pattern=Circuit%20Board&stargazers=1&theme=Auto) +![.NET Core](https://github.com/gui-cs/Terminal.Gui/workflows/.NET%20Core/badge.svg?branch=develop) +![Code scanning - action](https://github.com/gui-cs/Terminal.Gui/workflows/Code%20scanning%20-%20action/badge.svg) [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui) ![Code Coverage](https://img.shields.io/endpoint?url=https://gist.githubusercontent.com/migueldeicaza/90ef67a684cb71db1817921a970f8d27/raw/code-coverage.json) [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui) @@ -31,8 +31,10 @@ dotnet run ## Documentation -* [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs) +* [Getting Started](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/getting-started.html) +* [What's new in v2](https://gui-cs.github.io/Terminal.GuiV2Docs/docs/newinv2.html) * [API Documentation](https://gui-cs.github.io/Terminal.GuiV2Docs/api/Terminal.Gui.html) +* [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs) ## Showcase & Examples diff --git a/ReactiveExample/ReactiveExample.csproj b/ReactiveExample/ReactiveExample.csproj index e3e43708ea..68974a8b41 100644 --- a/ReactiveExample/ReactiveExample.csproj +++ b/ReactiveExample/ReactiveExample.csproj @@ -12,7 +12,7 @@ - + diff --git a/Terminal.Gui/Application.cs b/Terminal.Gui/Application.cs index 1901bba2ac..5e17af95f5 100644 --- a/Terminal.Gui/Application.cs +++ b/Terminal.Gui/Application.cs @@ -88,7 +88,7 @@ internal static void ResetState () #if DEBUG_IDISPOSABLE // Don't dispose the toplevels. It's up to caller dispose them - Debug.Assert (t.WasDisposed); + //Debug.Assert (t.WasDisposed); #endif } @@ -99,6 +99,7 @@ internal static void ResetState () // Don't dispose the Top. It's up to caller dispose it if (Top is { }) { + Debug.Assert (Top.WasDisposed); // If End wasn't called _cachedRunStateToplevel may be null @@ -132,7 +133,7 @@ internal static void ResetState () // Don't reset ForceDriver; it needs to be set before Init is called. //ForceDriver = string.Empty; - Force16Colors = false; + //Force16Colors = false; _forceFakeConsole = false; // Run State stuff @@ -525,7 +526,10 @@ public static RunState Begin (Toplevel toplevel) MoveCurrent (Current); } - toplevel.SetRelativeLayout (Driver.Bounds); + //if (Toplevel.LayoutStyle == LayoutStyle.Computed) { + toplevel.SetRelativeLayout (Driver.Screen.Size); + + //} // BUGBUG: This call is likely not needed. toplevel.LayoutSubviews (); @@ -638,7 +642,7 @@ public static T Run (Func errorHandler = null, ConsoleDriver public static void Run (Toplevel view, Func errorHandler = null, ConsoleDriver driver = null) { ArgumentNullException.ThrowIfNull (view); - + if (_initialized) { if (Driver is null) @@ -878,7 +882,7 @@ public static void RunIteration (ref RunState state, ref bool firstIteration) } else { - Driver.UpdateCursor (); + //Driver.UpdateCursor (); } if (state.Toplevel != Top && !state.Toplevel.Modal && (Top.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded)) @@ -1307,7 +1311,7 @@ public static bool OnSizeChanging (SizeChangedEventArgs args) foreach (Toplevel t in _topLevels) { - t.SetRelativeLayout (Rectangle.Empty with { Size = args.Size }); + t.SetRelativeLayout (args.Size); t.LayoutSubviews (); t.PositionToplevels (); t.OnSizeChanging (new (args.Size)); @@ -1437,15 +1441,15 @@ private static void OnUnGrabbedMouse (View view) /// /// /// Use this event to receive mouse events in screen coordinates. Use to - /// receive mouse events relative to a 's bounds. + /// receive mouse events relative to a . /// /// The will contain the that contains the mouse coordinates. /// - public static event EventHandler MouseEvent; + public static event EventHandler? MouseEvent; /// Called when a mouse event occurs. Raises the event. /// This method can be used to simulate a mouse event, e.g. in unit tests. - /// The mouse event with coordinates relative to the screen. + /// The mouse event with coordinates relative to the screen. internal static void OnMouseEvent (MouseEvent mouseEvent) { if (IsMouseDisabled) @@ -1453,7 +1457,6 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) return; } - // TODO: In PR #3273, FindDeepestView will return adornments. Update logic below to fix adornment mouse handling var view = View.FindDeepestView (Current, mouseEvent.X, mouseEvent.Y); if (view is { }) @@ -1472,18 +1475,18 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) { // If the mouse is grabbed, send the event to the view that grabbed it. // The coordinates are relative to the Bounds of the view that grabbed the mouse. - Point boundsLoc = MouseGrabView.ScreenToBounds (mouseEvent.X, mouseEvent.Y); + Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.X, mouseEvent.Y); var viewRelativeMouseEvent = new MouseEvent { - X = boundsLoc.X, - Y = boundsLoc.Y, + X = frameLoc.X, + Y = frameLoc.Y, Flags = mouseEvent.Flags, ScreenPosition = new (mouseEvent.X, mouseEvent.Y), View = MouseGrabView }; - if (MouseGrabView.Bounds.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 bounds of the view that grabbed the mouse _mouseEnteredView?.NewMouseLeaveEvent (mouseEvent); @@ -1546,14 +1549,14 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) View = view }; } - else if (view.BoundsToScreen (view.Bounds).Contains (mouseEvent.X, mouseEvent.Y)) + else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.X, mouseEvent.Y)) { - Point boundsPoint = view.ScreenToBounds (mouseEvent.X, mouseEvent.Y); + Point viewportLocation = view.ScreenToViewport (mouseEvent.X, mouseEvent.Y); me = new () { - X = boundsPoint.X, - Y = boundsPoint.Y, + X = viewportLocation.X, + Y = viewportLocation.Y, Flags = mouseEvent.Flags, ScreenPosition = new (mouseEvent.X, mouseEvent.Y), View = view @@ -1586,10 +1589,37 @@ internal static void OnMouseEvent (MouseEvent mouseEvent) //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}"); - if (view.NewMouseEvent (me) == false) + while (view.NewMouseEvent (me) != true) { - // Should we bubble up the event, if it is not handled? - //return; + if (MouseGrabView is { }) + { + break; + } + + if (view is Adornment adornmentView) + { + view = adornmentView.Parent.SuperView; + } + else + { + view = view.SuperView; + } + + if (view is null) + { + break; + } + + Point boundsPoint = view.ScreenToViewport (mouseEvent.X, mouseEvent.Y); + + me = new () + { + X = boundsPoint.X, + Y = boundsPoint.Y, + Flags = mouseEvent.Flags, + ScreenPosition = new (mouseEvent.X, mouseEvent.Y), + View = view + }; } BringOverlappedTopToFront (); diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 1974b6147e..7187d71912 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -18,15 +18,31 @@ public abstract class ConsoleDriver // This is in addition to the dirty flag on each cell. internal bool [] _dirtyLines; - /// Gets the dimensions of the terminal. - public Rectangle Bounds => new (0, 0, Cols, Rows); + // QUESTION: When non-full screen apps are supported, will this represent the app size, or will that be in Application? + /// Gets the location and size of the terminal screen. + public Rectangle Screen => new (0, 0, Cols, Rows); + + private Rectangle _clip; /// /// Gets or sets the clip rectangle that and are subject /// to. /// - /// The rectangle describing the bounds of . - public Rectangle Clip { get; set; } + /// The rectangle describing the of region. + public Rectangle Clip + { + get => _clip; + set + { + if (_clip == value) + { + return; + } + + // Don't ever let Clip be bigger than Screen + _clip = Rectangle.Intersect (Screen, value); + } + } /// Get the operating system clipboard. public IClipboard Clipboard { get; internal set; } @@ -278,16 +294,6 @@ public void AddStr (string str) for (var i = 0; i < runes.Count; i++) { - //if (runes [i].IsCombiningMark()) { - - // // Attempt to normalize - // string combined = runes [i-1] + runes [i].ToString(); - - // // Normalize to Form C (Canonical Composition) - // string normalized = combined.Normalize (NormalizationForm.FormC); - - // runes [i-] - //} AddRune (runes [i]); } } @@ -295,31 +301,27 @@ public void AddStr (string str) /// Clears the of the driver. public void ClearContents () { - // TODO: This method is really "Clear Contents" now and should not be abstract (or virtual) Contents = new Cell [Rows, Cols]; //CONCURRENCY: Unsynchronized access to Clip isn't safe. - Clip = new (0, 0, Cols, Rows); + // TODO: ClearContents should not clear the clip; it should only clear the contents. Move clearing it elsewhere. + Clip = Screen; _dirtyLines = new bool [Rows]; lock (Contents) { - // Can raise an exception while is still resizing. - try + for (var row = 0; row < Rows; row++) { - for (var row = 0; row < Rows; row++) + for (var c = 0; c < Cols; c++) { - for (var c = 0; c < Cols; c++) + Contents [row, c] = new Cell { - Contents [row, c] = new Cell - { - Rune = (Rune)' ', Attribute = new Attribute (Color.White, Color.Black), IsDirty = true - }; - _dirtyLines [row] = true; - } + Rune = (Rune)' ', + Attribute = new Attribute (Color.White, Color.Black), + IsDirty = true + }; + _dirtyLines [row] = true; } } - catch (IndexOutOfRangeException) - { } } } @@ -327,18 +329,28 @@ public void ClearContents () /// upon success public abstract bool EnsureCursorVisibility (); - // TODO: Move FillRect to ./Drawing - /// Fills the specified rectangle with the specified rune. - /// - /// + /// Fills the specified rectangle with the specified rune, using + /// + /// The value of is honored. Any parts of the rectangle not in the clip will not be drawn. + /// + /// The Screen-relative rectangle. + /// The Rune used to fill the rectangle public void FillRect (Rectangle rect, Rune rune = default) { - for (int r = rect.Y; r < rect.Y + rect.Height; r++) + rect = Rectangle.Intersect (rect, Clip); + lock (Contents) { - for (int c = rect.X; c < rect.X + rect.Width; c++) + for (int r = rect.Y; r < rect.Y + rect.Height; r++) { - Application.Driver.Move (c, r); - Application.Driver.AddRune (rune == default (Rune) ? new Rune (' ') : rune); + for (int c = rect.X; c < rect.X + rect.Width; c++) + { + Contents [r, c] = new Cell + { + Rune = (rune != default ? rune : (Rune)' '), + Attribute = CurrentAttribute, IsDirty = true + }; + _dirtyLines [r] = true; + } } } } @@ -372,10 +384,13 @@ public void FillRect (Rectangle rect, Rune rune = default) /// The column. /// The row. /// - /// if the coordinate is outside of the screen bounds or outside of . + /// if the coordinate is outside the screen bounds or outside of . /// otherwise. /// - public bool IsValidLocation (int col, int row) { return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row); } + public bool IsValidLocation (int col, int row) + { + return col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row); + } /// /// Updates and to the specified column and row in . @@ -437,6 +452,7 @@ public virtual void Move (int col, int row) /// Gets whether the supports TrueColor output. public virtual bool SupportsTrueColor => true; + // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. /// /// Gets or sets whether the should use 16 colors instead of the default TrueColors. /// See to change this setting via . @@ -466,6 +482,7 @@ public Attribute CurrentAttribute get => _currentAttribute; set { + // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. Once Attribute.PlatformColor is removed, this can be fixed. if (Application.Driver is { }) { _currentAttribute = new Attribute (value.Foreground, value.Background); diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs index 4a85804f28..22cd0b9872 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs @@ -1315,7 +1315,7 @@ public override void UpdateCursor () { EnsureCursorVisibility (); - if (Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) + if (Col >= 0 && Col < Cols && Row >= 0 && Row <= Rows) { SetCursorPosition (Col, Row); SetWindowPosition (0, Row); diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs index 9f5e8a006f..7b67c31f7a 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs @@ -18,6 +18,7 @@ using System.ComponentModel; using System.Diagnostics; using System.Runtime.InteropServices; +using System.Text; using static Terminal.Gui.ConsoleDrivers.ConsoleKeyMapping; namespace Terminal.Gui; @@ -110,6 +111,7 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord } _stringBuilder.Append (EscSeqUtils.CSI_RestoreCursorPosition); + _stringBuilder.Append (EscSeqUtils.CSI_HideCursor); var s = _stringBuilder.ToString (); @@ -129,6 +131,11 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord return result; } + public bool WriteANSI (string ansi) + { + return WriteConsole (_screenBuffer, ansi, (uint)ansi.Length, out uint _, null); + } + public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window) { _screenBuffer = CreateConsoleScreenBuffer ( @@ -167,7 +174,10 @@ public void ReadFromConsoleOutput (Size size, Coord coords, ref SmallRect window } } - public bool SetCursorPosition (Coord position) { return SetConsoleCursorPosition (_screenBuffer, position); } + public bool SetCursorPosition (Coord position) + { + return SetConsoleCursorPosition (_screenBuffer, position); + } public void SetInitialCursorVisibility () { @@ -577,14 +587,14 @@ public struct InputRecord public readonly override string ToString () { return EventType switch - { - EventType.Focus => FocusEvent.ToString (), - EventType.Key => KeyEvent.ToString (), - EventType.Menu => MenuEvent.ToString (), - EventType.Mouse => MouseEvent.ToString (), - EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), - _ => "Unknown event type: " + EventType - }; + { + EventType.Focus => FocusEvent.ToString (), + EventType.Key => KeyEvent.ToString (), + EventType.Menu => MenuEvent.ToString (), + EventType.Mouse => MouseEvent.ToString (), + EventType.WindowBufferSize => WindowBufferSizeEvent.ToString (), + _ => "Unknown event type: " + EventType + }; } } @@ -980,7 +990,6 @@ internal class WindowsDriver : ConsoleDriver { private readonly bool _isWindowsTerminal; - private CursorVisibility _cachedCursorVisibility; private WindowsConsole.SmallRect _damageRegion; private bool _isButtonDoubleClicked; private bool _isButtonPressed; @@ -1023,9 +1032,6 @@ public WindowsDriver () public WindowsConsole WinConsole { get; private set; } - /// - public override bool EnsureCursorVisibility () { return WinConsole is null || WinConsole.EnsureCursorVisibility (); } - public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsole.KeyEventRecord keyEvent) { if (keyEvent.wVirtualKeyCode != (VK)ConsoleKey.Packet) @@ -1072,25 +1078,12 @@ public WindowsConsole.KeyEventRecord FromVKPacketToKeyEventRecord (WindowsConsol }; } - /// - public override bool GetCursorVisibility (out CursorVisibility visibility) - { - if (WinConsole is { }) - { - return WinConsole.GetCursorVisibility (out visibility); - } - - visibility = _cachedCursorVisibility; - - return true; - } - public override bool IsRuneSupported (Rune rune) { return base.IsRuneSupported (rune) && rune.IsBmp; } public override void Refresh () { UpdateScreen (); - WinConsole?.SetInitialCursorVisibility (); + //WinConsole?.SetInitialCursorVisibility (); UpdateCursor (); } @@ -1164,13 +1157,6 @@ public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool al } } - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - _cachedCursorVisibility = visibility; - - return WinConsole is null || WinConsole.SetCursorVisibility (visibility); - } #region Not Implemented @@ -1194,9 +1180,13 @@ public WindowsConsole.ConsoleKeyInfoEx ToConsoleKeyInfoEx (WindowsConsole.KeyEve return new WindowsConsole.ConsoleKeyInfoEx (cki, capslock, numlock, scrolllock); } + #region Cursor Handling + + private CursorVisibility? _cachedCursorVisibility; + public override void UpdateCursor () { - if (Col < 0 || Row < 0 || Col > Cols || Row > Rows) + if (Col < 0 || Row < 0 || Col >= Cols || Row >= Rows) { GetCursorVisibility (out CursorVisibility cursorVisibility); _cachedCursorVisibility = cursorVisibility; @@ -1205,16 +1195,90 @@ public override void UpdateCursor () return; } - SetCursorVisibility (_cachedCursorVisibility); - var position = new WindowsConsole.Coord { X = (short)Col, Y = (short)Row }; - WinConsole?.SetCursorPosition (position); + + if (Force16Colors) + { + WinConsole?.SetCursorPosition (position); + } + else + { + var sb = new StringBuilder (); + sb.Append (EscSeqUtils.CSI_SetCursorPosition (position.Y + 1, position.X + 1)); + WinConsole?.WriteANSI (sb.ToString ()); + } + + if (_cachedCursorVisibility is { }) + { + SetCursorVisibility (_cachedCursorVisibility.Value); + } + //EnsureCursorVisibility (); } + /// + public override bool GetCursorVisibility (out CursorVisibility visibility) + { + if (WinConsole is { }) + { + return WinConsole.GetCursorVisibility (out visibility); + } + + visibility = _cachedCursorVisibility ?? CursorVisibility.Default; + + return true; + } + + /// + public override bool SetCursorVisibility (CursorVisibility visibility) + { + _cachedCursorVisibility = visibility; + + if (Force16Colors) + { + return WinConsole is null || WinConsole.SetCursorVisibility (visibility); + } + else + { + var sb = new StringBuilder (); + sb.Append (visibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); + return WinConsole?.WriteANSI (sb.ToString ()) ?? false; + } + } + + /// + public override bool EnsureCursorVisibility () + { + if (Force16Colors) + { + return WinConsole is null || WinConsole.EnsureCursorVisibility (); + } + else + { + var sb = new StringBuilder (); + sb.Append (_cachedCursorVisibility != CursorVisibility.Invisible ? EscSeqUtils.CSI_ShowCursor : EscSeqUtils.CSI_HideCursor); + return WinConsole?.WriteANSI (sb.ToString ()) ?? false; + } + + if (!(Col >= 0 && Row >= 0 && Col < Cols && Row < Rows)) + { + GetCursorVisibility (out CursorVisibility cursorVisibility); + _cachedCursorVisibility = cursorVisibility; + SetCursorVisibility (CursorVisibility.Invisible); + + return false; + } + + SetCursorVisibility (_cachedCursorVisibility ?? CursorVisibility.Default); + + return _cachedCursorVisibility == CursorVisibility.Default; + } + + #endregion Cursor Handling + public override void UpdateScreen () { Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows); @@ -1226,8 +1290,8 @@ public override void UpdateScreen () var bufferCoords = new WindowsConsole.Coord { - X = (short)Clip.Width, - Y = (short)Clip.Height + X = (short)Cols, //Clip.Width, + Y = (short)Rows, //Clip.Height }; for (var row = 0; row < Rows; row++) @@ -1574,13 +1638,13 @@ private KeyCode MapKey (WindowsConsole.ConsoleKeyInfoEx keyInfoEx) // returned (e.g. on ENG OemPlus un-shifted is =, not +). This is important // for key persistence ("Ctrl++" vs. "Ctrl+="). mappedChar = keyInfo.Key switch - { - ConsoleKey.OemPeriod => '.', - ConsoleKey.OemComma => ',', - ConsoleKey.OemPlus => '+', - ConsoleKey.OemMinus => '-', - _ => mappedChar - }; + { + ConsoleKey.OemPeriod => '.', + ConsoleKey.OemComma => ',', + ConsoleKey.OemPlus => '+', + ConsoleKey.OemMinus => '-', + _ => mappedChar + }; } // Return the mappedChar with they modifiers. Because mappedChar is un-shifted, if Shift was down @@ -1745,7 +1809,10 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag) Flags = mouseFlag }; - if (Application.WantContinuousButtonPressedView is null) + // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. + View view = Application.WantContinuousButtonPressedView; + + if (view is null) { break; } @@ -1759,6 +1826,7 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag) //Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}"); if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { + // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. Application.Invoke (() => OnMouseEvent (me)); } } @@ -1813,6 +1881,7 @@ private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) if (_isButtonDoubleClicked || _isOneFingerDoubleClicked) { + // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. Application.MainLoop.AddIdle ( () => { @@ -1884,6 +1953,7 @@ private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent) if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { + // TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application. Application.MainLoop.AddIdle ( () => { diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs index 6fe9858523..0e9fac7613 100644 --- a/Terminal.Gui/Drawing/LineCanvas.cs +++ b/Terminal.Gui/Drawing/LineCanvas.cs @@ -48,7 +48,7 @@ public class LineCanvas : IDisposable // TODO: Add other resolvers }; - private Rectangle _cachedBounds; + private Rectangle _cachedViewport; /// Creates a new instance. public LineCanvas () @@ -67,37 +67,37 @@ public LineCanvas () /// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is /// furthest left/top and Size is defined by the line that extends the furthest right/bottom. /// - public Rectangle Bounds + public Rectangle Viewport { get { - if (_cachedBounds.IsEmpty) + if (_cachedViewport.IsEmpty) { if (_lines.Count == 0) { - return _cachedBounds; + return _cachedViewport; } - Rectangle bounds = _lines [0].Bounds; + Rectangle viewport = _lines [0].Viewport; for (var i = 1; i < _lines.Count; i++) { - bounds = Rectangle.Union (bounds, _lines [i].Bounds); + viewport = Rectangle.Union (viewport, _lines [i].Viewport); } - if (bounds is {Width: 0} or {Height: 0}) + if (viewport is {Width: 0} or {Height: 0}) { - bounds = bounds with + viewport = viewport with { - Width = Math.Clamp (bounds.Width, 1, short.MaxValue), - Height = Math.Clamp (bounds.Height, 1, short.MaxValue) + Width = Math.Clamp (viewport.Width, 1, short.MaxValue), + Height = Math.Clamp (viewport.Height, 1, short.MaxValue) }; } - _cachedBounds = bounds; + _cachedViewport = viewport; } - return _cachedBounds; + return _cachedViewport; } } @@ -134,7 +134,7 @@ public void AddLine ( Attribute? attribute = default ) { - _cachedBounds = Rectangle.Empty; + _cachedViewport = Rectangle.Empty; _lines.Add (new StraightLine (start, length, orientation, style, attribute)); } @@ -142,14 +142,14 @@ public void AddLine ( /// public void AddLine (StraightLine line) { - _cachedBounds = Rectangle.Empty; + _cachedViewport = Rectangle.Empty; _lines.Add (line); } /// Clears all lines from the LineCanvas. public void Clear () { - _cachedBounds = Rectangle.Empty; + _cachedViewport = Rectangle.Empty; _lines.Clear (); } @@ -157,7 +157,7 @@ public void Clear () /// Clears any cached states from the canvas Call this method if you make changes to lines that have already been /// added. /// - public void ClearCache () { _cachedBounds = Rectangle.Empty; } + public void ClearCache () { _cachedViewport = Rectangle.Empty; } /// /// Evaluates the lines that have been added to the canvas and returns a map containing the glyphs and their @@ -170,9 +170,9 @@ public Dictionary GetCellMap () Dictionary map = new (); // walk through each pixel of the bitmap - for (int y = Bounds.Y; y < Bounds.Y + Bounds.Height; y++) + for (int y = Viewport.Y; y < Viewport.Y + Viewport.Height; y++) { - for (int x = Bounds.X; x < Bounds.X + Bounds.Width; x++) + for (int x = Viewport.X; x < Viewport.X + Viewport.Width; x++) { IntersectionDefinition? [] intersects = _lines .Select (l => l.Intersects (x, y)) @@ -232,7 +232,7 @@ public Dictionary GetMap (Rectangle inArea) /// intersection symbols. /// /// A map of all the points within the canvas. - public Dictionary GetMap () { return GetMap (Bounds); } + public Dictionary GetMap () { return GetMap (Viewport); } /// Merges one line canvas into this one. /// @@ -260,13 +260,13 @@ public StraightLine RemoveLastLine () /// /// Returns the contents of the line canvas rendered to a string. The string will include all columns and rows, - /// even if has negative coordinates. For example, if the canvas contains a single line that + /// even if has negative coordinates. For example, if the canvas contains a single line that /// starts at (-1,-1) with a length of 2, the rendered string will have a length of 2. /// /// The canvas rendered to a string. public override string ToString () { - if (Bounds.IsEmpty) + if (Viewport.IsEmpty) { return string.Empty; } @@ -275,13 +275,13 @@ public override string ToString () Dictionary runeMap = GetMap (); // Create the rune canvas - Rune [,] canvas = new Rune [Bounds.Height, Bounds.Width]; + Rune [,] canvas = new Rune [Viewport.Height, Viewport.Width]; // Copy the rune map to the canvas, adjusting for any negative coordinates foreach (KeyValuePair kvp in runeMap) { - int x = kvp.Key.X - Bounds.X; - int y = kvp.Key.Y - Bounds.Y; + int x = kvp.Key.X - Viewport.X; + int y = kvp.Key.Y - Viewport.Y; canvas [y, x] = kvp.Value; } diff --git a/Terminal.Gui/Drawing/StraightLine.cs b/Terminal.Gui/Drawing/StraightLine.cs index 1dd7b803c4..9a2785f0f4 100644 --- a/Terminal.Gui/Drawing/StraightLine.cs +++ b/Terminal.Gui/Drawing/StraightLine.cs @@ -45,8 +45,8 @@ public StraightLine ( /// Gets the rectangle that describes the bounds of the canvas. Location is the coordinates of the line that is /// furthest left/top and Size is defined by the line that extends the furthest right/bottom. /// - // PERF: Probably better to store the rectangle rather than make a new one on every single access to Bounds. - internal Rectangle Bounds + // PERF: Probably better to store the rectangle rather than make a new one on every single access to Viewport. + internal Rectangle Viewport { get { diff --git a/Terminal.Gui/Drawing/Thickness.cs b/Terminal.Gui/Drawing/Thickness.cs index 26f97ffb35..ffe8b3f708 100644 --- a/Terminal.Gui/Drawing/Thickness.cs +++ b/Terminal.Gui/Drawing/Thickness.cs @@ -161,6 +161,7 @@ public Rectangle Draw (Rectangle rect, string label = null) Application.Driver.FillRect (rect with { Height = Math.Min (rect.Height, Top) }, topChar); } + // Draw the Left side // Draw the Left side if (Left > 0) { diff --git a/Terminal.Gui/Input/Mouse.cs b/Terminal.Gui/Input/Mouse.cs index 2b3f2852be..7ea253a764 100644 --- a/Terminal.Gui/Input/Mouse.cs +++ b/Terminal.Gui/Input/Mouse.cs @@ -116,10 +116,10 @@ public class MouseEvent /// The View at the location for the mouse event. public View View { get; set; } - /// The X position of the mouse in -relative coordinates. + /// The X position of the mouse in -relative coordinates. public int X { get; set; } - /// The Y position of the mouse in -relative coordinates. + /// The Y position of the mouse in -relative coordinates. public int Y { get; set; } /// @@ -133,12 +133,12 @@ public class MouseEvent /// /// /// - /// The and properties are always -relative. When the mouse is grabbed by a view, + /// The and properties are always -relative. When the mouse is grabbed by a view, /// provides the mouse position screen-relative coordinates, enabling the grabbed view to know how much the /// mouse has moved. /// /// - /// Calculated and processed in . + /// Calculated and processed in . /// /// public Point ScreenPosition { get; set; } diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj index c2f39edb84..3c909c250d 100644 --- a/Terminal.Gui/Terminal.Gui.csproj +++ b/Terminal.Gui/Terminal.Gui.csproj @@ -48,8 +48,8 @@ - - + + diff --git a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs index ba5fbed5a4..76d74f512d 100644 --- a/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/AppendAutocomplete.cs @@ -117,7 +117,7 @@ public override void RenderOverlay (Point renderAt) Suggestion suggestion = Suggestions.ElementAt (SelectedIdx); string fragment = suggestion.Replacement.Substring (suggestion.Remove); - int spaceAvailable = textField.Bounds.Width - textField.Text.GetColumns (); + int spaceAvailable = textField.Viewport.Width - textField.Text.GetColumns (); int spaceRequired = fragment.EnumerateRunes ().Sum (c => c.GetColumns ()); if (spaceAvailable < spaceRequired) diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs index 55a6140759..86fc949ecf 100644 --- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs +++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.PopUp.cs @@ -16,7 +16,7 @@ public Popup (PopupAutocomplete autoComplete) protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { return _autoComplete.OnMouseEvent (mouseEvent); } - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (!_autoComplete.LastPopupPos.HasValue) { diff --git a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs index 84ea483657..e76aa8a2d0 100644 --- a/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs +++ b/Terminal.Gui/Text/Autocomplete/PopupAutocomplete.cs @@ -271,13 +271,13 @@ public override void RenderOverlay (Point renderAt) if (PopupInsideContainer) { // don't overspill vertically - height = Math.Min (HostControl.Bounds.Height - renderAt.Y, MaxHeight); + height = Math.Min (HostControl.Viewport.Height - renderAt.Y, MaxHeight); // There is no space below, lets see if can popup on top - if (height < Suggestions.Count && HostControl.Bounds.Height - renderAt.Y >= height) + if (height < Suggestions.Count && HostControl.Viewport.Height - renderAt.Y >= height) { // Verifies that the upper limit available is greater than the lower limit - if (renderAt.Y > HostControl.Bounds.Height - renderAt.Y) + if (renderAt.Y > HostControl.Viewport.Height - renderAt.Y) { renderAt.Y = Math.Max (renderAt.Y - Math.Min (Suggestions.Count + 1, MaxHeight + 1), 0); height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), LastPopupPos.Value.Y - 1); @@ -287,13 +287,13 @@ public override void RenderOverlay (Point renderAt) else { // don't overspill vertically - height = Math.Min (Math.Min (top.Bounds.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count); + height = Math.Min (Math.Min (top.Viewport.Height - HostControl.Frame.Bottom, MaxHeight), Suggestions.Count); // There is no space below, lets see if can popup on top if (height < Suggestions.Count && HostControl.Frame.Y - top.Frame.Y >= height) { // Verifies that the upper limit available is greater than the lower limit - if (HostControl.Frame.Y > top.Bounds.Height - HostControl.Frame.Y) + if (HostControl.Frame.Y > top.Viewport.Height - HostControl.Frame.Y) { renderAt.Y = Math.Max (HostControl.Frame.Y - Math.Min (Suggestions.Count, MaxHeight), 0); height = Math.Min (Math.Min (Suggestions.Count, MaxHeight), HostControl.Frame.Y); @@ -323,34 +323,34 @@ public override void RenderOverlay (Point renderAt) if (PopupInsideContainer) { // don't overspill horizontally, let's see if can be displayed on the left - if (width > HostControl.Bounds.Width - renderAt.X) + if (width > HostControl.Viewport.Width - renderAt.X) { // Verifies that the left limit available is greater than the right limit - if (renderAt.X > HostControl.Bounds.Width - renderAt.X) + if (renderAt.X > HostControl.Viewport.Width - renderAt.X) { renderAt.X -= Math.Min (width, LastPopupPos.Value.X); width = Math.Min (width, LastPopupPos.Value.X); } else { - width = Math.Min (width, HostControl.Bounds.Width - renderAt.X); + width = Math.Min (width, HostControl.Viewport.Width - renderAt.X); } } } else { // don't overspill horizontally, let's see if can be displayed on the left - if (width > top.Bounds.Width - (renderAt.X + HostControl.Frame.X)) + if (width > top.Viewport.Width - (renderAt.X + HostControl.Frame.X)) { // Verifies that the left limit available is greater than the right limit - if (renderAt.X + HostControl.Frame.X > top.Bounds.Width - (renderAt.X + HostControl.Frame.X)) + if (renderAt.X + HostControl.Frame.X > top.Viewport.Width - (renderAt.X + HostControl.Frame.X)) { renderAt.X -= Math.Min (width, LastPopupPos.Value.X); width = Math.Min (width, LastPopupPos.Value.X); } else { - width = Math.Min (width, top.Bounds.Width - renderAt.X); + width = Math.Min (width, top.Viewport.Width - renderAt.X); } } } diff --git a/Terminal.Gui/Text/TextFormatter.cs b/Terminal.Gui/Text/TextFormatter.cs index 6168c53f01..db13b5930b 100644 --- a/Terminal.Gui/Text/TextFormatter.cs +++ b/Terminal.Gui/Text/TextFormatter.cs @@ -30,7 +30,7 @@ public TextAlignment Alignment /// Gets or sets whether the should be automatically changed to fit the . /// - /// Used by to resize the view's to fit . + /// Used by to resize the view's to fit . /// /// AutoSize is ignored if and /// are used. @@ -73,8 +73,8 @@ public TextDirection Direction } /// - /// Determines if the bounds width will be used or only the text width will be used, - /// If all the bounds area will be filled with whitespaces and the same background color + /// Determines if the viewport width will be used or only the text width will be used, + /// If all the viewport area will be filled with whitespaces and the same background color /// showing a perfect rectangle. /// public bool FillRemaining { get; set; } @@ -202,17 +202,17 @@ public bool WordWrap /// 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. + /// Specifies the screen-relative location and maximum container size. /// The console driver currently used by the application. /// public void Draw ( - Rectangle bounds, + Rectangle screen, Attribute normalColor, Attribute hotColor, - Rectangle containerBounds = default, + Rectangle maximum = default, ConsoleDriver driver = null ) { @@ -240,46 +240,46 @@ public void Draw ( } bool isVertical = IsVerticalDirection (Direction); - Rectangle maxBounds = bounds; + Rectangle maxScreen = screen; if (driver is { }) { // INTENT: What, exactly, is the intent of this? - maxBounds = containerBounds == default (Rectangle) - ? bounds + maxScreen = maximum == default (Rectangle) + ? screen : new ( - Math.Max (containerBounds.X, bounds.X), - Math.Max (containerBounds.Y, bounds.Y), + Math.Max (maximum.X, screen.X), + Math.Max (maximum.Y, screen.Y), Math.Max ( - Math.Min (containerBounds.Width, containerBounds.Right - bounds.Left), + Math.Min (maximum.Width, maximum.Right - screen.Left), 0 ), Math.Max ( Math.Min ( - containerBounds.Height, - containerBounds.Bottom - bounds.Top + maximum.Height, + maximum.Bottom - screen.Top ), 0 ) ); } - if (maxBounds.Width == 0 || maxBounds.Height == 0) + if (maxScreen.Width == 0 || maxScreen.Height == 0) { return; } - int lineOffset = !isVertical && bounds.Y < 0 ? Math.Abs (bounds.Y) : 0; + int lineOffset = !isVertical && screen.Y < 0 ? Math.Abs (screen.Y) : 0; for (int line = lineOffset; line < linesFormatted.Count; line++) { - if ((isVertical && line > bounds.Width) || (!isVertical && line > bounds.Height)) + if ((isVertical && line > screen.Width) || (!isVertical && line > screen.Height)) { continue; } - if ((isVertical && line >= maxBounds.Left + maxBounds.Width) - || (!isVertical && line >= maxBounds.Top + maxBounds.Height + lineOffset)) + if ((isVertical && line >= maxScreen.Left + maxScreen.Width) + || (!isVertical && line >= maxScreen.Top + maxScreen.Height + lineOffset)) { break; } @@ -305,14 +305,14 @@ public void Draw ( if (isVertical) { int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth); - x = bounds.Right - runesWidth; - CursorPosition = bounds.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 = bounds.Right - runesWidth; - CursorPosition = bounds.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 void Draw ( int runesWidth = line > 0 ? GetWidestLineLength (linesFormatted, 0, line, TabWidth) : 0; - x = bounds.Left + runesWidth; + x = screen.Left + runesWidth; } else { - x = bounds.Left; + x = screen.Left; } CursorPosition = _hotKeyPos > -1 ? _hotKeyPos : 0; @@ -336,16 +336,16 @@ public void Draw ( if (isVertical) { int runesWidth = GetWidestLineLength (linesFormatted, line, TabWidth); - x = bounds.Left + line + (bounds.Width - runesWidth) / 2; + x = screen.Left + line + (screen.Width - runesWidth) / 2; - CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); + CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); } else { int runesWidth = StringExtensions.ToString (runes).GetColumns (); - x = bounds.Left + (bounds.Width - runesWidth) / 2; + x = screen.Left + (screen.Width - runesWidth) / 2; - CursorPosition = (bounds.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); + CursorPosition = (screen.Width - runesWidth) / 2 + (_hotKeyPos > -1 ? _hotKeyPos : 0); } } else @@ -358,35 +358,35 @@ public void Draw ( { if (isVertical) { - y = bounds.Bottom - runes.Length; + y = screen.Bottom - runes.Length; } else { - y = bounds.Bottom - linesFormatted.Count + line; + y = screen.Bottom - linesFormatted.Count + line; } } else if (VerticalAlignment is VerticalTextAlignment.Top or VerticalTextAlignment.Justified) { if (isVertical) { - y = bounds.Top; + y = screen.Top; } else { - y = bounds.Top + line; + y = screen.Top + line; } } else if (VerticalAlignment == VerticalTextAlignment.Middle) { if (isVertical) { - int s = (bounds.Height - runes.Length) / 2; - y = bounds.Top + s; + int s = (screen.Height - runes.Length) / 2; + y = screen.Top + s; } else { - int s = (bounds.Height - linesFormatted.Count) / 2; - y = bounds.Top + line + s; + int s = (screen.Height - linesFormatted.Count) / 2; + y = screen.Top + line + s; } } else @@ -394,9 +394,9 @@ public void Draw ( throw new ArgumentOutOfRangeException ($"{nameof (VerticalAlignment)}"); } - int colOffset = bounds.X < 0 ? Math.Abs (bounds.X) : 0; - int start = isVertical ? bounds.Top : bounds.Left; - int size = isVertical ? bounds.Height : bounds.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,15 +422,15 @@ public void Draw ( break; } - if ((!isVertical && current - start > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) - || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) + if ((!isVertical && current - start > maxScreen.Left + maxScreen.Width - screen.X + colOffset) + || (isVertical && idx > maxScreen.Top + maxScreen.Height - screen.Y)) { break; } } - //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - bounds.X + colOffset) - // || (isVertical && idx > maxBounds.Top + maxBounds.Height - bounds.Y)) + //if ((!isVertical && idx > maxBounds.Left + maxBounds.Width - viewport.X + colOffset) + // || (isVertical && idx > maxBounds.Top + maxBounds.Height - viewport.Y)) // break; diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs index 307f3cb3e1..585929e991 100644 --- a/Terminal.Gui/View/Adornment/Adornment.cs +++ b/Terminal.Gui/View/Adornment/Adornment.cs @@ -1,14 +1,14 @@ namespace Terminal.Gui; /// -/// Adornments are a special form of that appear outside the : +/// Adornments are a special form of that appear outside the : /// , , and . They are defined using the /// class, which specifies the thickness of the sides of a rectangle. /// /// /// /// Each of , , and has slightly different -/// behavior relative to , , keyboard input, and +/// behavior relative to , , keyboard input, and /// mouse input. Each can be customized by manipulating their Subviews. /// /// @@ -89,7 +89,7 @@ public void OnThicknessChanged (Thickness previousThickness) public override View SuperView { get => null; - set => throw new NotImplementedException (); + set => throw new InvalidOperationException (@"Adornments can not be Subviews or have SuperViews. Use Parent instead."); } //internal override Adornment CreateAdornment (Type adornmentType) @@ -107,10 +107,14 @@ internal override void LayoutAdornments () /// Gets the rectangle that describes the area of the Adornment. The Location is always (0,0). /// The size is the size of the . /// - public override Rectangle Bounds + /// + /// The Viewport of an Adornment cannot be modified. Attempting to set this property will throw an + /// . + /// + public override Rectangle Viewport { get => Frame with { Location = Point.Empty }; - set => throw new InvalidOperationException ("It makes no sense to set Bounds of a Thickness."); + set => throw new InvalidOperationException (@"The Viewport of an Adornment cannot be modified."); } /// @@ -123,10 +127,11 @@ public override Rectangle FrameToScreen () // Adornments are *Children* of a View, not SubViews. Thus View.FrameToScreen will not work. // To get the screen-relative coordinates of an Adornment, we need get the parent's Frame - // in screen coords, and add our Frame location to it. - Rectangle parent = Parent.FrameToScreen (); + // in screen coords, ... + Rectangle parentScreen = Parent.FrameToScreen (); - return new (new (parent.X + Frame.X, parent.Y + Frame.Y), Frame.Size); + // ...and add our Frame location to it. + return new (new (parentScreen.X + Frame.X, parentScreen.Y + Frame.Y), Frame.Size); } /// @@ -137,19 +142,21 @@ public override Rectangle FrameToScreen () public override bool OnDrawAdornments () { return false; } /// Redraws the Adornments that comprise the . - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (Thickness == Thickness.Empty) { return; } - Rectangle screenBounds = BoundsToScreen (contentArea); + Rectangle prevClip = SetClip (); + + Rectangle screen = ViewportToScreen (viewport); Attribute normalAttr = GetNormalColor (); Driver.SetAttribute (normalAttr); // This just draws/clears the thickness, not the insides. - Thickness.Draw (screenBounds, ToString ()); + Thickness.Draw (screen, ToString ()); if (!string.IsNullOrEmpty (TextFormatter.Text)) { @@ -160,11 +167,16 @@ public override void OnDrawContent (Rectangle contentArea) } } - TextFormatter?.Draw (screenBounds, normalAttr, normalAttr, Rectangle.Empty); + TextFormatter?.Draw (screen, normalAttr, normalAttr, Rectangle.Empty); if (Subviews.Count > 0) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); + } + + if (Driver is { }) + { + Driver.Clip = prevClip; } ClearLayoutNeeded (); @@ -181,8 +193,8 @@ public override void OnDrawContent (Rectangle contentArea) /// public override bool SuperViewRendersLineCanvas { - get => false; // throw new NotImplementedException (); - set => throw new NotImplementedException (); + get => false; + set => throw new InvalidOperationException (@"Adornment can only render to their Parent or Parent's Superview."); } #endregion View Overrides @@ -205,6 +217,7 @@ public override bool Contains (int x, int y) { return false; } + Rectangle frame = Frame; frame.Offset (Parent.Frame.Location); @@ -227,43 +240,6 @@ public override bool Contains (int x, int y) return base.OnMouseEnter (mouseEvent); } - /// Called when a mouse event occurs within the Adornment. - /// - /// - /// The coordinates are relative to . - /// - /// - /// A mouse click on the Adornment will cause the Parent to focus. - /// - /// - /// A mouse drag on the Adornment will cause the Parent to move. - /// - /// - /// - /// , if the event was handled, otherwise. - protected internal override bool OnMouseEvent (MouseEvent mouseEvent) - { - if (Parent is null) - { - return false; - } - - var args = new MouseEventEventArgs (mouseEvent); - - if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)) - { - if (Parent.CanFocus && !Parent.HasFocus) - { - Parent.SetFocus (); - Parent.SetNeedsDisplay (); - } - - return OnMouseClick (args); - } - - return false; - } - /// protected internal override bool OnMouseLeave (MouseEvent mouseEvent) { diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs index cee764c568..922fa2a19f 100644 --- a/Terminal.Gui/View/Adornment/Border.cs +++ b/Terminal.Gui/View/Adornment/Border.cs @@ -149,14 +149,14 @@ public override ColorScheme ColorScheme } } - Rectangle GetBorderBounds (Rectangle screenBounds) + Rectangle GetBorderRectangle (Rectangle screenRect) { return new ( - screenBounds.X + Math.Max (0, Thickness.Left - 1), - screenBounds.Y + Math.Max (0, Thickness.Top - 1), + screenRect.X + Math.Max (0, Thickness.Left - 1), + screenRect.Y + Math.Max (0, Thickness.Top - 1), Math.Max ( 0, - screenBounds.Width + screenRect.Width - Math.Max ( 0, Math.Max (0, Thickness.Left - 1) @@ -165,7 +165,7 @@ Rectangle GetBorderBounds (Rectangle screenBounds) ), Math.Max ( 0, - screenBounds.Height + screenRect.Height - Math.Max ( 0, Math.Max (0, Thickness.Top - 1) @@ -204,6 +204,7 @@ private void Border_Highlight (object sender, HighlightEventArgs e) { if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable)) { + e.Cancel = true; return; } @@ -300,7 +301,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) _dragPosition = new Point (mouseEvent.X, mouseEvent.Y); - Point parentLoc = Parent.SuperView?.ScreenToBounds (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y) ?? mouseEvent.ScreenPosition; + Point parentLoc = Parent.SuperView?.ScreenToViewport (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y) ?? mouseEvent.ScreenPosition; GetLocationEnsuringFullVisibility ( Parent, @@ -360,9 +361,9 @@ private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e) #endregion Mouse Support /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); if (Thickness == Thickness.Empty) { @@ -370,7 +371,7 @@ public override void OnDrawContent (Rectangle contentArea) } //Driver.SetAttribute (Colors.ColorSchemes ["Error"].Normal); - Rectangle screenBounds = BoundsToScreen (contentArea); + Rectangle screenBounds = ViewportToScreen (viewport); //OnDrawSubviews (bounds); @@ -381,7 +382,7 @@ public override void OnDrawContent (Rectangle contentArea) // ...thickness extends outward (border/title is always as far in as possible) // PERF: How about a call to Rectangle.Offset? - var borderBounds = GetBorderBounds (screenBounds); + var borderBounds = GetBorderRectangle (screenBounds); int topTitleLineY = borderBounds.Y; int titleY = borderBounds.Y; var titleBarsLength = 0; // the little vertical thingies @@ -432,10 +433,17 @@ public override void OnDrawContent (Rectangle contentArea) if (canDrawBorder && Thickness.Top > 0 && maxTitleWidth > 0 && !string.IsNullOrEmpty (Parent?.Title)) { + var focus = Parent.GetNormalColor(); + if (Parent.SuperView is { } && Parent.SuperView?.Subviews!.Count (s => s.CanFocus) > 1) + { + // Only use focus color if there are multiple focusable views + focus = Parent.GetFocusColor() ; + } + Parent.TitleTextFormatter.Draw ( new (borderBounds.X + 2, titleY, maxTitleWidth, 1), - Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (), - Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetHotNormalColor ()); + Parent.HasFocus ? focus : Parent.GetNormalColor (), + Parent.HasFocus ? focus : Parent.GetHotNormalColor ()); } if (canDrawBorder && LineStyle != LineStyle.None) @@ -641,7 +649,5 @@ public override void OnDrawContent (Rectangle contentArea) } } } - - //base.OnDrawContent (contentArea); } } diff --git a/Terminal.Gui/View/Adornment/Padding.cs b/Terminal.Gui/View/Adornment/Padding.cs index d9d373de4d..f979dcf903 100644 --- a/Terminal.Gui/View/Adornment/Padding.cs +++ b/Terminal.Gui/View/Adornment/Padding.cs @@ -42,7 +42,7 @@ public override ColorScheme ColorScheme /// Called when a mouse event occurs within the Padding. /// /// - /// The coordinates are relative to . + /// The coordinates are relative to . /// /// /// A mouse click on the Padding will cause the Parent to focus. diff --git a/Terminal.Gui/View/Layout/ViewLayout.cs b/Terminal.Gui/View/Layout/ViewLayout.cs index dcfce11103..f593d93d2b 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; @@ -42,18 +43,18 @@ public partial class View /// Gets or sets the absolute location and dimension of the view. /// /// The rectangle describing absolute location and dimension of the view, in coordinates relative to the - /// 's . + /// 's Content, which is bound by . /// /// - /// Frame is relative to the 's . + /// Frame is relative to the 's Content, which is bound by . /// /// Setting Frame will set , , , and to the /// values of the corresponding properties of the parameter. + /// This causes to be . /// - /// This causes to be . /// /// Altering the Frame will eventually (when the view hierarchy is next laid out via see - /// cref="LayoutSubviews"/>) cause and + /// cref="LayoutSubviews"/>) cause and /// /// methods to be called. /// @@ -63,7 +64,12 @@ public Rectangle Frame get => _frame; set { - _frame = value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) }; + if (_frame == value) + { + return; + } + + SetFrame (value with { Width = Math.Max (value.Width, 0), Height = Math.Max (value.Height, 0) }); // If Frame gets set, by definition, the View is now LayoutStyle.Absolute, so // set all Pos/Dim to Absolute values. @@ -73,62 +79,83 @@ public Rectangle Frame _height = _frame.Height; // TODO: Figure out if the below can be optimized. - if (IsInitialized /*|| LayoutStyle == LayoutStyle.Absolute*/) + if (IsInitialized) { - LayoutAdornments (); - SetTextFormatterSize (); - SetNeedsLayout (); - SetNeedsDisplay (); + OnResizeNeeded (); } } } + private void SetFrame (Rectangle frame) + { + Rectangle oldViewport = Rectangle.Empty; + if (IsInitialized) + { + oldViewport = Viewport; + } + // This is the only place where _frame should be set directly. Use Frame = or SetFrame instead. + _frame = frame; + + OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); + } + /// Gets the with a screen-relative location. /// The location and size of the view in screen-relative coordinates. public virtual Rectangle FrameToScreen () { - Rectangle ret = Frame; - View super = SuperView; + Rectangle screen = Frame; + View current = SuperView; - while (super is { }) + while (current is { }) { - if (super is Adornment adornment) + if (current is Adornment adornment) { // Adornments don't have SuperViews; use Adornment.FrameToScreen override - ret = adornment.FrameToScreen (); - ret.Offset (Frame.X, Frame.Y); + // which will give us the screen coordinates of the parent + + var parentScreen = adornment.FrameToScreen (); + + // Now add our Frame location + parentScreen.Offset (screen.X, screen.Y); - return ret; + return parentScreen; } - Point boundsOffset = super.GetBoundsOffset (); - boundsOffset.Offset(super.Frame.X, super.Frame.Y); - ret.X += boundsOffset.X; - ret.Y += boundsOffset.Y; - super = super.SuperView; + Point viewportOffset = current.GetViewportOffsetFromFrame (); + viewportOffset.Offset (current.Frame.X - current.Viewport.X, current.Frame.Y - current.Viewport.Y); + screen.X += viewportOffset.X; + screen.Y += viewportOffset.Y; + current = current.SuperView; } - return ret; + return screen; } /// /// Converts a screen-relative coordinate to a Frame-relative coordinate. Frame-relative means relative to the - /// View's 's . + /// View's 's . /// - /// The coordinate relative to the 's . + /// The coordinate relative to the 's . /// Screen-relative column. /// Screen-relative row. public virtual Point ScreenToFrame (int x, int y) { - Point superViewBoundsOffset = SuperView?.GetBoundsOffset () ?? Point.Empty; if (SuperView is null) { - superViewBoundsOffset.Offset (x - Frame.X, y - Frame.Y); - return superViewBoundsOffset; + return new Point (x - Frame.X, y - Frame.Y); } - var frame = SuperView.ScreenToFrame (x - superViewBoundsOffset.X, y - superViewBoundsOffset.Y); - frame.Offset (-Frame.X, -Frame.Y); + Point superViewViewportOffset = SuperView.GetViewportOffsetFromFrame (); + superViewViewportOffset.X -= SuperView.Viewport.X; + superViewViewportOffset.Y -= SuperView.Viewport.Y; + + x -= superViewViewportOffset.X; + y -= superViewViewportOffset.Y; + + Point frame = SuperView.ScreenToFrame (x, y); + frame.X -= Frame.X; + frame.Y -= Frame.Y; + return frame; } @@ -138,13 +165,16 @@ public virtual Point ScreenToFrame (int x, int y) /// The object representing the X position. /// /// + /// The position is relative to the 's Content, which is bound by . + /// + /// /// If set to a relative value (e.g. ) the value is indeterminate until the view has been - /// initialized ( is true) and has been + /// initialized ( is true) and has been /// called. /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. If the new value is not of type @@ -157,7 +187,13 @@ public Pos X get => VerifyIsInitialized (_x, nameof (X)); set { + if (Equals (_x, value)) + { + return; + } + _x = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (X)} cannot be null"); + OnResizeNeeded (); } } @@ -168,13 +204,16 @@ public Pos X /// The object representing the Y position. /// /// + /// The position is relative to the 's Content, which is bound by . + /// + /// /// If set to a relative value (e.g. ) the value is indeterminate until the view has been - /// initialized ( is true) and has been + /// initialized ( is true) and has been /// called. /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. If the new value is not of type @@ -187,6 +226,11 @@ public Pos Y get => VerifyIsInitialized (_y, nameof (Y)); set { + if (Equals (_y, value)) + { + return; + } + _y = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Y)} cannot be null"); OnResizeNeeded (); } @@ -198,13 +242,16 @@ public Pos Y /// The object representing the height of the view (the number of rows). /// /// + /// The dimension is relative to the 's Content, which is bound by . + /// + /// /// If set to a relative value (e.g. ) the value is indeterminate until the view has - /// been initialized ( is true) and has been + /// been initialized ( is true) and has been /// called. /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. If the new value is not of type @@ -217,6 +264,11 @@ public Dim Height get => VerifyIsInitialized (_height, nameof (Height)); set { + if (Equals (_height, value)) + { + return; + } + _height = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Height)} cannot be null"); if (AutoSize) @@ -247,13 +299,16 @@ public Dim Height /// The object representing the width of the view (the number of columns). /// /// + /// The dimension is relative to the 's Content, which is bound by . + /// + /// /// If set to a relative value (e.g. ) the value is indeterminate until the view has - /// been initialized ( is true) and has been + /// been initialized ( is true) and has been /// called. /// /// /// Changing this property will eventually (when the view is next drawn) cause the - /// and methods to be called. + /// and methods to be called. /// /// /// Changing this property will cause to be updated. If the new value is not of type @@ -266,6 +321,11 @@ public Dim Width get => VerifyIsInitialized (_width, nameof (Width)); set { + if (Equals (_width, value)) + { + return; + } + _width = value ?? throw new ArgumentNullException (nameof (value), @$"{nameof (Width)} cannot be null"); if (AutoSize) @@ -288,123 +348,13 @@ public Dim Width #endregion Frame - #region Bounds - - /// - /// The bounds represent the View-relative rectangle used for this view; the area inside the view where - /// subviews and content are presented. - /// - /// The rectangle describing the location and size of the area where the views' subviews and content are drawn. - /// - /// - /// If is the value of Bounds is indeterminate until - /// the view has been initialized ( is true) and has been - /// called. - /// - /// - /// Updates to the Bounds updates , and has the same effect as updating the - /// . - /// - /// - /// Altering the Bounds will eventually (when the view is next laid out) cause the - /// and methods to be called. - /// - /// - /// Because coordinates are relative to the upper-left corner of the , the - /// coordinates of the upper-left corner of the rectangle returned by this property are (0,0). Use this property to - /// obtain the size of the area of the view for tasks such as drawing the view's contents. - /// - /// - public virtual Rectangle Bounds - { - get - { -#if DEBUG - if (LayoutStyle == LayoutStyle.Computed && !IsInitialized) - { - Debug.WriteLine ( - $"WARNING: Bounds 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 Rectangle.Empty with { Size = Frame.Size }; - } - - Thickness totalThickness = GetAdornmentsThickness (); - - return Rectangle.Empty with - { - Size = new ( - Math.Max (0, Frame.Size.Width - totalThickness.Horizontal), - Math.Max (0, Frame.Size.Height - totalThickness.Vertical)) - }; - } - set - { - // TODO: Should we enforce Bounds.X/Y == 0? The code currently ignores value.X/Y which is - // TODO: correct behavior, but is silent. Perhaps an exception? -#if DEBUG - if (value.Location != Point.Empty) - { - Debug.WriteLine ( - $"WARNING: Bounds.Location must always be 0,0. Location ({value.Location}) is ignored. {this}" - ); - } -#endif // DEBUG - 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 BoundsToScreen (in Rectangle bounds) - { - // Translate bounds to Frame (our SuperView's Bounds-relative coordinates) - Rectangle screen = FrameToScreen (); - Point boundsOffset = GetBoundsOffset (); - screen.Offset (boundsOffset.X + bounds.X, boundsOffset.Y + bounds.Y); - - return new (screen.Location, bounds.Size); - } - - /// Converts a screen-relative coordinate to a bounds-relative coordinate. - /// The coordinate relative to this view's . - /// Screen-relative column. - /// Screen-relative row. - public Point ScreenToBounds (int x, int y) - { - Point boundsOffset = GetBoundsOffset (); - Point screen = ScreenToFrame (x, y); - screen.Offset (-boundsOffset.X, -boundsOffset.Y); - - return screen; - } - - /// - /// Helper to get the X and Y offset of the Bounds from the Frame. This is the sum of the Left and Top properties - /// of , and . - /// - public Point GetBoundsOffset () { return Padding is null ? Point.Empty : Padding.Thickness.GetInside (Padding.Frame).Location; } - - #endregion Bounds - #region AutoSize private bool _autoSize; /// /// Gets or sets a flag that determines whether the View will be automatically resized to fit the - /// within . + /// within . /// /// The default is . Set to to turn on AutoSize. If /// then and will be used if can @@ -462,7 +412,7 @@ private bool ResizeView (bool autoSize) if (ValidatePosDim) { // BUGBUG: This ain't right, obviously. We need to figure out how to handle this. - boundsChanged = ResizeBoundsToFit (newFrameSize); + boundsChanged = ResizeViewportToFit (newFrameSize); } else { @@ -577,35 +527,35 @@ internal bool TrySetWidth (int desiredWidth, out int resultWidth) /// Resizes the View to fit the specified size. Factors in the HotKey. /// ResizeBoundsToFit can only be called when AutoSize is true (or being set to true). /// - /// whether the Bounds was changed or not - private bool ResizeBoundsToFit (Size size) + /// whether the Viewport was changed or not + private bool ResizeViewportToFit (Size size) { //if (AutoSize == false) { - // throw new InvalidOperationException ("ResizeBoundsToFit can only be called when AutoSize is true"); + // throw new InvalidOperationException ("ResizeViewportToFit can only be called when AutoSize is true"); //} - var boundsChanged = false; + var changed = false; bool canSizeW = TrySetWidth (size.Width - GetHotKeySpecifierLength (), out int rW); bool canSizeH = TrySetHeight (size.Height - GetHotKeySpecifierLength (false), out int rH); if (canSizeW) { - boundsChanged = true; + changed = true; _width = rW; } if (canSizeH) { - boundsChanged = true; + changed = true; _height = rH; } - if (boundsChanged) + if (changed) { - Bounds = new (Bounds.X, Bounds.Y, canSizeW ? rW : Bounds.Width, canSizeH ? rH : Bounds.Height); + Viewport = new (Viewport.X, Viewport.Y, canSizeW ? rW : Viewport.Width, canSizeH ? rH : Viewport.Height); } - return boundsChanged; + return changed; } #endregion AutoSize @@ -651,25 +601,20 @@ public LayoutStyle LayoutStyle #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. @@ -682,57 +627,66 @@ public virtual bool Contains (int x, int y) // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews. internal static View? FindDeepestView (View? start, int x, int y) { - if (start is null || !start.Visible || !start.Contains (x, y)) + while (start is { Visible: true } && start.Contains (x, y)) { - return null; - } - - Adornment? found = null; + Adornment? found = null; - if (start.Margin.Contains (x, y)) - { - found = start.Margin; - } - else if (start.Border.Contains (x, y)) - { - found = start.Border; - } - else if (start.Padding.Contains (x, y)) - { - found = start.Padding; - } + if (start.Margin.Contains (x, y)) + { + found = start.Margin; + } + else if (start.Border.Contains (x, y)) + { + found = start.Border; + } + else if (start.Padding.Contains (x, y)) + { + found = start.Padding; + } - Point boundsOffset = start.GetBoundsOffset (); + Point viewportOffset = start.GetViewportOffsetFromFrame (); - if (found is { }) - { - start = found; - boundsOffset = found.Parent.Frame.Location; - } + if (found is { }) + { + start = found; + viewportOffset = found.Parent.Frame.Location; + } - if (start.InternalSubviews is { Count: > 0 }) - { - int startOffsetX = x - (start.Frame.X + boundsOffset.X); - int startOffsetY = y - (start.Frame.Y + boundsOffset.Y); + int startOffsetX = x - (start.Frame.X + viewportOffset.X); + int startOffsetY = y - (start.Frame.Y + viewportOffset.Y); + View? subview = null; for (int i = start.InternalSubviews.Count - 1; i >= 0; i--) { - View nextStart = start.InternalSubviews [i]; - - if (nextStart.Visible && nextStart.Contains (startOffsetX, startOffsetY)) + if (start.InternalSubviews [i].Visible + && start.InternalSubviews [i].Contains (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)) { - // TODO: Remove recursion - return FindDeepestView (nextStart, startOffsetX, startOffsetY) ?? nextStart; + subview = start.InternalSubviews [i]; + x = startOffsetX + start.Viewport.X; + y = startOffsetY + start.Viewport.Y; + + // start is the deepest subview under the mouse; stop searching the subviews + break; } } + + if (subview is null) + { + // No subview was found that's under the mouse, so we're done + return start; + } + + // We found a subview of start that's under the mouse, continue... + start = subview; } - return start; + return null; } + #nullable restore /// - /// Gets a new location of the that is within the Bounds of the 's + /// Gets a new location of the that is within the Viewport of the 's /// (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates. /// /// @@ -761,6 +715,7 @@ out StatusBar statusBar { int maxDimension; View superView; + statusBar = null; if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top) { @@ -769,8 +724,8 @@ out StatusBar statusBar } else { - // Use the SuperView's Bounds, not Frame - maxDimension = viewToMove.SuperView.Bounds.Width; + // Use the SuperView's Viewport, not Frame + maxDimension = viewToMove.SuperView.Viewport.Width; superView = viewToMove.SuperView; } @@ -795,7 +750,8 @@ out StatusBar statusBar } //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) { @@ -805,12 +761,15 @@ out StatusBar statusBar { 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) @@ -833,13 +792,16 @@ out StatusBar statusBar { 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) @@ -848,7 +810,7 @@ out StatusBar statusBar } 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) @@ -916,8 +878,7 @@ public virtual void LayoutSubviews () LayoutAdornments (); - Rectangle oldBounds = Bounds; - OnLayoutStarted (new () { OldBounds = oldBounds }); + OnLayoutStarted (new (ContentSize)); SetTextFormatterSize (); @@ -929,7 +890,7 @@ public virtual void LayoutSubviews () foreach (View v in ordered) { - LayoutSubview (v, new (GetBoundsOffset (), Bounds.Size)); + LayoutSubview (v, ContentSize); } // If the 'to' is rooted to 'from' and the layoutstyle is Computed it's a special-case. @@ -938,13 +899,19 @@ public virtual void LayoutSubviews () { foreach ((View from, View to) in edges) { - LayoutSubview (to, from.Frame); + LayoutSubview (to, from.ContentSize); } } LayoutNeeded = false; - OnLayoutComplete (new () { OldBounds = oldBounds }); + OnLayoutComplete (new (ContentSize)); + } + 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. @@ -969,19 +936,19 @@ public virtual void LayoutSubviews () /// /// /// Determines the relative bounds of the and its s, and then calls - /// to update the view. + /// to update the view. /// /// internal void OnResizeNeeded () { // TODO: Identify a real-world use-case where this API should be virtual. // TODO: Until then leave it `internal` and non-virtual - // First try SuperView.Bounds, then Application.Top, then Driver.Bounds. + // First try SuperView.Viewport, then Application.Top, then Driver.Viewport. // Finally, if none of those are valid, use int.MaxValue (for Unit tests). - Rectangle relativeBounds = SuperView is { IsInitialized: true } ? SuperView.Bounds : - Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.Bounds : - Application.Driver?.Bounds ?? new Rectangle (0, 0, int.MaxValue, int.MaxValue); - SetRelativeLayout (relativeBounds); + Size contentSize = SuperView is { IsInitialized: true } ? SuperView.ContentSize : + Application.Top is { } && Application.Top != this && Application.Top.IsInitialized ? Application.Top.ContentSize : + Application.Driver?.Screen.Size ?? new (int.MaxValue, int.MaxValue); + SetRelativeLayout (contentSize); // TODO: Determine what, if any of the below is actually needed here. if (IsInitialized) @@ -997,6 +964,7 @@ internal void OnResizeNeeded () 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. @@ -1021,15 +989,22 @@ internal void SetNeedsLayout () } /// - /// Applies the view's position (, ) and dimension (, and - /// ) to , given a rectangle describing the SuperView's Bounds (nominally the - /// same as this.SuperView.Bounds). + /// Adjusts given the SuperView's ContentSize (nominally the same as + /// this.SuperView.ContentSize) + /// and the position (, ) and dimension (, and + /// ). /// - /// - /// The rectangle describing the SuperView's Bounds (nominally the same as - /// this.SuperView.Bounds). + /// + /// + /// 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). /// - internal void SetRelativeLayout (Rectangle superviewBounds) + internal void SetRelativeLayout (Size superviewContentSize) { Debug.Assert (_x is { }); Debug.Assert (_y is { }); @@ -1051,13 +1026,13 @@ internal void SetRelativeLayout (Rectangle superviewBounds) // TODO: View.AutoSize stuff is removed. // Returns the new dimension (width or height) and location (x or y) for the View given - // the superview's Bounds + // the superview's Viewport // the current Pos (View.X or View.Y) // the current Dim (View.Width or View.Height) // This method is called recursively if pos is Pos.PosCombine (int newLocation, int newDimension) GetNewLocationAndDimension ( bool width, - Rectangle superviewBounds, + Size superviewContentSize, Pos pos, Dim dim, int autosizeDimension @@ -1119,7 +1094,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) } int newDimension, newLocation; - int superviewDimension = width ? superviewBounds.Width : superviewBounds.Height; + int superviewDimension = width ? superviewContentSize.Width : superviewContentSize.Height; // Determine new location switch (pos) @@ -1143,7 +1118,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) (left, newDimension) = GetNewLocationAndDimension ( width, - superviewBounds, + superviewContentSize, combine._left, dim, autosizeDimension @@ -1151,7 +1126,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) (right, newDimension) = GetNewLocationAndDimension ( width, - superviewBounds, + superviewContentSize, combine._right, dim, autosizeDimension @@ -1193,10 +1168,10 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) } // horizontal/width - (newX, newW) = GetNewLocationAndDimension (true, superviewBounds, _x, _width, autosize.Width); + (newX, newW) = GetNewLocationAndDimension (true, superviewContentSize, _x, _width, autosize.Width); // vertical/height - (newY, newH) = GetNewLocationAndDimension (false, superviewBounds, _y, _height, autosize.Height); + (newY, newH) = GetNewLocationAndDimension (false, superviewContentSize, _y, _height, autosize.Height); Rectangle r = new (newX, newY, newW, newH); @@ -1204,7 +1179,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) { // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making // the view LayoutStyle.Absolute. - _frame = r; + SetFrame (r); if (_x is Pos.PosAbsolute) { @@ -1236,7 +1211,7 @@ int GetNewDimension (Dim d, int location, int dimension, int autosize) { // Set the frame. Do NOT use `Frame` as it overwrites X, Y, Width, and Height, making // the view LayoutStyle.Absolute. - _frame = _frame with { Size = autosize }; + SetFrame (_frame with { Size = autosize }); if (autosize.Width == 0) { @@ -1417,17 +1392,6 @@ internal static List TopologicalSort ( return result; } // TopologicalSort - private void LayoutSubview (View v, Rectangle contentArea) - { - //if (v.LayoutStyle == LayoutStyle.Computed) { - v.SetRelativeLayout (contentArea); - - //} - - v.LayoutSubviews (); - v.LayoutNeeded = false; - } - #region Diagnostics // Diagnostics to highlight when Width or Height is read before the view has been initialized @@ -1468,4 +1432,4 @@ private Pos VerifyIsInitialized (Pos pos, string member) public bool ValidatePosDim { get; set; } #endregion -} +} \ No newline at end of file diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs index 07235e638b..8a04e51702 100644 --- a/Terminal.Gui/View/View.cs +++ b/Terminal.Gui/View/View.cs @@ -52,7 +52,7 @@ namespace Terminal.Gui; /// To create a View using Absolute layout, call a constructor that takes a Rect parameter to specify the /// absolute position and size or simply set ). To create a View using Computed layout use /// a constructor that does not take a Rect parameter and set the X, Y, Width and Height properties on the view to -/// non-absolute values. Both approaches use coordinates that are relative to the of the +/// non-absolute values. Both approaches use coordinates that are relative to the of the /// the View is added to. /// /// @@ -73,7 +73,7 @@ namespace Terminal.Gui; /// a View can be accessed with the property. /// /// -/// To flag a region of the View's to be redrawn call +/// To flag a region of the View's to be redrawn call /// . /// To flag the entire view for redraw call . /// @@ -234,7 +234,7 @@ public virtual void EndInit () // TODO: Move these into ViewText.cs as EndInit_Text() to consolodate. // TODO: Verify UpdateTextDirection really needs to be called here. - // These calls were moved from BeginInit as they access Bounds which is indeterminate until EndInit is called. + // These calls were moved from BeginInit as they access Viewport which is indeterminate until EndInit is called. UpdateTextDirection (TextDirection); UpdateTextFormatterText (); OnResizeNeeded (); diff --git a/Terminal.Gui/View/ViewAdornments.cs b/Terminal.Gui/View/ViewAdornments.cs index 55445cef82..4efd92b526 100644 --- a/Terminal.Gui/View/ViewAdornments.cs +++ b/Terminal.Gui/View/ViewAdornments.cs @@ -39,7 +39,7 @@ private void DisposeAdornments () /// /// The that enables separation of a View from other SubViews of the same - /// SuperView. The margin offsets the from the . + /// SuperView. The margin offsets the from the . /// /// /// @@ -55,7 +55,7 @@ private void DisposeAdornments () public Margin Margin { get; private set; } /// - /// The that offsets the from the . + /// The that offsets the from the . /// The Border provides the space for a visual border (drawn using /// line-drawing glyphs) and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2` the /// border and title will take up the first row and the second row will be filled with spaces. @@ -116,7 +116,7 @@ public LineStyle BorderStyle } /// - /// The inside of the view that offsets the + /// The inside of the view that offsets the /// from the . /// /// @@ -151,7 +151,7 @@ internal virtual void LayoutAdornments () if (Margin.Frame.Size != Frame.Size) { - Margin._frame = Rectangle.Empty with { Size = Frame.Size }; + Margin.SetFrame (Rectangle.Empty with { Size = Frame.Size }); Margin.X = 0; Margin.Y = 0; Margin.Width = Frame.Size.Width; @@ -170,7 +170,7 @@ internal virtual void LayoutAdornments () if (border != Border.Frame) { - Border._frame = border; + Border.SetFrame (border); Border.X = border.Location.X; Border.Y = border.Location.Y; Border.Width = border.Size.Width; @@ -189,7 +189,7 @@ internal virtual void LayoutAdornments () if (padding != Padding.Frame) { - Padding._frame = padding; + Padding.SetFrame (padding); Padding.X = padding.Location.X; Padding.Y = padding.Location.Y; Padding.Width = padding.Size.Width; diff --git a/Terminal.Gui/View/ViewContent.cs b/Terminal.Gui/View/ViewContent.cs new file mode 100644 index 0000000000..90fa3ab4a5 --- /dev/null +++ b/Terminal.Gui/View/ViewContent.cs @@ -0,0 +1,483 @@ +using System.Diagnostics; + +namespace Terminal.Gui; + +/// +/// Settings for how the behaves relative to the View's Content area. +/// +[Flags] +public enum ViewportSettings +{ + /// + /// No settings. + /// + None = 0, + + /// + /// If set, .X can be set to negative values enabling scrolling beyond the left of + /// the + /// content area. + /// + /// + /// + /// When not set, .X is constrained to positive values. + /// + /// + AllowNegativeX = 1, + + /// + /// If set, .Y can be set to negative values enabling scrolling beyond the top of the + /// content area. + /// + /// + /// + /// When not set, .Y is constrained to positive values. + /// + /// + AllowNegativeY = 2, + + /// + /// If set, .Size can be set to negative coordinates enabling scrolling beyond the + /// top-left of the + /// content area. + /// + /// + /// + /// When not set, .Size is constrained to positive coordinates. + /// + /// + AllowNegativeLocation = AllowNegativeX | AllowNegativeY, + + /// + /// If set, .X can be set values greater than + /// .Width enabling scrolling beyond the right + /// of the content area. + /// + /// + /// + /// When not set, .X is constrained to + /// .Width - 1. + /// This means the last column of the content will remain visible even if there is an attempt to scroll the + /// Viewport past the last column. + /// + /// + /// The practical effect of this is that the last column of the content will always be visible. + /// + /// + AllowXGreaterThanContentWidth = 4, + + /// + /// If set, .Y can be set values greater than + /// .Height enabling scrolling beyond the right + /// of the content area. + /// + /// + /// + /// When not set, .Y is constrained to + /// .Height - 1. + /// This means the last row of the content will remain visible even if there is an attempt to scroll the Viewport + /// past the last row. + /// + /// + /// The practical effect of this is that the last row of the content will always be visible. + /// + /// + AllowYGreaterThanContentHeight = 8, + + /// + /// If set, .Size can be set values greater than + /// enabling scrolling beyond the bottom-right + /// of the content area. + /// + /// + /// + /// When not set, is constrained to -1. + /// This means the last column and row of the content will remain visible even if there is an attempt to + /// scroll the Viewport past the last column or row. + /// + /// + AllowLocationGreaterThanContentSize = AllowXGreaterThanContentWidth | AllowYGreaterThanContentHeight, + + /// + /// By default, clipping is applied to the . Setting this flag will cause clipping to be + /// applied to the visible content area. + /// + ClipContentOnly = 16, + + /// + /// If set will clear only the portion of the content + /// area that is visible within the . This is useful for views that have a + /// content area larger than the Viewport and want the area outside the content to be visually distinct. + /// + /// + /// must be set for this setting to work (clipping beyond the visible area must be + /// disabled). + /// + ClearContentOnly = 32 +} + +public partial class View +{ + #region Content Area + + private Size _contentSize; + + /// + /// Gets or sets the size of the View's content. If not set, the value will be the same as the size of , + /// 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. + /// + /// + /// Negative sizes are not supported. + /// + /// + public Size ContentSize + { + get => _contentSize == Size.Empty ? Viewport.Size : _contentSize; + set + { + if (value.Width < 0 || value.Height < 0) + { + throw new ArgumentException (@"ContentSize cannot be negative.", nameof (value)); + } + + if (value == _contentSize) + { + return; + } + + _contentSize = value; + OnContentSizeChanged (new (_contentSize)); + } + } + + /// + /// Called when changes. Invokes the event. + /// + /// + /// + protected bool? OnContentSizeChanged (SizeChangedEventArgs e) + { + ContentSizeChanged?.Invoke (this, e); + + if (e.Cancel != true) + { + SetNeedsLayout (); + SetNeedsDisplay (); + } + + return e.Cancel; + } + + /// + /// Event raised when the changes. + /// + public event EventHandler ContentSizeChanged; + + /// + /// Converts a Content-relative location to a Screen-relative location. + /// + /// The Content-relative location. + /// The Screen-relative location. + public Point ContentToScreen (in Point location) + { + // Subtract the ViewportOffsetFromFrame to get the Viewport-relative location. + Point viewportOffset = GetViewportOffsetFromFrame (); + Point contentRelativeToViewport = location; + contentRelativeToViewport.Offset (-Viewport.X, -Viewport.Y); + + // Translate to Screen-Relative (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, which is + /// always at 0, 0. + /// + /// The Screen-relative location. + /// The coordinate relative to this view's Content. + public Point ScreenToContent (in Point location) + { + Point viewportOffset = GetViewportOffsetFromFrame (); + Point screen = ScreenToFrame (location.X, location.Y); + screen.Offset (Viewport.X - viewportOffset.X, Viewport.Y - viewportOffset.Y); + + return screen; + } + + #endregion Content Area + + #region Viewport + + private ViewportSettings _viewportSettings; + + /// + /// Gets or sets how scrolling the on the View's Content Area is handled. + /// + public ViewportSettings ViewportSettings + { + get => _viewportSettings; + set + { + if (_viewportSettings == value) + { + return; + } + + _viewportSettings = value; + + if (IsInitialized) + { + // Force set Viewport to cause settings to be applied as needed + SetViewport (Viewport); + } + } + } + + /// + /// The location of the viewport into the view's content (0,0) is the top-left corner of the content. The Content + /// area's size + /// is . + /// + private Point _viewportLocation; + + /// + /// 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 . + /// If the viewport Size is the same as the Location will be 0, 0. + /// + /// + /// The rectangle describing the location and size of the viewport into the View's virtual content, described by + /// . + /// + /// + /// + /// Positive values for the location indicate the visible area is offset into (down-and-right) the View's virtual + /// . This enables scrolling down and to the right (e.g. in a . + /// + /// + /// Negative values for the location indicate the visible area is offset above (up-and-left) the View's virtual + /// . This enables scrolling up and to the left (e.g. in an image viewer that supports zoom + /// where the image stays centered). + /// + /// + /// The property controls how scrolling is handled. + /// + /// + /// 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 (_viewportLocation, Frame.Size); + } + + Thickness thickness = GetAdornmentsThickness (); + + return new ( + _viewportLocation, + new ( + Math.Max (0, Frame.Size.Width - thickness.Horizontal), + Math.Max (0, Frame.Size.Height - thickness.Vertical) + )); + } + set => SetViewport (value); + } + + private void SetViewport (Rectangle viewport) + { + Rectangle oldViewport = viewport; + ApplySettings (ref viewport); + + Thickness thickness = GetAdornmentsThickness (); + + Size newSize = new ( + viewport.Size.Width + thickness.Horizontal, + viewport.Size.Height + thickness.Vertical); + + if (newSize == Frame.Size) + { + // The change is not changing the Frame, so we don't need to update it. + // Just call SetNeedsLayout to update the layout. + if (_viewportLocation != viewport.Location) + { + _viewportLocation = viewport.Location; + SetNeedsLayout (); + } + + OnViewportChanged (new (IsInitialized ? Viewport : Rectangle.Empty, oldViewport)); + + return; + } + + _viewportLocation = viewport.Location; + + // Update the Frame because we made it bigger or smaller which impacts subviews. + Frame = Frame with + { + Size = newSize + }; + + void ApplySettings (ref Rectangle newViewport) + { + if (!ViewportSettings.HasFlag (ViewportSettings.AllowXGreaterThanContentWidth)) + { + if (newViewport.X >= ContentSize.Width) + { + newViewport.X = ContentSize.Width - 1; + } + } + + // IMPORTANT: Check for negative location AFTER checking for location greater than content width + if (!ViewportSettings.HasFlag (ViewportSettings.AllowNegativeX)) + { + if (newViewport.X < 0) + { + newViewport.X = 0; + } + } + + if (!ViewportSettings.HasFlag (ViewportSettings.AllowYGreaterThanContentHeight)) + { + if (newViewport.Y >= ContentSize.Height) + { + newViewport.Y = ContentSize.Height - 1; + } + } + + // IMPORTANT: Check for negative location AFTER checking for location greater than content width + if (!ViewportSettings.HasFlag (ViewportSettings.AllowNegativeY)) + { + if (newViewport.Y < 0) + { + newViewport.Y = 0; + } + } + } + } + + /// + /// Fired when the changes. This event is fired after the has been updated. + /// + [CanBeNull] + public event EventHandler ViewportChanged; + + /// + /// Called when the changes. Invokes the event. + /// + /// + protected virtual void OnViewportChanged (DrawEventArgs e) { ViewportChanged?.Invoke (this, e); } + + /// + /// 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 = GetViewportOffsetFromFrame (); + 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 = GetViewportOffsetFromFrame (); + 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 GetViewportOffsetFromFrame () { 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; + } + + 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; + } + + Viewport = Viewport with { X = Viewport.X + cols }; + + return true; + } + + #endregion Viewport +} diff --git a/Terminal.Gui/View/ViewDrawing.cs b/Terminal.Gui/View/ViewDrawing.cs index 37094bbaea..cc7109a5bb 100644 --- a/Terminal.Gui/View/ViewDrawing.cs +++ b/Terminal.Gui/View/ViewDrawing.cs @@ -1,4 +1,6 @@ -namespace Terminal.Gui; +using System.Drawing; + +namespace Terminal.Gui; public partial class View { @@ -54,67 +56,108 @@ public bool NeedsDisplay public bool SubViewNeedsDisplay { get; private set; } /// - /// Gets or sets whether this View will use it's SuperView's for rendering any border - /// lines. If the rendering of any borders drawn by this Frame will be done by it's parent's + /// Gets or sets whether this View will use it's SuperView's for rendering any + /// lines. If the rendering of any borders drawn by this Frame will be done by its parent's /// SuperView. If (the default) this View's method will be /// called to render the borders. /// public virtual bool SuperViewRendersLineCanvas { get; set; } = false; - /// Displays the specified character in the specified column and row of the View. - /// Column (view-relative). - /// Row (view-relative). - /// Ch. - public void AddRune (int col, int row, Rune ch) + /// Draws the specified character in the specified viewport-relative column and row of 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. + /// + /// Column (viewport-relative). + /// Row (viewport-relative). + /// The Rune. + public void AddRune (int col, int row, Rune rune) { - if (row < 0 || col < 0) + if (Move (col, row)) { - return; + Driver.AddRune (rune); } + } - if (row > _frame.Height - 1 || col > _frame.Width - 1) + /// Clears with the normal background. + /// + /// + /// If has only + /// the portion of the content + /// area that is visible within the will be cleared. This is useful for views that have a + /// content area larger than the Viewport (e.g. when is + /// enabled) and want + /// the area outside the content to be visually distinct. + /// + /// + public void Clear () + { + if (Driver is null) { return; } - Move (col, row); - Driver.AddRune (ch); - } + // Get screen-relative coords + Rectangle toClear = ViewportToScreen (Viewport with { Location = new (0, 0) }); + + Rectangle prevClip = Driver.Clip; + + if (ViewportSettings.HasFlag (ViewportSettings.ClearContentOnly)) + { + Rectangle visibleContent = ViewportToScreen (new (new (-Viewport.X, -Viewport.Y), ContentSize)); + toClear = Rectangle.Intersect (toClear, visibleContent); + } - /// Clears with the normal background. - /// - public void Clear () { Clear (Bounds); } + Attribute prev = Driver.SetAttribute (GetNormalColor()); + Driver.FillRect (toClear); + Driver.SetAttribute (prev); + + Driver.Clip = prevClip; + } - /// Clears the specified -relative rectangle with the normal background. - /// - /// The Bounds-relative rectangle to clear. - public void Clear (Rectangle contentArea) + /// Fills the specified -relative rectangle with the specified color. + /// The Viewport-relative rectangle to clear. + /// The color to use to fill the rectangle. If not provided, the Normal background color will be used. + public void FillRect (Rectangle rect, Color? color = null) { if (Driver is null) { return; } - Attribute prev = Driver.SetAttribute (GetNormalColor ()); + // Get screen-relative coords + Rectangle toClear = ViewportToScreen (rect); + + Rectangle prevClip = Driver.Clip; + + Driver.Clip = Rectangle.Intersect (prevClip, ViewportToScreen (Viewport with { Location = new (0, 0) })); - // Clamp the region to the bounds of the view - contentArea = Rectangle.Intersect (contentArea, Bounds); - Driver.FillRect (BoundsToScreen (contentArea)); + Attribute prev = Driver.SetAttribute (new (color ?? GetNormalColor().Background)); + Driver.FillRect (toClear); Driver.SetAttribute (prev); + + Driver.Clip = prevClip; } - /// Expands the 's clip region to include . + /// Sets the 's clip region to . + /// + /// + /// By default, the clip rectangle is set to the intersection of the current clip region and the + /// . This ensures that drawing is constrained to the viewport, but allows + /// content to be drawn beyond the viewport. + /// + /// + /// If has set, clipping will be + /// applied to just the visible content area. + /// + /// /// /// The current screen-relative clip region, which can be then re-applied by setting /// . /// - /// - /// - /// If and do not intersect, the clip region will be set to - /// . - /// - /// - public Rectangle ClipToBounds () + public Rectangle SetClip () { if (Driver is null) { @@ -122,7 +165,18 @@ public Rectangle ClipToBounds () } Rectangle previous = Driver.Clip; - Driver.Clip = Rectangle.Intersect (previous, BoundsToScreen (Bounds)); + + // Clamp the Clip to the entire visible area + Rectangle clip = Rectangle.Intersect (ViewportToScreen (Viewport with { Location = Point.Empty }), previous); + + if (ViewportSettings.HasFlag (ViewportSettings.ClipContentOnly)) + { + // Clamp the Clip to the just content area that is within the viewport + Rectangle visibleContent = ViewportToScreen (new (new (-Viewport.X, -Viewport.Y), ContentSize)); + clip = Rectangle.Intersect (clip, visibleContent); + } + + Driver.Clip = clip; return previous; } @@ -133,7 +187,7 @@ public Rectangle ClipToBounds () /// /// /// - /// Always use (view-relative) when calling , NOT + /// Always use (view-relative) when calling , NOT /// (superview-relative). /// /// @@ -142,7 +196,8 @@ public Rectangle ClipToBounds () /// /// /// Overrides of must ensure they do not set Driver.Clip to a clip - /// region larger than the property, as this will cause the driver to clip the entire region. + /// region larger than the property, as this will cause the driver to clip the entire + /// region. /// /// public void Draw () @@ -154,21 +209,24 @@ public void Draw () OnDrawAdornments (); - Rectangle prevClip = ClipToBounds (); - if (ColorScheme is { }) { //Driver.SetAttribute (HasFocus ? GetFocusColor () : GetNormalColor ()); Driver?.SetAttribute (GetNormalColor ()); } + // 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. + Rectangle prevClip = SetClip (); + // Invoke DrawContentEvent - var dev = new DrawEventArgs (Bounds); + var dev = new DrawEventArgs (Viewport, Rectangle.Empty); DrawContent?.Invoke (this, dev); if (!dev.Cancel) { - OnDrawContent (Bounds); + OnDrawContent (Viewport); } if (Driver is { }) @@ -179,7 +237,7 @@ public void Draw () OnRenderLineCanvas (); // Invoke DrawContentCompleteEvent - OnDrawContentComplete (Bounds); + OnDrawContentComplete (Viewport); // BUGBUG: v2 - We should be able to use View.SetClip here and not have to resort to knowing Driver details. ClearLayoutNeeded (); @@ -264,14 +322,13 @@ public void DrawHotString (string text, bool focused, ColorScheme scheme) /// Determines the current based on the value. /// - /// if is or - /// if is . If it's + /// if is or + /// if is . If it's /// overridden can return other values. /// public virtual Attribute GetFocusColor () { ColorScheme cs = ColorScheme; - if (ColorScheme is null) { cs = new (); @@ -316,19 +373,33 @@ public virtual Attribute GetNormalColor () return Enabled ? cs.Normal : cs.Disabled; } - /// This moves the cursor to the specified column and row in the view. - /// The move. - /// The column to move to, in view-relative coordinates. - /// the row to move to, in view-relative coordinates. - public void Move (int col, int row) + /// Moves the drawing cursor to the specified -relative location 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. + /// + /// + /// Column (viewport-relative). + /// Row (viewport-relative). + public bool Move (int col, int row) { if (Driver is null || Driver?.Rows == 0) { - return; + return false; } - Rectangle screen = BoundsToScreen (new (col, row, 0, 0)); + if (col < 0 || row < 0 || col >= Viewport.Width || row >= Viewport.Height) + { + return false; + } + + Rectangle screen = ViewportToScreen (new (col, row, 0, 0)); Driver?.Move (screen.X, screen.Y); + + return true; } // TODO: Make this cancelable @@ -347,26 +418,51 @@ public virtual bool OnDrawAdornments () // Each of these renders lines to either this View's LineCanvas // Those lines will be finally rendered in OnRenderLineCanvas - Margin?.OnDrawContent (Margin.Bounds); - Border?.OnDrawContent (Border.Bounds); - Padding?.OnDrawContent (Padding.Bounds); + Margin?.OnDrawContent (Margin.Viewport); + Border?.OnDrawContent (Border.Viewport); + Padding?.OnDrawContent (Padding.Viewport); return true; } - /// Enables overrides to draw infinitely scrolled content and/or a background behind added controls. - /// - /// The view-relative rectangle describing the currently visible viewport into the - /// + /// + /// Draws the view's content, including Subviews. + /// + /// + /// + /// The parameter is provided as a convenience; it has the same values as the + /// property. + /// + /// + /// The Location and Size indicate what part of the View's content, defined + /// by , is visible and should be drawn. The coordinates taken by and + /// are relative to , thus if ViewPort.Location.Y is 5 + /// the 6th row of the content should be drawn using MoveTo (x, 5). + /// + /// + /// If is larger than ViewPort.Size drawing code should use + /// to constrain drawing for better performance. + /// + /// + /// The may define smaller area than ; complex drawing code + /// can be more + /// efficient by using to constrain drawing for better performance. + /// + /// + /// Overrides should loop through the subviews and call . + /// + /// + /// + /// The rectangle describing the currently visible viewport into the ; has the same value as + /// . /// - /// This method will be called before any subviews added with have been drawn. - public virtual void OnDrawContent (Rectangle contentArea) + public virtual void OnDrawContent (Rectangle viewport) { if (NeedsDisplay) { if (SuperView is { }) { - Clear (contentArea); + Clear (); } if (!string.IsNullOrEmpty (TextFormatter.Text)) @@ -378,8 +474,11 @@ public virtual void OnDrawContent (Rectangle contentArea) } // This should NOT clear + // TODO: If the output is not in the Viewport, do nothing + var drawRect = new Rectangle (ContentToScreen (Point.Empty), ContentSize); + TextFormatter?.Draw ( - BoundsToScreen (contentArea), + drawRect, HasFocus ? GetFocusColor () : GetNormalColor (), HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (), Rectangle.Empty @@ -387,6 +486,7 @@ public virtual void OnDrawContent (Rectangle contentArea) SetSubViewNeedsDisplay (); } + // TODO: Move drawing of subviews to a separate OnDrawSubviews virtual method // Draw subviews // TODO: Implement OnDrawSubviews (cancelable); if (_subviews is { } && SubViewNeedsDisplay) @@ -395,39 +495,25 @@ public virtual void OnDrawContent (Rectangle contentArea) view => view.Visible && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded) ); - foreach (View view in subviewsNeedingDraw) { - //view.Frame.IntersectsWith (bounds)) { - // && (view.Frame.IntersectsWith (bounds) || bounds.X < 0 || bounds.Y < 0)) { if (view.LayoutNeeded) { view.LayoutSubviews (); } - - // Draw the subview - // Use the view's bounds (view-relative; Location will always be (0,0) - //if (view.Visible && view.Frame.Width > 0 && view.Frame.Height > 0) { view.Draw (); - - //} } } } /// - /// Enables overrides after completed drawing infinitely scrolled content and/or a background behind removed - /// controls. + /// Called after to enable overrides. /// - /// - /// The view-relative rectangle describing the currently visible viewport into the + /// + /// The viewport-relative rectangle describing the currently visible viewport into the /// /// - /// - /// This method will be called after any subviews removed with have been completed - /// drawing. - /// - public virtual void OnDrawContentComplete (Rectangle contentArea) { DrawContentComplete?.Invoke (this, new (contentArea)); } + public virtual void OnDrawContentComplete (Rectangle viewport) { DrawContentComplete?.Invoke (this, new (viewport, Rectangle.Empty)); } // TODO: Make this cancelable /// @@ -438,13 +524,13 @@ public virtual void OnDrawContent (Rectangle contentArea) /// public virtual bool OnRenderLineCanvas () { - if (!IsInitialized) + if (!IsInitialized || Driver is null) { return false; } // If we have a SuperView, it'll render our frames. - if (!SuperViewRendersLineCanvas && LineCanvas.Bounds != Rectangle.Empty) + if (!SuperViewRendersLineCanvas && LineCanvas.Viewport != Rectangle.Empty) { foreach (KeyValuePair p in LineCanvas.GetCellMap ()) { @@ -484,7 +570,7 @@ public virtual bool OnRenderLineCanvas () return true; } - /// Sets the area of this view needing to be redrawn to . + /// Sets the area of this view needing to be redrawn to . /// /// If the view has not been initialized ( is ), this method /// does nothing. @@ -493,21 +579,28 @@ public void SetNeedsDisplay () { if (IsInitialized) { - SetNeedsDisplay (Bounds); + SetNeedsDisplay (Viewport); } } /// Expands the area of this view needing to be redrawn to include . /// - /// If the view has not been initialized ( is ), the area to be - /// redrawn will be the . + /// + /// The location of is relative to the View's content, bound by Size.Empty and + /// . + /// + /// + /// If the view has not been initialized ( is ), the area to be + /// redrawn will be the . + /// /// - /// The Bounds-relative region that needs to be redrawn. + /// The content-relative region that needs to be redrawn. public void SetNeedsDisplay (Rectangle region) { if (!IsInitialized) { _needsDisplayRect = region; + return; } @@ -572,25 +665,7 @@ protected void ClearNeedsDisplay () foreach (View subview in Subviews) { - subview.ClearNeedsDisplay(); + subview.ClearNeedsDisplay (); } } - - // INTENT: Isn't this just intersection? It isn't used anyway. - // Clips a rectangle in screen coordinates to the dimensions currently available on the screen - internal Rectangle ScreenClip (Rectangle regionScreen) - { - int x = regionScreen.X < 0 ? 0 : regionScreen.X; - int y = regionScreen.Y < 0 ? 0 : regionScreen.Y; - - int w = regionScreen.X + regionScreen.Width >= Driver.Cols - ? Driver.Cols - regionScreen.X - : regionScreen.Width; - - int h = regionScreen.Y + regionScreen.Height >= Driver.Rows - ? Driver.Rows - regionScreen.Y - : regionScreen.Height; - - return new (x, y, w, h); - } } diff --git a/Terminal.Gui/View/ViewEventArgs.cs b/Terminal.Gui/View/ViewEventArgs.cs index 3179f94ec0..a53575f2e7 100644 --- a/Terminal.Gui/View/ViewEventArgs.cs +++ b/Terminal.Gui/View/ViewEventArgs.cs @@ -3,8 +3,8 @@ /// Args for events that relate to specific public class ViewEventArgs : EventArgs { - /// Creates a new instance of the class. - /// + /// Creates a new instance of the class. + /// The view that the event is about. public ViewEventArgs (View view) { View = view; } /// The view that the event is about. @@ -18,25 +18,40 @@ public class ViewEventArgs : EventArgs /// Event arguments for the event. public class LayoutEventArgs : EventArgs { - /// The view-relative bounds of the before it was laid out. - public Rectangle OldBounds { get; set; } + /// Creates a new instance of the class. + /// The view that the event is about. + public LayoutEventArgs (Size oldContentSize) { OldContentSize = oldContentSize; } + + /// The viewport of the before it was laid out. + public Size OldContentSize { get; set; } } /// Event args for draw events public class DrawEventArgs : EventArgs { /// Creates a new instance of the class. - /// - /// Gets the view-relative rectangle describing the currently visible viewport into the + /// + /// The Content-relative rectangle describing the new visible viewport into the + /// . + /// + /// + /// The Content-relative rectangle describing the old visible viewport into the /// . /// - public DrawEventArgs (Rectangle rect) { Rectangle = rect; } + public DrawEventArgs (Rectangle newViewport, Rectangle oldViewport) + { + NewViewport = newViewport; + OldViewport = oldViewport; + } /// If set to true, the draw operation will be canceled, if applicable. public bool Cancel { get; set; } - /// Gets the view-relative rectangle describing the currently visible viewport into the . - public Rectangle Rectangle { get; } + /// Gets the Content-relative rectangle describing the old visible viewport into the . + public Rectangle OldViewport { get; } + + /// Gets the Content-relative rectangle describing the currently visible viewport into the . + public Rectangle NewViewport { get; } } /// Defines the event arguments for diff --git a/Terminal.Gui/View/ViewMouse.cs b/Terminal.Gui/View/ViewMouse.cs index fa94be54f7..00684a3cf9 100644 --- a/Terminal.Gui/View/ViewMouse.cs +++ b/Terminal.Gui/View/ViewMouse.cs @@ -21,12 +21,12 @@ public enum HighlightStyle #endif /// - /// The mouse is pressed within the . + /// The mouse is pressed within the . /// Pressed = 2, /// - /// The mouse is pressed but moved outside the . + /// The mouse is pressed but moved outside the . /// PressedOutside = 4 } @@ -36,6 +36,10 @@ public enum HighlightStyle /// public class HighlightEventArgs : CancelEventArgs { + /// + /// Constructs a new instance of . + /// + /// public HighlightEventArgs (HighlightStyle style) { HighlightStyle = style; @@ -63,7 +67,7 @@ public partial class View public virtual bool WantMousePositionReports { get; set; } /// - /// Called by when the mouse enters . The view will + /// Called by when the mouse enters . The view will /// then receive mouse events until is called indicating the mouse has left /// the view. /// @@ -110,7 +114,7 @@ public partial class View } /// - /// Called by when the mouse enters . The view will + /// Called by when the mouse enters . The view will /// then receive mouse events until is called indicating the mouse has left /// the view. /// @@ -119,7 +123,7 @@ public partial class View /// Override this method or subscribe to to change the default enter behavior. /// /// - /// The coordinates are relative to . + /// The coordinates are relative to . /// /// /// @@ -133,12 +137,12 @@ public partial class View return args.Handled; } - /// Event fired when the mouse moves into the View's . + /// Event fired when the mouse moves into the View's . public event EventHandler MouseEnter; /// - /// Called by when the mouse leaves . The view will + /// Called by when the mouse leaves . The view will /// then no longer receive mouse events. /// /// @@ -180,15 +184,15 @@ public partial class View return false; } /// - /// Called by when a mouse leaves . The view will + /// Called by when a mouse leaves . The view will /// no longer receive mouse events. /// /// /// - /// Override this method or subscribe to to change the default leave behavior. + /// Override this method or subscribe to to change the default leave behavior. /// /// - /// The coordinates are relative to . + /// The coordinates are relative to . /// /// /// @@ -211,7 +215,7 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) return args.Handled; } - /// Event fired when the mouse leaves the View's . + /// Event fired when the mouse leaves the View's . public event EventHandler MouseLeave; /// @@ -227,7 +231,7 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) /// mouse buttons was clicked, it calls to process the click. /// /// - /// See and for more information. + /// See for more information. /// /// /// If is , the event @@ -307,7 +311,7 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent) /// /// /// - /// , if the event was handled, otherwise. + /// , if the event was handled, otherwise. private bool HandlePressed (MouseEvent mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed) @@ -325,9 +329,11 @@ private bool HandlePressed (MouseEvent mouseEvent) // Set the focus, but don't invoke Accept SetFocus (); } + + mouseEvent.Handled = true; } - if (Bounds.Contains (mouseEvent.X, mouseEvent.Y)) + if (Viewport.Contains (mouseEvent.X, mouseEvent.Y)) { if (SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None) == true) { @@ -408,7 +414,7 @@ internal bool HandleClicked (MouseEvent mouseEvent) } // If mouse is still in bounds, click - if (!WantContinuousButtonPressed && Bounds.Contains (mouseEvent.X, mouseEvent.Y)) + if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.X, mouseEvent.Y)) { return OnMouseClick (new (mouseEvent)); } @@ -489,9 +495,9 @@ internal bool SetHighlight (HighlightStyle style) }; ColorScheme = cs; } - - return true; } + // Return false since we don't want to eat the event + return false; } @@ -526,10 +532,10 @@ internal bool SetHighlight (HighlightStyle style) return args.Cancel; } - /// Called when a mouse event occurs within the view's . + /// Called when a mouse event occurs within the view's . /// /// - /// The coordinates are relative to . + /// The coordinates are relative to . /// /// /// @@ -546,7 +552,7 @@ protected internal virtual bool OnMouseEvent (MouseEvent mouseEvent) /// Event fired when a mouse event occurs. /// /// - /// The coordinates are relative to . + /// The coordinates are relative to . /// /// public event EventHandler MouseEvent; @@ -564,9 +570,7 @@ protected bool OnMouseClick (MouseEventEventArgs args) if (!Enabled) { // QUESTION: Is this right? Should a disabled view eat mouse clicks? - args.Handled = true; - - return true; + return args.Handled = true; } MouseClick?.Invoke (this, args); @@ -592,7 +596,7 @@ protected bool OnMouseClick (MouseEventEventArgs args) /// to see which button was clicked. /// /// - /// The coordinates are relative to . + /// The coordinates are relative to . /// /// public event EventHandler MouseClick; diff --git a/Terminal.Gui/View/ViewSubViews.cs b/Terminal.Gui/View/ViewSubViews.cs index 27570da2ca..9df4fee4d7 100644 --- a/Terminal.Gui/View/ViewSubViews.cs +++ b/Terminal.Gui/View/ViewSubViews.cs @@ -234,7 +234,15 @@ public virtual void Remove (View view) } } - /// Removes all subviews (children) added via or from this View. + /// + /// Removes all subviews (children) added via or from this View. + /// + /// + /// + /// Normally Subviews will be disposed when this View is disposed. Removing a Subview causes ownership of the Subview's + /// lifecycle to be transferred to the caller; the caller must call on any Views that were added. + /// + /// public virtual void RemoveAll () { if (_subviews is null) @@ -485,7 +493,7 @@ public virtual bool OnEnter (View view) { return true; } - + return false; } @@ -855,16 +863,13 @@ public virtual void PositionCursor () return; } - // BUGBUG: v2 - This needs to support children of Frames too + // BUGBUG: v2 - This needs to support Subviews of Adornments too if (Focused is null && SuperView is { }) { SuperView.EnsureFocus (); } - else if (Focused?.Visible == true - && Focused?.Enabled == true - && Focused?.Frame.Width > 0 - && Focused.Frame.Height > 0) + else if (Focused is { Visible: true, Enabled: true, Frame: { Width: > 0, Height: > 0 } }) { Focused.PositionCursor (); } diff --git a/Terminal.Gui/View/ViewText.cs b/Terminal.Gui/View/ViewText.cs index 598e2c76be..96c33e56e6 100644 --- a/Terminal.Gui/View/ViewText.cs +++ b/Terminal.Gui/View/ViewText.cs @@ -30,10 +30,10 @@ public virtual bool PreserveTrailingSpaces /// and . /// /// - /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height + /// The text will word-wrap to additional lines if it does not fit horizontally. If 's height /// is 1, the text will be clipped. /// - /// If is true, the will be adjusted to fit the text. + /// If is true, the will be adjusted to fit the text. /// When the text changes, the is fired. /// public virtual string Text @@ -80,7 +80,7 @@ public void OnTextChanged (string oldValue, string newValue) /// redisplay the . /// /// - /// If is true, the will be adjusted to fit the text. + /// If is true, the will be adjusted to fit the text. /// /// The text alignment. public virtual TextAlignment TextAlignment @@ -99,7 +99,7 @@ public virtual TextAlignment TextAlignment /// . /// /// - /// If is true, the will be adjusted to fit the text. + /// If is true, the will be adjusted to fit the text. /// /// The text alignment. public virtual TextDirection TextDirection @@ -120,7 +120,7 @@ public virtual TextDirection TextDirection /// redisplay the . /// /// - /// If is true, the will be adjusted to fit the text. + /// If is true, the will be adjusted to fit the text. /// /// The text alignment. public virtual VerticalTextAlignment VerticalTextAlignment @@ -134,7 +134,7 @@ public virtual VerticalTextAlignment VerticalTextAlignment } /// - /// Gets the Frame dimensions required to fit within using the text + /// Gets the Frame dimensions required to fit within using the text /// specified by the property and accounting for any /// characters. /// @@ -146,8 +146,8 @@ public Size GetAutoSize () if (IsInitialized) { - x = Bounds.X; - y = Bounds.Y; + x = Viewport.X; + y = Viewport.Y; } Rectangle rect = TextFormatter.CalcRect (x, y, TextFormatter.Text, TextFormatter.Direction); @@ -225,7 +225,7 @@ internal Size GetSizeNeededForTextWithoutHotKey () } /// - /// Internal API. Sets .Size to the current size, adjusted for + /// Internal API. Sets .Size to the current size, adjusted for /// . /// /// @@ -244,14 +244,14 @@ internal void SetTextFormatterSize () if (string.IsNullOrEmpty (TextFormatter.Text)) { - TextFormatter.Size = Bounds.Size; + TextFormatter.Size = ContentSize; return; } TextFormatter.Size = new ( - Bounds.Size.Width + GetHotKeySpecifierLength (), - Bounds.Size.Height + GetHotKeySpecifierLength (false) + ContentSize.Width + GetHotKeySpecifierLength (), + ContentSize.Height + GetHotKeySpecifierLength (false) ); } @@ -304,12 +304,12 @@ private bool SetFrameToFitText () throw new InvalidOperationException ("SetFrameToFitText can only be called when AutoSize is true"); } - // BUGBUG: This API is broken - should not assume Frame.Height == Bounds.Height + // BUGBUG: This API is broken - should not assume Frame.Height == Viewport.Height // // Gets the minimum dimensions required to fit the View's , factoring in . // // The minimum dimensions required. - // if the dimensions fit within the View's , otherwise. + // if the dimensions fit within the View's , otherwise. // // Always returns if is or // if (Horizontal) or (Vertical) are not not set or zero. @@ -324,7 +324,7 @@ bool GetMinimumSizeOfText (out Size sizeRequired) return false; } - sizeRequired = Bounds.Size; + sizeRequired = ContentSize; if (AutoSize || string.IsNullOrEmpty (TextFormatter.Text)) { @@ -336,11 +336,11 @@ bool GetMinimumSizeOfText (out Size sizeRequired) case true: int colWidth = TextFormatter.GetWidestLineLength (new List { TextFormatter.Text }, 0, 1); - // TODO: v2 - This uses frame.Width; it should only use Bounds + // TODO: v2 - This uses frame.Width; it should only use Viewport if (_frame.Width < colWidth - && (Width is null || (Bounds.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, Bounds.Height); + sizeRequired = new (colWidth, ContentSize.Height); return true; } @@ -349,7 +349,7 @@ bool GetMinimumSizeOfText (out Size sizeRequired) default: if (_frame.Height < 1 && (Height is null || (Height is Dim.DimAbsolute && Height.Anchor (0) == 0))) { - sizeRequired = new (Bounds.Width, 1); + sizeRequired = new (ContentSize.Width, 1); return true; } @@ -365,7 +365,7 @@ bool GetMinimumSizeOfText (out Size sizeRequired) // TODO: This is a hack. //_width = size.Width; //_height = size.Height; - _frame = new (_frame.Location, size); + SetFrame (new (_frame.Location, size)); //throw new InvalidOperationException ("This is a hack."); return true; @@ -392,7 +392,7 @@ private void UpdateTextDirection (TextDirection newDirection) } else if (AutoSize && directionChanged && IsAdded) { - ResizeBoundsToFit (Bounds.Size); + ResizeViewportToFit (Viewport.Size); } SetTextFormatterSize (); diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index a710bc2d92..53916fa178 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -33,9 +33,6 @@ public class Button : View private readonly Rune _rightDefault; private bool _isDefault; - /// - private bool _wantContinuousButtonPressed; - /// Initializes a new instance of using layout. /// The width of the is computed based on the text length. The height will always be 1. public Button () @@ -59,10 +56,10 @@ public Button () #endif // Override default behavior of View AddCommand (Command.HotKey, () => - { - SetFocus (); - return !OnAccept (); - }); + { + SetFocus (); + return !OnAccept (); + }); KeyBindings.Add (Key.Space, Command.HotKey); KeyBindings.Add (Key.Enter, Command.HotKey); @@ -71,6 +68,8 @@ public Button () MouseClick += Button_MouseClick; } + private bool _wantContinuousButtonPressed; + /// public override bool WantContinuousButtonPressed { @@ -97,7 +96,7 @@ public override bool WantContinuousButtonPressed private void Button_MouseClick (object sender, MouseEventEventArgs e) { - e.Handled = InvokeCommand (Command.HotKey) == true; + e.Handled = InvokeCommand (Command.HotKey) == true; } private void Button_TitleChanged (object sender, StateEventArgs e) @@ -189,4 +188,4 @@ protected override void UpdateTextFormatterText () } } } -} +} \ No newline at end of file diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs index ece096699d..f9f492874a 100644 --- a/Terminal.Gui/Views/ColorPicker.cs +++ b/Terminal.Gui/Views/ColorPicker.cs @@ -43,24 +43,11 @@ private void SetInitialProperties () Width = _cols * BoxWidth + thickness.Vertical; Height = _rows * BoxHeight + thickness.Horizontal; }; -// MouseEvent += ColorPicker_MouseEvent; + MouseClick += ColorPicker_MouseClick; } // TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor. - //private void ColorPicker_MouseEvent (object sender, MouseEventEventArgs me) - //{ - // if (me.MouseEvent.X > Bounds.Width || me.MouseEvent.Y > Bounds.Height) - // { - // me.Handled = true; - - // return; - // } - - // me.Handled = true; - - // return; - //} private void ColorPicker_MouseClick (object sender, MouseEventEventArgs me) { @@ -181,16 +168,16 @@ public virtual bool MoveDown () } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); Driver.SetAttribute (HasFocus ? ColorScheme.Focus : GetNormalColor ()); var colorIndex = 0; - for (var y = 0; y < Bounds.Height / BoxHeight; y++) + for (var y = 0; y < Viewport.Height / BoxHeight; y++) { - for (var x = 0; x < Bounds.Width / BoxWidth; x++) + for (var x = 0; x < Viewport.Width / BoxWidth; x++) { int foregroundColorIndex = y == 0 ? colorIndex + _cols : colorIndex - _cols; Driver.SetAttribute (new Attribute ((ColorName)foregroundColorIndex, (ColorName)colorIndex)); diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs index b3c719b5ef..5f1d25a9ee 100644 --- a/Terminal.Gui/Views/ComboBox.cs +++ b/Terminal.Gui/Views/ComboBox.cs @@ -245,8 +245,8 @@ public virtual bool Expand () /// protected internal override bool OnMouseEvent (MouseEvent me) { - if (me.X == Bounds.Right - 1 - && me.Y == Bounds.Top + if (me.X == Viewport.Right - 1 + && me.Y == Viewport.Top && me.Flags == MouseFlags.Button1Pressed && _autoHide) { @@ -284,9 +284,9 @@ protected internal override bool OnMouseEvent (MouseEvent me) public virtual void OnCollapsed () { Collapsed?.Invoke (this, EventArgs.Empty); } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); if (!_autoHide) { @@ -294,7 +294,7 @@ public override void OnDrawContent (Rectangle contentArea) } Driver.SetAttribute (ColorScheme.Focus); - Move (Bounds.Right - 1, 0); + Move (Viewport.Right - 1, 0); Driver.AddRune (Glyphs.DownArrow); } @@ -401,15 +401,15 @@ private bool ActivateSelected () /// private int CalculatetHeight () { - if (!IsInitialized || Bounds.Height == 0) + if (!IsInitialized || Viewport.Height == 0) { return 0; } return Math.Min ( - Math.Max (Bounds.Height - 1, _minimumHeight - 1), + Math.Max (Viewport.Height - 1, _minimumHeight - 1), _searchset?.Count > 0 ? _searchset.Count : - IsShow ? Math.Max (Bounds.Height - 1, _minimumHeight - 1) : 0 + IsShow ? Math.Max (Viewport.Height - 1, _minimumHeight - 1) : 0 ); } @@ -491,10 +491,10 @@ private void HideList () } Reset (true); - _listview.Clear (_listview.IsInitialized ? _listview.Bounds : Rectangle.Empty); + _listview.Clear (); _listview.TabStop = false; SuperView?.SendSubviewToBack (this); - Rectangle rect = _listview.BoundsToScreen (_listview.IsInitialized ? _listview.Bounds : Rectangle.Empty); + Rectangle rect = _listview.ViewportToScreen (_listview.IsInitialized ? _listview.Viewport : Rectangle.Empty); SuperView?.SetNeedsDisplay (rect); OnCollapsed (); } @@ -607,18 +607,18 @@ private bool PageUp () private void ProcessLayout () { - if (Bounds.Height < _minimumHeight && (Height is null || Height is Dim.DimAbsolute)) + if (Viewport.Height < _minimumHeight && (Height is null || Height is Dim.DimAbsolute)) { Height = _minimumHeight; } - if ((!_autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width) - || (_autoHide && Bounds.Width > 0 && _search.Frame.Width != Bounds.Width - 1)) + if ((!_autoHide && Viewport.Width > 0 && _search.Frame.Width != Viewport.Width) + || (_autoHide && Viewport.Width > 0 && _search.Frame.Width != Viewport.Width - 1)) { - _search.Width = _listview.Width = _autoHide ? Bounds.Width - 1 : Bounds.Width; + _search.Width = _listview.Width = _autoHide ? Viewport.Width - 1 : Viewport.Width; _listview.Height = CalculatetHeight (); - _search.SetRelativeLayout (Bounds); - _listview.SetRelativeLayout (Bounds); + _search.SetRelativeLayout (ContentSize); + _listview.SetRelativeLayout (ContentSize); } } @@ -761,7 +761,7 @@ private void SetValue (object text, bool isFromSelectedItem = false) private void ShowList () { _listview.SetSource (_searchset); - _listview.Clear (Bounds); // Ensure list shrinks in Dialog as you type + _listview.Clear (); _listview.Height = CalculatetHeight (); SuperView?.BringSubviewToFront (this); } @@ -839,7 +839,7 @@ protected internal override bool OnMouseEvent (MouseEvent me) return res; } - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { Attribute current = ColorScheme.Focus; Driver.SetAttribute (current); diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs index b22480ccc5..46787265e2 100644 --- a/Terminal.Gui/Views/Dialog.cs +++ b/Terminal.Gui/Views/Dialog.cs @@ -56,6 +56,7 @@ public enum ButtonAlignments /// public Dialog () { + Arrangement = ViewArrangement.Movable; X = Pos.Center (); Y = Pos.Center (); ValidatePosDim = true; @@ -201,7 +202,7 @@ private void LayoutButtons () { case ButtonAlignments.Center: // Center Buttons - shiftLeft = (Bounds.Width - buttonsWidth - _buttons.Count - 1) / 2 + 1; + shiftLeft = (Viewport.Width - buttonsWidth - _buttons.Count - 1) / 2 + 1; for (int i = _buttons.Count - 1; i >= 0; i--) { @@ -214,7 +215,7 @@ private void LayoutButtons () } else { - button.X = Bounds.Width - shiftLeft; + button.X = Viewport.Width - shiftLeft; } button.Y = Pos.AnchorEnd (1); @@ -226,7 +227,7 @@ private void LayoutButtons () // Justify Buttons // leftmost and rightmost buttons are hard against edges. The rest are evenly spaced. - var spacing = (int)Math.Ceiling ((double)(Bounds.Width - buttonsWidth) / (_buttons.Count - 1)); + var spacing = (int)Math.Ceiling ((double)(Viewport.Width - buttonsWidth) / (_buttons.Count - 1)); for (int i = _buttons.Count - 1; i >= 0; i--) { @@ -242,7 +243,7 @@ private void LayoutButtons () if (i == 0) { // first (leftmost) button - int left = Bounds.Width; + int left = Viewport.Width; button.X = Pos.AnchorEnd (left); } else diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs index ec29e69ba7..0e4f95c2c6 100644 --- a/Terminal.Gui/Views/FileDialog.cs +++ b/Terminal.Gui/Views/FileDialog.cs @@ -410,23 +410,23 @@ public bool IsCompatibleWithAllowedExtensions (IFileInfo file) } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); if (!string.IsNullOrWhiteSpace (_feedback)) { int feedbackWidth = _feedback.EnumerateRunes ().Sum (c => c.GetColumns ()); - int feedbackPadLeft = (Bounds.Width - feedbackWidth) / 2 - 1; + int feedbackPadLeft = (Viewport.Width - feedbackWidth) / 2 - 1; - feedbackPadLeft = Math.Min (Bounds.Width, feedbackPadLeft); + feedbackPadLeft = Math.Min (Viewport.Width, feedbackPadLeft); feedbackPadLeft = Math.Max (0, feedbackPadLeft); - int feedbackPadRight = Bounds.Width - (feedbackPadLeft + feedbackWidth + 2); - feedbackPadRight = Math.Min (Bounds.Width, feedbackPadRight); + int feedbackPadRight = Viewport.Width - (feedbackPadLeft + feedbackWidth + 2); + feedbackPadRight = Math.Min (Viewport.Width, feedbackPadRight); feedbackPadRight = Math.Max (0, feedbackPadRight); - Move (0, Bounds.Height / 2); + Move (0, Viewport.Height / 2); Driver.SetAttribute (new Attribute (Color.Red, ColorScheme.Normal.Background)); Driver.AddStr (new string (' ', feedbackPadLeft)); @@ -519,7 +519,7 @@ public override void OnLoaded () _allowedTypeMenuBar.DrawContentComplete += (s, e) => { - _allowedTypeMenuBar.Move (e.Rectangle.Width - 1, 0); + _allowedTypeMenuBar.Move (e.NewViewport.Width - 1, 0); Driver.AddRune (Glyphs.DownArrow); }; @@ -776,12 +776,12 @@ private int CalculateOkButtonPosX () return 0; } - return Bounds.Width - - _btnOk.Bounds.Width - - _btnCancel.Bounds.Width + return Viewport.Width + - _btnOk.Viewport.Width + - _btnCancel.Viewport.Width - 1 - // TODO: Fiddle factor, seems the Bounds are wrong for someone + // TODO: Fiddle factor, seems the Viewport are wrong for someone - 2; } diff --git a/Terminal.Gui/Views/GraphView/Annotations.cs b/Terminal.Gui/Views/GraphView/Annotations.cs index 1a77a49496..30247d4a69 100644 --- a/Terminal.Gui/Views/GraphView/Annotations.cs +++ b/Terminal.Gui/Views/GraphView/Annotations.cs @@ -18,7 +18,7 @@ public interface IAnnotation /// /// Called once after series have been rendered (or before if is true). Use - /// to draw and to avoid drawing outside of graph + /// to draw and to avoid drawing outside of graph /// /// void Render (GraphView graph); @@ -62,7 +62,7 @@ public void Render (GraphView graph) /// /// Draws the at the given coordinates with truncation to avoid spilling over - /// of the + /// of the /// /// /// Screen x position to start drawing string @@ -70,7 +70,7 @@ public void Render (GraphView graph) protected void DrawText (GraphView graph, int x, int y) { // the draw point is out of control bounds - if (!graph.Bounds.Contains (new Point (x, y))) + if (!graph.Viewport.Contains (new Point (x, y))) { return; } @@ -83,7 +83,7 @@ protected void DrawText (GraphView graph, int x, int y) graph.Move (x, y); - int availableWidth = graph.Bounds.Width - x; + int availableWidth = graph.Viewport.Width - x; if (availableWidth <= 0) { @@ -127,7 +127,7 @@ public LegendAnnotation (Rectangle legendBounds) /// Returns false i.e. Legends render after series public bool BeforeSeries => false; - /// Draws the Legend and all entries into the area within + /// Draws the Legend and all entries into the area within /// public void Render (GraphView graph) { @@ -165,13 +165,13 @@ public void Render (GraphView graph) // add the text Move (1, linesDrawn); - string str = TextFormatter.ClipOrPad (entry.Item2, Bounds.Width - 1); + string str = TextFormatter.ClipOrPad (entry.Item2, Viewport.Width - 1); Application.Driver.AddStr (str); linesDrawn++; // Legend has run out of space - if (linesDrawn >= Bounds.Height) + if (linesDrawn >= Viewport.Height) { break; } @@ -182,7 +182,7 @@ public void Render (GraphView graph) /// The symbol appearing on the graph that should appear in the legend /// /// Text to render on this line of the legend. Will be truncated if outside of Legend - /// + /// /// public void AddEntry (GraphCellToRender graphCellToRender, string text) { _entries.Add (Tuple.Create (graphCellToRender, text)); } } diff --git a/Terminal.Gui/Views/GraphView/Axis.cs b/Terminal.Gui/Views/GraphView/Axis.cs index 8d0389b371..7e7561b76d 100644 --- a/Terminal.Gui/Views/GraphView/Axis.cs +++ b/Terminal.Gui/Views/GraphView/Axis.cs @@ -113,7 +113,7 @@ public override void DrawAxisLabel (GraphView graph, int screenPosition, string string toRender = text; // this is how much space is left - int xSpaceAvailable = graph.Bounds.Width - drawAtX; + int xSpaceAvailable = graph.Viewport.Width - drawAtX; // There is no space for the label at all! if (xSpaceAvailable <= 0) @@ -127,7 +127,7 @@ public override void DrawAxisLabel (GraphView graph, int screenPosition, string toRender = toRender.Substring (0, xSpaceAvailable); } - graph.Move (drawAtX, Math.Min (y + 1, graph.Bounds.Height - 1)); + graph.Move (drawAtX, Math.Min (y + 1, graph.Viewport.Height - 1)); driver.AddStr (toRender); } } @@ -140,9 +140,9 @@ public override void DrawAxisLabels (GraphView graph) return; } - Rectangle bounds = graph.Bounds; + Rectangle viewport = graph.Viewport; - IEnumerable labels = GetLabels (graph, bounds); + IEnumerable labels = GetLabels (graph, viewport); foreach (AxisIncrementToRender label in labels) { @@ -155,12 +155,12 @@ public override void DrawAxisLabels (GraphView graph) string toRender = Text; // if label is too long - if (toRender.Length > graph.Bounds.Width) + if (toRender.Length > graph.Viewport.Width) { - toRender = toRender.Substring (0, graph.Bounds.Width); + toRender = toRender.Substring (0, graph.Viewport.Width); } - graph.Move (graph.Bounds.Width / 2 - toRender.Length / 2, graph.Bounds.Height - 1); + graph.Move (graph.Viewport.Width / 2 - toRender.Length / 2, graph.Viewport.Height - 1); Application.Driver.AddStr (toRender); } } @@ -174,7 +174,7 @@ public override void DrawAxisLine (GraphView graph) return; } - Rectangle bounds = graph.Bounds; + Rectangle bounds = graph.Viewport; graph.Move (0, 0); @@ -212,7 +212,7 @@ public int GetAxisYPosition (GraphView graph) // float the X axis so that it accurately represents the origin of the graph // but anchor it to top/bottom if the origin is offscreen - return Math.Min (Math.Max (0, origin.Y), graph.Bounds.Height - ((int)graph.MarginBottom + 1)); + return Math.Min (Math.Max (0, origin.Y), graph.Viewport.Height - ((int)graph.MarginBottom + 1)); } /// Draws a horizontal axis line at the given , screen coordinates @@ -225,7 +225,7 @@ protected override void DrawAxisLine (GraphView graph, int x, int y) Application.Driver.AddRune (Glyphs.HLine); } - private IEnumerable GetLabels (GraphView graph, Rectangle bounds) + private IEnumerable GetLabels (GraphView graph, Rectangle viewport) { // if no labels if (Increment == 0) @@ -237,7 +237,7 @@ private IEnumerable GetLabels (GraphView graph, Rectangle int y = GetAxisYPosition (graph); RectangleF start = graph.ScreenToGraphSpace ((int)graph.MarginLeft, y); - RectangleF end = graph.ScreenToGraphSpace (bounds.Width, y); + RectangleF end = graph.ScreenToGraphSpace (viewport.Width, y); // don't draw labels below the minimum if (Minimum.HasValue) @@ -266,7 +266,7 @@ private IEnumerable GetLabels (GraphView graph, Rectangle ; } - // Label or no label definetly render it + // Label or no label definitely render it yield return toRender; current.X += Increment; @@ -317,7 +317,7 @@ public override void DrawAxisLabels (GraphView graph) return; } - Rectangle bounds = graph.Bounds; + Rectangle bounds = graph.Viewport; IEnumerable labels = GetLabels (graph, bounds); foreach (AxisIncrementToRender label in labels) @@ -331,13 +331,13 @@ public override void DrawAxisLabels (GraphView graph) string toRender = Text; // if label is too long - if (toRender.Length > graph.Bounds.Height) + if (toRender.Length > graph.Viewport.Height) { - toRender = toRender.Substring (0, graph.Bounds.Height); + toRender = toRender.Substring (0, graph.Viewport.Height); } // Draw it 1 letter at a time vertically down row 0 of the control - int startDrawingAtY = graph.Bounds.Height / 2 - toRender.Length / 2; + int startDrawingAtY = graph.Viewport.Height / 2 - toRender.Length / 2; for (var i = 0; i < toRender.Length; i++) { @@ -356,7 +356,7 @@ public override void DrawAxisLine (GraphView graph) return; } - Rectangle bounds = graph.Bounds; + Rectangle bounds = graph.Viewport; int x = GetAxisXPosition (graph); @@ -385,7 +385,7 @@ public int GetAxisXPosition (GraphView graph) // float the Y axis so that it accurately represents the origin of the graph // but anchor it to left/right if the origin is offscreen - return Math.Min (Math.Max ((int)graph.MarginLeft, origin.X), graph.Bounds.Width - 1); + return Math.Min (Math.Max ((int)graph.MarginLeft, origin.X), graph.Viewport.Width - 1); } /// Draws a vertical axis line at the given , screen coordinates @@ -409,7 +409,7 @@ private int GetAxisYEnd (GraphView graph) return graph.GraphSpaceToScreen (new PointF (0, Minimum.Value)).Y; } - return graph.Bounds.Height; + return graph.Viewport.Height; } private IEnumerable GetLabels (GraphView graph, Rectangle bounds) diff --git a/Terminal.Gui/Views/GraphView/GraphView.cs b/Terminal.Gui/Views/GraphView/GraphView.cs index 30b902286b..6f4f05bf16 100644 --- a/Terminal.Gui/Views/GraphView/GraphView.cs +++ b/Terminal.Gui/Views/GraphView/GraphView.cs @@ -192,12 +192,12 @@ public Point GraphSpaceToScreen (PointF location) (int)((location.X - ScrollOffset.X) / CellSize.X) + (int)MarginLeft, // screen coordinates are top down while graph coordinates are bottom up - Bounds.Height - 1 - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y) + Viewport.Height - 1 - (int)MarginBottom - (int)((location.Y - ScrollOffset.Y) / CellSize.Y) ); } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (CellSize.X == 0 || CellSize.Y == 0) { @@ -209,10 +209,10 @@ public override void OnDrawContent (Rectangle contentArea) Move (0, 0); // clear all old content - for (var i = 0; i < Bounds.Height; i++) + for (var i = 0; i < Viewport.Height; i++) { Move (0, i); - Driver.AddStr (new string (' ', Bounds.Width)); + Driver.AddStr (new string (' ', Viewport.Width)); } // If there is no data do not display a graph @@ -222,8 +222,8 @@ public override void OnDrawContent (Rectangle contentArea) } // The drawable area of the graph (anything that isn't in the margins) - int graphScreenWidth = Bounds.Width - (int)MarginLeft; - int graphScreenHeight = Bounds.Height - (int)MarginBottom; + int graphScreenWidth = Viewport.Width - (int)MarginLeft; + int graphScreenHeight = Viewport.Height - (int)MarginBottom; // if the margins take up the full draw bounds don't render if (graphScreenWidth < 0 || graphScreenHeight < 0) @@ -287,10 +287,10 @@ public override bool OnEnter (View view) } /// Scrolls the graph down 1 page. - public void PageDown () { Scroll (0, -1 * CellSize.Y * Bounds.Height); } + public void PageDown () { Scroll (0, -1 * CellSize.Y * Viewport.Height); } /// Scrolls the graph up 1 page. - public void PageUp () { Scroll (0, CellSize.Y * Bounds.Height); } + public void PageUp () { Scroll (0, CellSize.Y * Viewport.Height); } /// /// Clears all settings configured on the graph and resets all properties to default values ( @@ -316,7 +316,7 @@ public RectangleF ScreenToGraphSpace (int col, int row) { return new ( ScrollOffset.X + (col - MarginLeft) * CellSize.X, - ScrollOffset.Y + (Bounds.Height - (row + MarginBottom + 1)) * CellSize.Y, + ScrollOffset.Y + (Viewport.Height - (row + MarginBottom + 1)) * CellSize.Y, CellSize.X, CellSize.Y ); diff --git a/Terminal.Gui/Views/GraphView/Series.cs b/Terminal.Gui/Views/GraphView/Series.cs index 673419b161..c117bd0a24 100644 --- a/Terminal.Gui/Views/GraphView/Series.cs +++ b/Terminal.Gui/Views/GraphView/Series.cs @@ -192,7 +192,7 @@ public virtual void DrawSeries (GraphView graph, Rectangle drawBounds, Rectangle screenStart.X = graph.AxisY.GetAxisXPosition (graph); // dont draw bar off the right of the control - screenEnd.X = Math.Min (graph.Bounds.Width - 1, screenEnd.X); + screenEnd.X = Math.Min (graph.Viewport.Width - 1, screenEnd.X); // if bar is off the screen if (screenStart.Y < 0 || screenStart.Y > drawBounds.Height - graph.MarginBottom) diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index a9a7f0aad8..2d166db3c2 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -264,7 +264,7 @@ public void ApplyEdits (Stream stream = null) /// protected internal override bool OnMouseEvent (MouseEvent me) { - // BUGBUG: Test this with a border! Assumes Frame == Bounds! + // BUGBUG: Test this with a border! Assumes Frame == Viewport! if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) @@ -351,14 +351,14 @@ protected internal override bool OnMouseEvent (MouseEvent me) } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { Attribute currentAttribute; Attribute current = ColorScheme.Focus; Driver.SetAttribute (current); Move (0, 0); - // BUGBUG: Bounds!!!! + // BUGBUG: Viewport!!!! Rectangle frame = Frame; int nblocks = bytesPerLine / bsize; @@ -373,7 +373,7 @@ public override void OnDrawContent (Rectangle contentArea) { Rectangle lineRect = new (0, line, frame.Width, 1); - if (!Bounds.Contains (lineRect)) + if (!Viewport.Contains (lineRect)) { continue; } @@ -611,15 +611,15 @@ private void HexView_LayoutComplete (object sender, LayoutEventArgs e) // Small buffers will just show the position, with the bsize field value (4 bytes) bytesPerLine = bsize; - if (Bounds.Width - displayWidth > 17) + if (Viewport.Width - displayWidth > 17) { - bytesPerLine = bsize * ((Bounds.Width - displayWidth) / 18); + bytesPerLine = bsize * ((Viewport.Width - displayWidth) / 18); } } private bool MoveDown (int bytes) { - // BUGBUG: Bounds! + // BUGBUG: Viewport! RedisplayLine (position); if (position + bytes < source.Length) @@ -657,7 +657,7 @@ private bool MoveEnd () { position = source.Length; - // BUGBUG: Bounds! + // BUGBUG: Viewport! if (position >= DisplayStart + bytesPerLine * Frame.Height) { SetDisplayStart (position); @@ -744,7 +744,7 @@ private bool MoveRight () position++; } - // BUGBUG: Bounds! + // BUGBUG: Viewport! if (position >= DisplayStart + bytesPerLine * Frame.Height) { SetDisplayStart (DisplayStart + bytesPerLine); @@ -793,7 +793,7 @@ private void RedisplayLine (long pos) var delta = (int)(pos - DisplayStart); int line = delta / bytesPerLine; - // BUGBUG: Bounds! + // BUGBUG: Viewport! SetNeedsDisplay (new (0, line, Frame.Width, 1)); } diff --git a/Terminal.Gui/Views/Line.cs b/Terminal.Gui/Views/Line.cs index 9fe92ae7a7..8ef2940615 100644 --- a/Terminal.Gui/Views/Line.cs +++ b/Terminal.Gui/Views/Line.cs @@ -17,7 +17,7 @@ public Line () public Orientation Orientation { get; set; } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { LineCanvas lc = LineCanvas; @@ -26,7 +26,7 @@ public override void OnDrawContent (Rectangle contentArea) lc = adornment.Parent.LineCanvas; } lc.AddLine ( - BoundsToScreen (contentArea).Location, + ViewportToScreen (viewport).Location, Orientation == Orientation.Horizontal ? Frame.Width : Frame.Height, Orientation, BorderStyle diff --git a/Terminal.Gui/Views/LineView.cs b/Terminal.Gui/Views/LineView.cs index 2f43411139..a362cca10d 100644 --- a/Terminal.Gui/Views/LineView.cs +++ b/Terminal.Gui/Views/LineView.cs @@ -54,16 +54,16 @@ public LineView (Orientation orientation) public Rune? StartingAnchor { get; set; } /// Draws the line including any starting/ending anchors - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); Move (0, 0); Driver.SetAttribute (GetNormalColor ()); int hLineWidth = Math.Max (1, Glyphs.HLine.GetColumns ()); - int dEnd = Orientation == Orientation.Horizontal ? Bounds.Width : Bounds.Height; + int dEnd = Orientation == Orientation.Horizontal ? Viewport.Width : Viewport.Height; for (var d = 0; d < dEnd; d += hLineWidth) { diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index f1de20abc1..0fb229afec 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; @@ -94,7 +95,10 @@ public class ListView : View private int _lastSelectedItem = -1; private int _selected = -1; private IListDataSource _source; - private int _top, _left; + // TODO: ListView has been upgraded to use Viewport and ContentSize instead of the + // TODO: bespoke _top and _left. It was a quick & dirty port. There is now duplicate logic + // TODO: that could be removed. + //private int _top, _left; /// /// Initializes a new instance of . Set the property to display @@ -107,8 +111,8 @@ public ListView () // 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 ()); @@ -117,6 +121,9 @@ public ListView () 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); @@ -199,7 +206,7 @@ public bool AllowsMultipleSelection /// The left position. public int LeftItem { - get => _left; + get => Viewport.X; set { if (_source is null) @@ -212,7 +219,7 @@ public int LeftItem throw new ArgumentException ("value"); } - _left = value; + Viewport = Viewport with { X = value }; SetNeedsDisplay (); } } @@ -250,20 +257,34 @@ public IListDataSource Source get => _source; set { + if (_source == value) + + { + return; + } _source = value; + + ContentSize = new Size (_source?.Length ?? Viewport.Width, _source?.Count ?? Viewport.Width); + if (IsInitialized) + { + Viewport = Viewport with { Y = 0 }; + } + KeystrokeNavigator.Collection = _source?.ToList (); - _top = 0; _selected = -1; _lastSelectedItem = -1; SetNeedsDisplay (); } } - /// Gets or sets the item that is displayed at the top of the . + /// Gets or sets the index of the item that will appear at the top of the . + /// + /// This a helper property for accessing listView.Viewport.Y. + /// /// The top item. public int TopItem { - get => _top; + get => Viewport.Y; set { if (_source is null) @@ -271,13 +292,7 @@ public int TopItem return; } - if (value < 0 || (_source.Count > 0 && value >= _source.Count)) - { - throw new ArgumentException ("value"); - } - - _top = Math.Max (value, 0); - SetNeedsDisplay (); + Viewport = Viewport with { Y = value }; } } @@ -314,13 +329,14 @@ public void EnsureSelectedItemVisible () { if (SuperView?.IsInitialized == true) { - if (_selected < _top) + if (_selected < Viewport.Y) { - _top = Math.Max (_selected, 0); + // TODO: The Max check here is not needed because, by default, Viewport enforces staying w/in ContentArea (View.ScrollSettings). + Viewport = Viewport with { Y = _selected }; } - else if (Bounds.Height > 0 && _selected >= _top + Bounds.Height) + else if (Viewport.Height > 0 && _selected >= Viewport.Y + Viewport.Height) { - _top = Math.Max (_selected - Bounds.Height + 1, 0); + Viewport = Viewport with { Y = _selected - Viewport.Height + 1}; } LayoutStarted -= ListView_LayoutStarted; @@ -347,7 +363,7 @@ public virtual bool MarkUnmarkRow () } /// - protected internal override bool OnMouseEvent (MouseEvent me) + protected internal override bool OnMouseEvent (MouseEvent me) { if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) @@ -371,40 +387,40 @@ protected internal override bool OnMouseEvent (MouseEvent me) 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; } - if (me.Y + _top >= _source.Count - || me.Y + _top < 0 - || me.Y + _top > _top + Bounds.Height) + if (me.Y + Viewport.Y >= _source.Count + || me.Y + Viewport.Y < 0 + || me.Y + Viewport.Y > Viewport.Y + Viewport.Height) { return true; } - _selected = _top + me.Y; + _selected = Viewport.Y + me.Y; if (AllowsAll ()) { @@ -449,13 +465,13 @@ public virtual bool MoveDown () //can move by down by one. _selected++; - if (_selected >= _top + Bounds.Height) + if (_selected >= Viewport.Y + Viewport.Height) { - _top++; + Viewport = Viewport with { Y = Viewport.Y + 1 }; } - else if (_selected < _top) + else if (_selected < Viewport.Y) { - _top = Math.Max (_selected, 0); + Viewport = Viewport with { Y = _selected }; } OnSelectedChanged (); @@ -466,9 +482,9 @@ public virtual bool MoveDown () OnSelectedChanged (); SetNeedsDisplay (); } - else if (_selected >= _top + Bounds.Height) + else if (_selected >= Viewport.Y + Viewport.Height) { - _top = Math.Max (_source.Count - Bounds.Height, 0); + Viewport = Viewport with { Y = _source.Count - Viewport.Height }; SetNeedsDisplay (); } @@ -483,9 +499,9 @@ public virtual bool MoveEnd () { _selected = _source.Count - 1; - if (_top + _selected > Bounds.Height - 1) + if (Viewport.Y + _selected > Viewport.Height - 1) { - _top = Math.Max (_selected, 0); + Viewport = Viewport with { Y = _selected }; } OnSelectedChanged (); @@ -502,7 +518,7 @@ public virtual bool MoveHome () if (_selected != 0) { _selected = 0; - _top = Math.Max (_selected, 0); + Viewport = Viewport with { Y = _selected }; OnSelectedChanged (); SetNeedsDisplay (); } @@ -522,7 +538,7 @@ public virtual bool MovePageDown () return true; } - int n = _selected + Bounds.Height; + int n = _selected + Viewport.Height; if (n >= _source.Count) { @@ -533,13 +549,13 @@ public virtual bool MovePageDown () { _selected = n; - if (_source.Count >= Bounds.Height) + if (_source.Count >= Viewport.Height) { - _top = Math.Max (_selected, 0); + Viewport = Viewport with { Y = _selected }; } else { - _top = 0; + Viewport = Viewport with { Y = 0 }; } OnSelectedChanged (); @@ -553,7 +569,7 @@ public virtual bool MovePageDown () /// public virtual bool MovePageUp () { - int n = _selected - Bounds.Height; + int n = _selected - Viewport.Height; if (n < 0) { @@ -563,7 +579,7 @@ public virtual bool MovePageUp () if (n != _selected) { _selected = n; - _top = Math.Max (_selected, 0); + Viewport = Viewport with { Y = _selected }; OnSelectedChanged (); SetNeedsDisplay (); } @@ -599,21 +615,21 @@ public virtual bool MoveUp () _selected = Source.Count - 1; } - if (_selected < _top) + if (_selected < Viewport.Y) { - _top = Math.Max (_selected, 0); + Viewport = Viewport with { Y = _selected }; } - else if (_selected > _top + Bounds.Height) + else if (_selected > Viewport.Y + Viewport.Height) { - _top = Math.Max (_selected - Bounds.Height + 1, 0); + Viewport = Viewport with { Y = _selected - Viewport.Height + 1 }; } OnSelectedChanged (); SetNeedsDisplay (); } - else if (_selected < _top) + else if (_selected < Viewport.Y) { - _top = Math.Max (_selected, 0); + Viewport = Viewport with { Y = _selected }; SetNeedsDisplay (); } @@ -621,18 +637,18 @@ public virtual bool MoveUp () } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); Attribute current = ColorScheme.Focus; Driver.SetAttribute (current); Move (0, 0); - Rectangle f = Bounds; - int item = _top; + Rectangle f = Viewport; + int item = Viewport.Y; bool focused = HasFocus; int col = _allowsMarking ? 2 : 0; - int start = _left; + int start = Viewport.X; for (var row = 0; row < f.Height; row++, item++) { @@ -769,57 +785,17 @@ public override void PositionCursor () { if (_allowsMarking) { - Move (0, _selected - _top); + Move (0, _selected - Viewport.Y); } else { - Move (Bounds.Width - 1, _selected - _top); + Move (Viewport.Width - 1, _selected - Viewport.Y); } } /// 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) - { - _top = Math.Max (Math.Min (_top + items, _source.Count - 1), 0); - SetNeedsDisplay (); - - return true; - } - - /// Scrolls the view left. - /// Number of columns to scroll left. - public virtual bool ScrollLeft (int cols) - { - _left = Math.Max (_left - cols, 0); - SetNeedsDisplay (); - - return true; - } - - /// Scrolls the view right. - /// Number of columns to scroll right. - public virtual bool ScrollRight (int cols) - { - _left = Math.Max (Math.Min (_left + 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) - { - _top = Math.Max (_top - items, 0); - SetNeedsDisplay (); - - return true; - } - /// This event is raised when the selected item in the has changed. public event EventHandler SelectedItemChanged; diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs index ae080a298f..cc635de003 100644 --- a/Terminal.Gui/Views/Menu/ContextMenu.cs +++ b/Terminal.Gui/Views/Menu/ContextMenu.cs @@ -144,12 +144,12 @@ public void Show () _container = Application.Current; _container.Closing += Container_Closing; _container.Deactivate += Container_Deactivate; - Rectangle frame = Application.Driver.Bounds; + Rectangle frame = Application.Driver.Screen; Point position = Position; if (Host is { }) { - Point pos = Host.BoundsToScreen (frame).Location; + Point pos = Host.ViewportToScreen (frame).Location; pos.Y += Host.Frame.Height - 1; if (position != pos) @@ -186,7 +186,7 @@ public void Show () } else { - Point pos = Host.BoundsToScreen (frame).Location; + Point pos = Host.ViewportToScreen (frame).Location; position.Y = pos.Y - rect.Height - 1; } } diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs index 60f7c00bf3..62d847c659 100644 --- a/Terminal.Gui/Views/Menu/Menu.cs +++ b/Terminal.Gui/Views/Menu/Menu.cs @@ -726,7 +726,7 @@ private void Application_RootMouseEvent (object sender, MouseEvent a) View view = a.View ?? this; - Point boundsPoint = view.ScreenToBounds (a.X, a.Y); + Point boundsPoint = view.ScreenToViewport (a.X, a.Y); var me = new MouseEvent { X = boundsPoint.X, @@ -757,7 +757,7 @@ internal Attribute DetermineColorSchemeFor (MenuItem item, int index) return !item.IsEnabled () ? ColorScheme.Disabled : GetNormalColor (); } - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (_barItems.Children is null) { @@ -771,14 +771,14 @@ public override void OnDrawContent (Rectangle contentArea) OnDrawAdornments (); OnRenderLineCanvas (); - for (int i = Bounds.Y; i < _barItems.Children.Length; i++) + for (int i = Viewport.Y; i < _barItems.Children.Length; i++) { if (i < 0) { continue; } - if (BoundsToScreen (Bounds).Y + i >= Driver.Rows) + if (ViewportToScreen (Viewport).Y + i >= Driver.Rows) { break; } @@ -792,7 +792,8 @@ public override void OnDrawContent (Rectangle contentArea) if (item is null && BorderStyle != LineStyle.None) { - Move (-1, i); + var s = ViewportToScreen (new (-1, i, 0, 0)); + Driver.Move (s.X, s.Y); Driver.AddRune (Glyphs.LeftTee); } else if (Frame.X < Driver.Cols) @@ -802,7 +803,7 @@ public override void OnDrawContent (Rectangle contentArea) Driver.SetAttribute (DetermineColorSchemeFor (item, i)); - for (int p = Bounds.X; p < Frame.Width - 2; p++) + for (int p = Viewport.X; p < Frame.Width - 2; p++) { // This - 2 is for the border if (p < 0) @@ -810,7 +811,7 @@ public override void OnDrawContent (Rectangle contentArea) continue; } - if (BoundsToScreen (Bounds).X + p >= Driver.Cols) + if (ViewportToScreen (Viewport).X + p >= Driver.Cols) { break; } @@ -839,7 +840,8 @@ public override void OnDrawContent (Rectangle contentArea) { if (BorderStyle != LineStyle.None && SuperView?.Frame.Right - Frame.X > Frame.Width) { - Move (Frame.Width - 2, i); + var s = ViewportToScreen (new (Frame.Width - 2, i, 0, 0)); + Driver.Move (s.X, s.Y); Driver.AddRune (Glyphs.RightTee); } @@ -875,7 +877,7 @@ public override void OnDrawContent (Rectangle contentArea) textToDraw = item.Title; } - Rectangle screen = BoundsToScreen (new (new (0 , i), Size.Empty)); + Rectangle screen = ViewportToScreen (new (new (0 , i), Size.Empty)); if (screen.X < Driver.Cols) { Driver.Move (screen.X + 1, screen.Y); @@ -893,10 +895,10 @@ public override void OnDrawContent (Rectangle contentArea) // The -3 is left/right border + one space (not sure what for) tf.Draw ( - BoundsToScreen (new (1, i, Frame.Width - 3, 1)), + ViewportToScreen (new (1, i, Frame.Width - 3, 1)), i == _currentChild ? ColorScheme.Focus : GetNormalColor (), i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal, - SuperView?.BoundsToScreen (SuperView.Bounds) ?? Rectangle.Empty + SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty ); } else @@ -913,7 +915,7 @@ public override void OnDrawContent (Rectangle contentArea) ? item.Help.GetColumns () : item.Help.GetColumns () + item.ShortcutTag.GetColumns () + 2; int col = Frame.Width - l - 3; - screen = BoundsToScreen (new (new (col, i), Size.Empty)); + screen = ViewportToScreen (new (new (col, i), Size.Empty)); if (screen.X < Driver.Cols) { @@ -939,7 +941,7 @@ private void Current_DrawContentComplete (object sender, DrawEventArgs e) { if (Visible) { - OnDrawContent (Bounds); + OnDrawContent (Viewport); } } diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs index 07ff6912c3..805890d096 100644 --- a/Terminal.Gui/Views/Menu/MenuBar.cs +++ b/Terminal.Gui/Views/Menu/MenuBar.cs @@ -469,15 +469,11 @@ internal Menu openCurrentMenu public event EventHandler MenuOpening; /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - Move (0, 0); Driver.SetAttribute (GetNormalColor ()); - for (var i = 0; i < Frame.Width; i++) - { - Driver.AddRune ((Rune)' '); - } + Clear (); var pos = 0; @@ -824,13 +820,13 @@ internal Point GetScreenOffset () return Point.Empty; } - Rectangle superViewFrame = SuperView is null ? Driver.Bounds : SuperView.Frame; + Rectangle superViewFrame = SuperView is null ? Driver.Screen : SuperView.Frame; View sv = SuperView is null ? Application.Current : SuperView; - Point boundsOffset = sv.GetBoundsOffset (); + Point viewportOffset = sv.GetViewportOffsetFromFrame (); return new ( - superViewFrame.X - sv.Frame.X - boundsOffset.X, - superViewFrame.Y - sv.Frame.Y - boundsOffset.Y + superViewFrame.X - sv.Frame.X - viewportOffset.X, + superViewFrame.Y - sv.Frame.Y - viewportOffset.Y ); } @@ -841,11 +837,11 @@ internal Point GetScreenOffset () /// The location offset. internal Point GetScreenOffsetFromCurrent () { - Rectangle screen = Driver.Bounds; + Rectangle screen = Driver.Screen; Rectangle currentFrame = Application.Current.Frame; - Point boundsOffset = Application.Top.GetBoundsOffset (); + Point viewportOffset = Application.Top.GetViewportOffsetFromFrame (); - return new (screen.X - currentFrame.X - boundsOffset.X, screen.Y - currentFrame.Y - boundsOffset.Y); + return new (screen.X - currentFrame.X - viewportOffset.X, screen.Y - currentFrame.Y - viewportOffset.Y); } internal void NextMenu (bool isSubMenu = false, bool ignoreUseSubMenusSingleFrame = false) @@ -1319,7 +1315,7 @@ private void ProcessMenu (int i, MenuBarItem mi) if (mi.IsTopLevel) { - Rectangle screen = BoundsToScreen (new (new (0, i), Size.Empty)); + Rectangle screen = ViewportToScreen (new (new (0, i), Size.Empty)); var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi }; menu.Run (mi.Action); menu.Dispose (); @@ -1683,7 +1679,7 @@ protected internal override bool OnMouseEvent (MouseEvent me) { if (Menus [i].IsTopLevel) { - Rectangle screen = BoundsToScreen (new (new (0, i), Size.Empty)); + Rectangle screen = ViewportToScreen (new (new (0, i), Size.Empty)); var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] }; menu.Run (Menus [i].Action); menu.Dispose (); diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs index 9b00337d71..ef7a33d10b 100644 --- a/Terminal.Gui/Views/MessageBox.cs +++ b/Terminal.Gui/Views/MessageBox.cs @@ -422,7 +422,7 @@ void Dialog_Loaded (object s, EventArgs e) } // TODO: replace with Dim.Fit when implemented - Rectangle maxBounds = d.SuperView?.Bounds ?? Application.Top.Bounds; + Rectangle maxBounds = d.SuperView?.Viewport ?? Application.Top.Viewport; Thickness adornmentsThickness = d.GetAdornmentsThickness (); @@ -467,7 +467,7 @@ void Dialog_Loaded (object s, EventArgs e) + adornmentsThickness.Vertical); } - d.SetRelativeLayout (d.SuperView?.Frame ?? Application.Top.Frame); + d.SetRelativeLayout (d.SuperView?.ContentSize ?? Application.Top.ContentSize); d.LayoutSubviews (); } } diff --git a/Terminal.Gui/Views/ProgressBar.cs b/Terminal.Gui/Views/ProgressBar.cs index 189533ee60..cfddca1e00 100644 --- a/Terminal.Gui/Views/ProgressBar.cs +++ b/Terminal.Gui/Views/ProgressBar.cs @@ -143,7 +143,7 @@ public override string Text } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { Driver.SetAttribute (GetHotNormalColor ()); @@ -151,7 +151,7 @@ public override void OnDrawContent (Rectangle contentArea) if (_isActivity) { - for (var i = 0; i < Bounds.Width; i++) + for (var i = 0; i < Viewport.Width; i++) { if (Array.IndexOf (_activityPos, i) != -1) { @@ -165,15 +165,15 @@ public override void OnDrawContent (Rectangle contentArea) } else { - var mid = (int)(_fraction * Bounds.Width); + var mid = (int)(_fraction * Viewport.Width); int i; - for (i = 0; (i < mid) & (i < Bounds.Width); i++) + for (i = 0; (i < mid) & (i < Viewport.Width); i++) { Driver.AddRune (SegmentCharacter); } - for (; i < Bounds.Width; i++) + for (; i < Viewport.Width; i++) { Driver.AddRune ((Rune)' '); } @@ -190,10 +190,10 @@ public override void OnDrawContent (Rectangle contentArea) } tf?.Draw ( - BoundsToScreen (Bounds), + ViewportToScreen (Viewport), attr, ColorScheme.Normal, - SuperView?.BoundsToScreen (SuperView.Bounds) ?? default (Rectangle) + SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle) ); } } @@ -244,13 +244,13 @@ public void Pulse () _delta = 1; } - else if (_activityPos [0] >= Bounds.Width) + else if (_activityPos [0] >= Viewport.Width) { if (_bidirectionalMarquee) { for (var i = 0; i < _activityPos.Length; i++) { - _activityPos [i] = Bounds.Width + i - 2; + _activityPos [i] = Viewport.Width + i - 2; } _delta = -1; @@ -291,7 +291,7 @@ private void ProgressBar_LayoutStarted (object sender, EventArgs e) private void SetInitialProperties () { - Height = 1; // This will be updated when Bounds is updated in ProgressBar_LayoutStarted + Height = 1; // This will be updated when Viewport is updated in ProgressBar_LayoutStarted CanFocus = false; _fraction = 0; LayoutStarted += ProgressBar_LayoutStarted; diff --git a/Terminal.Gui/Views/RadioGroup.cs b/Terminal.Gui/Views/RadioGroup.cs index 6f10c3bc03..5c9d6b477c 100644 --- a/Terminal.Gui/Views/RadioGroup.cs +++ b/Terminal.Gui/Views/RadioGroup.cs @@ -196,9 +196,9 @@ public int SelectedItem } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); Driver.SetAttribute (GetNormalColor ()); diff --git a/Terminal.Gui/Views/ScrollBarView.cs b/Terminal.Gui/Views/ScrollBarView.cs index e70f1e5c73..3f47a5cfa8 100644 --- a/Terminal.Gui/Views/ScrollBarView.cs +++ b/Terminal.Gui/Views/ScrollBarView.cs @@ -152,14 +152,14 @@ public bool KeepContentAlwaysInViewport _keepContentAlwaysInViewport = value; var pos = 0; - if (value && !_vertical && _position + Host.Bounds.Width > _size) + if (value && !_vertical && _position + Host.Viewport.Width > _size) { - pos = _size - Host.Bounds.Width + (_showBothScrollIndicator ? 1 : 0); + pos = _size - Host.Viewport.Width + (_showBothScrollIndicator ? 1 : 0); } - if (value && _vertical && _position + Host.Bounds.Height > _size) + if (value && _vertical && _position + Host.Viewport.Height > _size) { - pos = _size - Host.Bounds.Height + (_showBothScrollIndicator ? 1 : 0); + pos = _size - Host.Viewport.Height + (_showBothScrollIndicator ? 1 : 0); } if (pos != 0) @@ -204,13 +204,12 @@ public int Position get => _position; set { - _position = value; - - if (IsInitialized) + if (_position == value) { - // We're not initialized so we can't do anything fancy. Just cache value. - SetPosition (value); + return; } + + SetPosition (value); } } @@ -262,7 +261,7 @@ public int Size if (IsInitialized) { - SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); ShowHideScrollBars (false); SetNeedsDisplay (); } @@ -275,7 +274,7 @@ public int Size public event EventHandler ChangedPosition; /// - protected internal override bool OnMouseEvent (MouseEvent mouseEvent) + protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { if (mouseEvent.Flags != MouseFlags.Button1Pressed && mouseEvent.Flags != MouseFlags.Button1DoubleClicked @@ -301,7 +300,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } int location = _vertical ? mouseEvent.Y : mouseEvent.X; - int barsize = _vertical ? Bounds.Height : Bounds.Width; + int barsize = _vertical ? Viewport.Height : Viewport.Width; int posTopLeftTee = _vertical ? _posTopTee + 1 : _posLeftTee + 1; int posBottomRightTee = _vertical ? _posBottomTee + 1 : _posRightTee + 1; barsize -= 2; @@ -449,7 +448,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) public virtual void OnChangedPosition () { ChangedPosition?.Invoke (this, EventArgs.Empty); } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (ColorScheme is null || ((!_showScrollIndicator || Size == 0) && AutoHideScrollBars && Visible)) { @@ -461,7 +460,7 @@ public override void OnDrawContent (Rectangle contentArea) return; } - if (Size == 0 || (_vertical && Bounds.Height == 0) || (!_vertical && Bounds.Width == 0)) + if (Size == 0 || (_vertical && Viewport.Height == 0) || (!_vertical && Viewport.Width == 0)) { return; } @@ -470,13 +469,13 @@ public override void OnDrawContent (Rectangle contentArea) if (_vertical) { - if (Bounds.Right < Bounds.Width - 1) + if (Viewport.Right < Viewport.Width - 1) { return; } - int col = Bounds.Width - 1; - int bh = Bounds.Height; + int col = Viewport.Width - 1; + int bh = Viewport.Height; Rune special; if (bh < 4) @@ -486,7 +485,7 @@ public override void OnDrawContent (Rectangle contentArea) Move (col, 0); - if (Bounds.Height == 1) + if (Viewport.Height == 1) { Driver.AddRune (Glyphs.Diamond); } @@ -495,15 +494,15 @@ public override void OnDrawContent (Rectangle contentArea) Driver.AddRune (Glyphs.UpArrow); } - if (Bounds.Height == 3) + if (Viewport.Height == 3) { Move (col, 1); Driver.AddRune (Glyphs.Diamond); } - if (Bounds.Height > 1) + if (Viewport.Height > 1) { - Move (col, Bounds.Height - 1); + Move (col, Viewport.Height - 1); Driver.AddRune (Glyphs.DownArrow); } } @@ -524,8 +523,7 @@ public override void OnDrawContent (Rectangle contentArea) by1 = Math.Max (by1 - 1, 0); } - Move (col, 0); - Driver.AddRune (Glyphs.UpArrow); + AddRune (col, 0, Glyphs.UpArrow); var hasTopTee = false; var hasDiamond = false; @@ -533,7 +531,6 @@ public override void OnDrawContent (Rectangle contentArea) for (var y = 0; y < bh; y++) { - Move (col, y + 1); if ((y < by1 || y > by2) && ((_position > 0 && !hasTopTee) || (hasTopTee && hasBottomTee))) { @@ -567,28 +564,26 @@ public override void OnDrawContent (Rectangle contentArea) } } - Driver.AddRune (special); + AddRune (col, y + 1, special); } if (!hasTopTee) { - Move (col, Bounds.Height - 2); - Driver.AddRune (Glyphs.TopTee); + AddRune (col, Viewport.Height - 2, Glyphs.TopTee); } - Move (col, Bounds.Height - 1); - Driver.AddRune (Glyphs.DownArrow); + AddRune (col, Viewport.Height - 1, Glyphs.DownArrow); } } else { - if (Bounds.Bottom < Bounds.Height - 1) + if (Viewport.Bottom < Viewport.Height - 1) { return; } - int row = Bounds.Height - 1; - int bw = Bounds.Width; + int row = Viewport.Height - 1; + int bw = Viewport.Width; Rune special; if (bw < 4) @@ -663,7 +658,7 @@ public override void OnDrawContent (Rectangle contentArea) if (!hasLeftTee) { - Move (Bounds.Width - 2, row); + Move (Viewport.Width - 2, row); Driver.AddRune (Glyphs.LeftTee); } @@ -685,7 +680,7 @@ public override bool OnEnter (View view) internal bool CanScroll (int n, out int max, bool isVertical = false) { - if (Host?.Bounds.IsEmpty != false) + if (Host?.Viewport.IsEmpty != false) { max = 0; @@ -706,7 +701,7 @@ internal bool CanScroll (int n, out int max, bool isVertical = false) private bool CheckBothScrollBars (ScrollBarView scrollBarView, bool pending = false) { - int barsize = scrollBarView._vertical ? scrollBarView.Bounds.Height : scrollBarView.Bounds.Width; + int barsize = scrollBarView._vertical ? scrollBarView.Viewport.Height : scrollBarView.Viewport.Width; if (barsize == 0 || barsize >= scrollBarView._size) { @@ -845,15 +840,15 @@ private void CreateBottomRightCorner (View host) private int GetBarsize (bool isVertical) { - if (Host?.Bounds.IsEmpty != false) + if (Host?.Viewport.IsEmpty != false) { return 0; } return isVertical ? KeepContentAlwaysInViewport - ? Host.Bounds.Height + (_showBothScrollIndicator ? -2 : -1) + ? Host.Viewport.Height + (_showBothScrollIndicator ? -2 : -1) : 0 : - KeepContentAlwaysInViewport ? Host.Bounds.Width + (_showBothScrollIndicator ? -2 : -1) : 0; + KeepContentAlwaysInViewport ? Host.Viewport.Width + (_showBothScrollIndicator ? -2 : -1) : 0; } private void Host_EnabledChanged (object sender, EventArgs e) @@ -890,7 +885,7 @@ private void Host_VisibleChanged (object sender, EventArgs e) private void ScrollBarView_Initialized (object sender, EventArgs e) { SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame ?? Host?.Frame ?? Frame); + SetRelativeLayout (SuperView?.Frame.Size ?? Host?.Frame.Size ?? Frame.Size); if (OtherScrollBarView is null) { @@ -904,7 +899,22 @@ private void ScrollBarView_Initialized (object sender, EventArgs e) // Helper to assist Initialized event handler private void SetPosition (int newPosition) { - if (CanScroll (newPosition - _position, out int max, _vertical)) + if (!IsInitialized) + { + // We're not initialized so we can't do anything fancy. Just cache value. + _position = newPosition; + + return; + } + + if (newPosition < 0) + { + _position = 0; + SetNeedsDisplay (); + + return; + } + else if (CanScroll (newPosition - _position, out int max, _vertical)) { if (max == newPosition - _position) { @@ -995,11 +1005,11 @@ private void ShowHideScrollBars (bool redraw = true) } SetWidthHeight (); - SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); if (_otherScrollBarView is { }) { - OtherScrollBarView.SetRelativeLayout (SuperView?.Frame ?? Host.Frame); + OtherScrollBarView.SetRelativeLayout (SuperView?.Frame.Size ?? Host.Frame.Size); } if (_showBothScrollIndicator) diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index f021932ab9..a41ae12dfc 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -21,7 +21,7 @@ namespace Terminal.Gui; /// /// The subviews that are added to this are offset by the /// property. The view itself is a window into the space represented by the -/// . +/// . /// /// Use the /// @@ -33,7 +33,6 @@ public class ScrollView : View private bool _autoHideScrollBars = true; private View _contentBottomRightCorner; private Point _contentOffset; - private Size _contentSize; private bool _keepContentAlwaysInViewport = true; private bool _showHorizontalScrollIndicator; private bool _showVerticalScrollIndicator; @@ -85,14 +84,14 @@ public ScrollView () AddCommand (Command.ScrollDown, () => ScrollDown (1)); AddCommand (Command.ScrollLeft, () => ScrollLeft (1)); AddCommand (Command.ScrollRight, () => ScrollRight (1)); - AddCommand (Command.PageUp, () => ScrollUp (Bounds.Height)); - AddCommand (Command.PageDown, () => ScrollDown (Bounds.Height)); - AddCommand (Command.PageLeft, () => ScrollLeft (Bounds.Width)); - AddCommand (Command.PageRight, () => ScrollRight (Bounds.Width)); - AddCommand (Command.TopHome, () => ScrollUp (_contentSize.Height)); - AddCommand (Command.BottomEnd, () => ScrollDown (_contentSize.Height)); - AddCommand (Command.LeftHome, () => ScrollLeft (_contentSize.Width)); - AddCommand (Command.RightEnd, () => ScrollRight (_contentSize.Width)); + AddCommand (Command.PageUp, () => ScrollUp (Viewport.Height)); + AddCommand (Command.PageDown, () => ScrollDown (Viewport.Height)); + AddCommand (Command.PageLeft, () => ScrollLeft (Viewport.Width)); + AddCommand (Command.PageRight, () => ScrollRight (Viewport.Width)); + AddCommand (Command.TopHome, () => ScrollUp (ContentSize.Height)); + AddCommand (Command.BottomEnd, () => ScrollDown (ContentSize.Height)); + AddCommand (Command.LeftHome, () => ScrollLeft (ContentSize.Width)); + AddCommand (Command.RightEnd, () => ScrollRight (ContentSize.Width)); // Default keybindings for this view KeyBindings.Add (Key.CursorUp, Command.ScrollUp); @@ -134,6 +133,14 @@ public ScrollView () _vertical.ChangedPosition += delegate { ContentOffset = new Point (ContentOffset.X, _vertical.Position); }; _horizontal.ChangedPosition += delegate { ContentOffset = new Point (_horizontal.Position, ContentOffset.Y); }; }; + ContentSizeChanged += ScrollViewContentSizeChanged; + } + + private void ScrollViewContentSizeChanged (object sender, SizeChangedEventArgs e) + { + _contentView.Frame = new Rectangle (ContentOffset, e.Size with {Width = e.Size.Width-1, Height = e.Size.Height-1}); + _vertical.Size = e.Size.Height; + _horizontal.Size = e.Size.Width; } private void Application_UnGrabbedMouse (object sender, ViewEventArgs e) @@ -194,7 +201,6 @@ public Point ContentOffset { // 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; } @@ -203,23 +209,23 @@ public Point ContentOffset } } - /// Represents the contents of the data shown inside the scrollview - /// The size of the content. - public Size ContentSize - { - get => _contentSize; - set - { - if (_contentSize != value) - { - _contentSize = value; - _contentView.Frame = new Rectangle (_contentOffset, value); - _vertical.Size = _contentSize.Height; - _horizontal.Size = _contentSize.Width; - SetNeedsDisplay (); - } - } - } + ///// Represents the contents of the data shown inside the scrollview + ///// The size of the content. + //public new Size ContentSize + //{ + // get => ContentSize; + // set + // { + // if (ContentSize != value) + // { + // ContentSize = value; + // _contentView.Frame = new Rectangle (_contentOffset, value); + // _vertical.Size = ContentSize.Height; + // _horizontal.Size = ContentSize.Width; + // SetNeedsDisplay (); + // } + // } + //} /// Get or sets if the view-port is kept always visible in the area of this public bool KeepContentAlwaysInViewport @@ -234,26 +240,26 @@ public bool KeepContentAlwaysInViewport _horizontal.OtherScrollBarView.KeepContentAlwaysInViewport = value; Point p = default; - if (value && -_contentOffset.X + Bounds.Width > _contentSize.Width) + if (value && -_contentOffset.X + Viewport.Width > ContentSize.Width) { p = new Point ( - _contentSize.Width - Bounds.Width + (_showVerticalScrollIndicator ? 1 : 0), + ContentSize.Width - Viewport.Width + (_showVerticalScrollIndicator ? 1 : 0), -_contentOffset.Y ); } - if (value && -_contentOffset.Y + Bounds.Height > _contentSize.Height) + if (value && -_contentOffset.Y + Viewport.Height > ContentSize.Height) { if (p == default (Point)) { p = new Point ( -_contentOffset.X, - _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0) + ContentSize.Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0) ); } else { - p.Y = _contentSize.Height - Bounds.Height + (_showHorizontalScrollIndicator ? 1 : 0); + p.Y = ContentSize.Height - Viewport.Height + (_showHorizontalScrollIndicator ? 1 : 0); } } @@ -359,14 +365,12 @@ public override void Add (View view) } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { SetViewsNeedsDisplay (); - Rectangle savedClip = ClipToBounds (); - // TODO: It's bad practice for views to always clear a view. It negates clipping. - Clear (contentArea); + Clear (); if (!string.IsNullOrEmpty (_contentView.Text) || _contentView.Subviews.Count > 0) { @@ -374,8 +378,6 @@ public override void OnDrawContent (Rectangle contentArea) } DrawScrollBars (); - - Driver.Clip = savedClip; } /// @@ -613,7 +615,7 @@ private void SetContentOffset (Point offset) { // INTENT: Unclear intent. How about a call to Offset? _contentOffset = new Point (-Math.Abs (offset.X), -Math.Abs (offset.Y)); - _contentView.Frame = new Rectangle (_contentOffset, _contentSize); + _contentView.Frame = new Rectangle (_contentOffset, ContentSize); int p = Math.Max (0, -_contentOffset.Y); if (_vertical.Position != p) @@ -644,7 +646,7 @@ private void ShowHideScrollBars () bool v = false, h = false; var p = false; - if (Bounds.Height == 0 || Bounds.Height > _contentSize.Height) + if (Viewport.Height == 0 || Viewport.Height > ContentSize.Height) { if (ShowVerticalScrollIndicator) { @@ -653,7 +655,7 @@ private void ShowHideScrollBars () v = false; } - else if (Bounds.Height > 0 && Bounds.Height == _contentSize.Height) + else if (Viewport.Height > 0 && Viewport.Height == ContentSize.Height) { p = true; } @@ -667,7 +669,7 @@ private void ShowHideScrollBars () v = true; } - if (Bounds.Width == 0 || Bounds.Width > _contentSize.Width) + if (Viewport.Width == 0 || Viewport.Width > ContentSize.Width) { if (ShowHorizontalScrollIndicator) { @@ -676,7 +678,7 @@ private void ShowHideScrollBars () h = false; } - else if (Bounds.Width > 0 && Bounds.Width == _contentSize.Width && p) + else if (Viewport.Width > 0 && Viewport.Width == ContentSize.Width && p) { if (ShowHorizontalScrollIndicator) { @@ -728,13 +730,13 @@ private void ShowHideScrollBars () if (v) { - _vertical.SetRelativeLayout (Bounds); + _vertical.SetRelativeLayout (Viewport.Size); _vertical.Draw (); } if (h) { - _horizontal.SetRelativeLayout (Bounds); + _horizontal.SetRelativeLayout (Viewport.Size); _horizontal.Draw (); } @@ -742,7 +744,7 @@ private void ShowHideScrollBars () if (v && h) { - _contentBottomRightCorner.SetRelativeLayout (Bounds); + _contentBottomRightCorner.SetRelativeLayout (Viewport.Size); _contentBottomRightCorner.Draw (); } } diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs index 7500037246..3d0ea3fb02 100644 --- a/Terminal.Gui/Views/Slider.cs +++ b/Terminal.Gui/Views/Slider.cs @@ -374,7 +374,7 @@ public bool AllowEmpty } /// - /// If the slider will be sized to fit the available space (the Bounds of the the + /// If the slider will be sized to fit the available space (the Viewport of the the /// SuperView). /// /// @@ -663,17 +663,17 @@ internal void CalcSpacingConfig () if (AutoSize) { - // Max size is SuperView's Bounds. Min Size is size that will fit. + // Max size is SuperView's Viewport. Min Size is size that will fit. if (SuperView is { }) { - // Calculate the size of the slider based on the size of the SuperView's Bounds. + // Calculate the size of the slider based on the size of the SuperView's Viewport. if (_config._sliderOrientation == Orientation.Horizontal) { - size = int.Min (SuperView.Bounds.Width, CalcBestLength ()); + size = int.Min (SuperView.Viewport.Width, CalcBestLength ()); } else { - size = int.Min (SuperView.Bounds.Height, CalcBestLength ()); + size = int.Min (SuperView.Viewport.Height, CalcBestLength ()); } } else @@ -686,14 +686,14 @@ internal void CalcSpacingConfig () } else { - // Fit Slider to the Bounds + // Fit Slider to the Viewport if (_config._sliderOrientation == Orientation.Horizontal) { - size = Bounds.Width; + size = Viewport.Width; } else { - size = Bounds.Height; + size = Viewport.Height; } } @@ -777,15 +777,15 @@ public void SetBoundsBestFit () if (_config._sliderOrientation == Orientation.Horizontal) { - Bounds = new ( - Bounds.Location, + Viewport = new ( + Viewport.Location, new ( int.Min ( - SuperView.Bounds.Width - adornmentsThickness.Horizontal, + SuperView.Viewport.Width - adornmentsThickness.Horizontal, CalcBestLength () ), int.Min ( - SuperView.Bounds.Height - adornmentsThickness.Vertical, + SuperView.Viewport.Height - adornmentsThickness.Vertical, CalcThickness () ) ) @@ -793,15 +793,15 @@ public void SetBoundsBestFit () } else { - Bounds = new ( - Bounds.Location, + Viewport = new ( + Viewport.Location, new ( int.Min ( - SuperView.Bounds.Width - adornmentsThickness.Horizontal, + SuperView.Viewport.Width - adornmentsThickness.Horizontal, CalcThickness () ), int.Min ( - SuperView.Bounds.Height - adornmentsThickness.Vertical, + SuperView.Viewport.Height - adornmentsThickness.Vertical, CalcBestLength () ) ) @@ -988,7 +988,7 @@ public override void PositionCursor () if (TryGetPositionByOption (FocusedOption, out (int x, int y) position)) { - if (IsInitialized && Bounds.Contains (position.x, position.y)) + if (IsInitialized && Viewport.Contains (position.x, position.y)) { Move (position.x, position.y); } @@ -996,7 +996,7 @@ public override void PositionCursor () } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { // TODO: make this more surgical to reduce repaint @@ -1009,9 +1009,9 @@ public override void OnDrawContent (Rectangle contentArea) #if (DEBUG) Driver?.SetAttribute (new Attribute (Color.White, Color.Red)); - for (var y = 0; y < contentArea.Height; y++) + for (var y = 0; y < viewport.Height; y++) { - for (var x = 0; x < contentArea.Width; x++) + for (var x = 0; x < viewport.Width; x++) { // MoveAndAdd (x, y, '·'); } @@ -1073,7 +1073,7 @@ private string AlignText (string text, int width, TextAlignment textAlignment) private void DrawSlider () { // TODO: be more surgical on clear - Clear (Bounds); + Clear (); // Attributes @@ -1241,7 +1241,7 @@ private void DrawSlider () } } - int remaining = isVertical ? Bounds.Height - y : Bounds.Width - x; + int remaining = isVertical ? Viewport.Height - y : Viewport.Width - x; // Right Spacing if (_config._showEndSpacing) diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 0dfe31cf11..6dc96f663d 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -172,7 +172,7 @@ protected internal override bool OnMouseEvent (MouseEvent me) } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { Move (0, 0); Driver.SetAttribute (GetNormalColor ()); diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs index aac2bcf94d..44375baf93 100644 --- a/Terminal.Gui/Views/TabView.cs +++ b/Terminal.Gui/Views/TabView.cs @@ -297,7 +297,7 @@ public void EnsureSelectedTabIsVisible () } // if current viewport does not include the selected tab - if (!CalculateViewport (Bounds).Any (r => Equals (SelectedTab, r.Tab))) + if (!CalculateViewport (Viewport).Any (r => Equals (SelectedTab, r.Tab))) { // Set scroll offset so the first tab rendered is the TabScrollOffset = Math.Max (0, Tabs.IndexOf (SelectedTab)); @@ -311,14 +311,14 @@ public void EnsureSelectedTabIsVisible () public int EnsureValidScrollOffsets (int value) { return Math.Max (Math.Min (value, Tabs.Count - 1), 0); } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { Driver.SetAttribute (GetNormalColor ()); if (Tabs.Any ()) { - Rectangle savedClip = ClipToBounds (); - _tabsBar.OnDrawContent (contentArea); + Rectangle savedClip = SetClip (); + _tabsBar.OnDrawContent (viewport); _contentView.SetNeedsDisplay (); _contentView.Draw (); Driver.Clip = savedClip; @@ -326,7 +326,7 @@ public override void OnDrawContent (Rectangle contentArea) } /// - public override void OnDrawContentComplete (Rectangle contentArea) { _tabsBar.OnDrawContentComplete (contentArea); } + public override void OnDrawContentComplete (Rectangle viewport) { _tabsBar.OnDrawContentComplete (viewport); } /// /// Removes the given from . Caller is responsible for disposing the @@ -657,12 +657,12 @@ protected internal override bool OnMouseEvent (MouseEvent me) return false; } - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - _host._tabLocations = _host.CalculateViewport (Bounds).ToArray (); + _host._tabLocations = _host.CalculateViewport (Viewport).ToArray (); // clear any old text - Clear (contentArea); + Clear (); RenderTabLine (); @@ -670,7 +670,7 @@ public override void OnDrawContent (Rectangle contentArea) Driver.SetAttribute (GetNormalColor ()); } - public override void OnDrawContentComplete (Rectangle contentArea) + public override void OnDrawContentComplete (Rectangle viewport) { if (_host._tabLocations is null) { @@ -683,7 +683,7 @@ public override void OnDrawContentComplete (Rectangle contentArea) for (var i = 0; i < tabLocations.Length; i++) { View tab = tabLocations [i].Tab; - Rectangle vts = tab.BoundsToScreen (tab.Bounds); + Rectangle vts = tab.ViewportToScreen (tab.Viewport); var lc = new LineCanvas (); int selectedOffset = _host.Style.ShowTopLine && tabLocations [i].IsSelected ? 0 : 1; @@ -1115,7 +1115,7 @@ public override void OnDrawContentComplete (Rectangle contentArea) int lastSelectedTab = !_host.Style.ShowTopLine && i == selectedTab ? 1 : _host.Style.TabsOnBottom ? 1 : 0; - Rectangle tabsBarVts = BoundsToScreen (Bounds); + Rectangle tabsBarVts = ViewportToScreen (Viewport); int lineLength = tabsBarVts.Right - vts.Right; // Right horizontal line @@ -1238,7 +1238,7 @@ private void RenderTabLine () View selected = null; int topLine = _host.Style.ShowTopLine ? 1 : 0; - int width = Bounds.Width; + int width = Viewport.Width; foreach (TabToRender toRender in tabLocations) { @@ -1314,7 +1314,7 @@ private void RenderTabLine () } tab.TextFormatter.Draw ( - tab.BoundsToScreen (tab.Bounds), + tab.ViewportToScreen (tab.Viewport), prevAttr, ColorScheme.HotNormal ); @@ -1360,7 +1360,7 @@ private void RenderUnderline () // if there are more tabs to the right not visible if (ShouldDrawRightScrollIndicator ()) { - _rightScrollIndicator.X = Bounds.Width - 1; + _rightScrollIndicator.X = Viewport.Width - 1; _rightScrollIndicator.Y = y; // indicate that diff --git a/Terminal.Gui/Views/TableView/ListTableSource.cs b/Terminal.Gui/Views/TableView/ListTableSource.cs index 8695155019..1286e2660a 100644 --- a/Terminal.Gui/Views/TableView/ListTableSource.cs +++ b/Terminal.Gui/Views/TableView/ListTableSource.cs @@ -109,12 +109,12 @@ private int CalculateColumns () if (Style.Orientation == Orientation.Vertical != Style.ScrollParallel) { - float f = (float)_tableView.Bounds.Height - _tableView.GetHeaderHeight (); + float f = (float)_tableView.Viewport.Height - _tableView.GetHeaderHeight (); cols = (int)Math.Ceiling (Count / f); } else { - cols = (int)Math.Ceiling (((float)_tableView.Bounds.Width - 1) / colWidth) - 2; + cols = (int)Math.Ceiling (((float)_tableView.Viewport.Width - 1) / colWidth) - 2; } return cols > 1 ? cols : 1; @@ -179,7 +179,7 @@ private DataTable CreateTable (int cols = 1) private void TableView_DrawContent (object sender, DrawEventArgs e) { - if (!_tableView.Bounds.Equals (_lastBounds) + if (!_tableView.Viewport.Equals (_lastBounds) || _tableView.MaxCellWidth != _lastMaxCellWidth || _tableView.MinCellWidth != _lastMinCellWidth || Style != _lastStyle @@ -188,7 +188,7 @@ private void TableView_DrawContent (object sender, DrawEventArgs e) DataTable = CreateTable (CalculateColumns ()); } - _lastBounds = _tableView.Bounds; + _lastBounds = _tableView.Viewport; _lastMinCellWidth = _tableView.MaxCellWidth; _lastMaxCellWidth = _tableView.MaxCellWidth; _lastStyle = Style; diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs index 34d3bfbe8d..e82a006661 100644 --- a/Terminal.Gui/Views/TableView/TableView.cs +++ b/Terminal.Gui/Views/TableView/TableView.cs @@ -483,7 +483,7 @@ public ITableSource Table return null; } - IEnumerable viewPort = CalculateViewport (Bounds); + IEnumerable viewPort = CalculateViewport (Viewport); int headerHeight = GetHeaderHeightIfAny (); @@ -502,7 +502,7 @@ public ITableSource Table } // the cell is way down below the scroll area and off the screen - if (tableRow > RowOffset + (Bounds.Height - headerHeight)) + if (tableRow > RowOffset + (Viewport.Height - headerHeight)) { return null; } @@ -577,7 +577,7 @@ public void EnsureSelectedCellIsVisible () return; } - ColumnToRender [] columnsToRender = CalculateViewport (Bounds).ToArray (); + ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray (); int headerHeight = GetHeaderHeightIfAny (); //if we have scrolled too far to the left @@ -595,7 +595,7 @@ public void EnsureSelectedCellIsVisible () while (SelectedColumn > columnsToRender.Max (r => r.Column)) { ColumnOffset++; - columnsToRender = CalculateViewport (Bounds).ToArray (); + columnsToRender = CalculateViewport (Viewport).ToArray (); // if we are already scrolled to the last column then break // this will prevent any theoretical infinite loop @@ -612,9 +612,9 @@ public void EnsureSelectedCellIsVisible () } //if we have scrolled too far down - if (SelectedRow >= RowOffset + (Bounds.Height - headerHeight)) + if (SelectedRow >= RowOffset + (Viewport.Height - headerHeight)) { - RowOffset = SelectedRow - (Bounds.Height - headerHeight) + 1; + RowOffset = SelectedRow - (Viewport.Height - headerHeight) + 1; } //if we have scrolled too far up @@ -896,9 +896,9 @@ protected internal override bool OnMouseEvent (MouseEvent me) } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); Move (0, 0); @@ -906,12 +906,12 @@ public override void OnDrawContent (Rectangle contentArea) scrollLeftPoint = null; // What columns to render at what X offset in viewport - ColumnToRender [] columnsToRender = CalculateViewport (Bounds).ToArray (); + ColumnToRender [] columnsToRender = CalculateViewport (Viewport).ToArray (); Driver.SetAttribute (GetNormalColor ()); //invalidate current row (prevents scrolling around leaving old characters in the frame - Driver.AddStr (new string (' ', Bounds.Width)); + Driver.AddStr (new string (' ', Viewport.Width)); var line = 0; @@ -925,7 +925,7 @@ public override void OnDrawContent (Rectangle contentArea) */ if (Style.ShowHorizontalHeaderOverline) { - RenderHeaderOverline (line, Bounds.Width, columnsToRender); + RenderHeaderOverline (line, Viewport.Width, columnsToRender); line++; } @@ -937,7 +937,7 @@ public override void OnDrawContent (Rectangle contentArea) if (Style.ShowHorizontalHeaderUnderline) { - RenderHeaderUnderline (line, Bounds.Width, columnsToRender); + RenderHeaderUnderline (line, Viewport.Width, columnsToRender); line++; } } @@ -945,9 +945,9 @@ public override void OnDrawContent (Rectangle contentArea) int headerLinesConsumed = line; //render the cells - for (; line < Bounds.Height; line++) + for (; line < Viewport.Height; line++) { - ClearLine (line, Bounds.Width); + ClearLine (line, Viewport.Width); //work out what Row to render int rowToRender = RowOffset + (line - headerLinesConsumed); @@ -963,7 +963,7 @@ public override void OnDrawContent (Rectangle contentArea) { if (rowToRender == Table.Rows && Style.ShowHorizontalBottomline) { - RenderBottomLine (line, Bounds.Width, columnsToRender); + RenderBottomLine (line, Viewport.Width, columnsToRender); } continue; @@ -1001,7 +1001,7 @@ public override bool OnProcessKeyDown (Key keyEvent) /// true to extend the current selection (if any) instead of replacing public void PageDown (bool extend) { - ChangeSelectionByOffset (0, Bounds.Height - GetHeaderHeightIfAny (), extend); + ChangeSelectionByOffset (0, Viewport.Height - GetHeaderHeightIfAny (), extend); Update (); } @@ -1009,7 +1009,7 @@ public void PageDown (bool extend) /// true to extend the current selection (if any) instead of replacing public void PageUp (bool extend) { - ChangeSelectionByOffset (0, -(Bounds.Height - GetHeaderHeightIfAny ()), extend); + ChangeSelectionByOffset (0, -(Viewport.Height - GetHeaderHeightIfAny ()), extend); Update (); } @@ -1073,7 +1073,7 @@ public override void PositionCursor () return null; } - IEnumerable viewPort = CalculateViewport (Bounds); + IEnumerable viewPort = CalculateViewport (Viewport); int headerHeight = GetHeaderHeightIfAny (); @@ -1658,7 +1658,7 @@ private void RenderHeaderMidline (int row, ColumnToRender [] columnsToRender) // Renders something like: // │ArithmeticComparator│chi │Healthboard│Interpretation│Labnumber│ - ClearLine (row, Bounds.Width); + ClearLine (row, Viewport.Width); //render start of line if (style.ShowVerticalHeaderLines) @@ -1688,7 +1688,7 @@ private void RenderHeaderMidline (int row, ColumnToRender [] columnsToRender) //render end of line if (style.ShowVerticalHeaderLines) { - AddRune (Bounds.Width - 1, row, Glyphs.VLine); + AddRune (Viewport.Width - 1, row, Glyphs.VLine); } } @@ -1846,7 +1846,7 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen } Driver.SetAttribute (color); - Driver.AddStr (new string (' ', Bounds.Width)); + Driver.AddStr (new string (' ', Viewport.Width)); // Render cells for each visible header for the current row for (var i = 0; i < columnsToRender.Length; i++) @@ -1955,7 +1955,7 @@ private void RenderRow (int row, int rowToRender, ColumnToRender [] columnsToRen //render start and end of line AddRune (0, row, Glyphs.VLine); - AddRune (Bounds.Width - 1, row, Glyphs.VLine); + AddRune (Viewport.Width - 1, row, Glyphs.VLine); } } diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index e97a27ec51..5ee281596e 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -973,7 +973,7 @@ public void MoveEnd () } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { _isDrawing = true; @@ -1196,8 +1196,8 @@ public override void PositionCursor () int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0); int offB = OffSetBackground (); - Rectangle containerFrame = SuperView?.BoundsToScreen (SuperView.Bounds) ?? default (Rectangle); - Rectangle thisFrame = BoundsToScreen (Bounds); + Rectangle containerFrame = SuperView?.ViewportToScreen (SuperView.Viewport) ?? default (Rectangle); + Rectangle thisFrame = ViewportToScreen (Viewport); if (pos > -1 && col >= pos @@ -1746,7 +1746,7 @@ private void MoveWordRightExtend () } } - // BUGBUG: This assumes Frame == Bounds. It's also not clear what the intention is. For now, changed to always return 0. + // BUGBUG: This assumes Frame == Viewport. It's also not clear what the intention is. For now, changed to always return 0. private int OffSetBackground () { var offB = 0; @@ -1878,9 +1878,9 @@ private void RenderCaption () Move (0, 0); string render = Caption; - if (render.GetColumns () > Bounds.Width) + if (render.GetColumns () > Viewport.Width) { - render = render [..Bounds.Width]; + render = render [..Viewport.Width]; } Driver.AddStr (render); @@ -1941,9 +1941,9 @@ private void TextField_Initialized (object sender, EventArgs e) { _cursorPosition = Text.GetRuneCount (); - if (Bounds.Width > 0) + if (Viewport.Width > 0) { - ScrollOffset = _cursorPosition > Bounds.Width + 1 ? _cursorPosition - Bounds.Width + 1 : 0; + ScrollOffset = _cursorPosition > Viewport.Width + 1 ? _cursorPosition - Viewport.Width + 1 : 0; } Autocomplete.HostControl = this; diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs index a86cd910f5..a62fa4a549 100644 --- a/Terminal.Gui/Views/TextValidateField.cs +++ b/Terminal.Gui/Views/TextValidateField.cs @@ -537,7 +537,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)) { - int c = _provider.Cursor (mouseEvent.X - GetMargins (Bounds.Width).left); + int c = _provider.Cursor (mouseEvent.X - GetMargins (Viewport.Width).left); if (_provider.Fixed == false && TextAlignment == TextAlignment.Right && Text.Length > 0) { @@ -555,7 +555,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (_provider is null) { @@ -568,7 +568,7 @@ public override void OnDrawContent (Rectangle contentArea) Color bgcolor = !IsValid ? new Color (Color.BrightRed) : ColorScheme.Focus.Background; var textColor = new Attribute (ColorScheme.Focus.Foreground, bgcolor); - (int margin_left, int margin_right) = GetMargins (Bounds.Width); + (int margin_left, int margin_right) = GetMargins (Viewport.Width); Move (0, 0); @@ -642,7 +642,7 @@ public override bool OnProcessKeyDown (Key a) /// public override void PositionCursor () { - (int left, _) = GetMargins (Bounds.Width); + (int left, _) = GetMargins (Viewport.Width); // Fixed = true, is for inputs that have fixed width, like masked ones. // Fixed = false, is for normal input. @@ -660,7 +660,7 @@ public override void PositionCursor () Move (curPos, 0); } - if (curPos < 0 || curPos >= Bounds.Width) + if (curPos < 0 || curPos >= Viewport.Width) { Application.Driver.SetCursorVisibility (CursorVisibility.Invisible); } diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 038e1ced38..a20b4cbbc4 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -3586,7 +3586,7 @@ public virtual void OnContentsChanged () } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { _isDrawing = true; @@ -3649,7 +3649,7 @@ public override void OnDrawContent (Rectangle contentArea) AddRune (col, row, rune); } - if (!TextModel.SetCol (ref col, contentArea.Right, cols)) + if (!TextModel.SetCol (ref col, viewport.Right, cols)) { break; } @@ -3672,7 +3672,7 @@ public override void OnDrawContent (Rectangle contentArea) if (row < bottom) { SetNormalColor (); - ClearRegion (contentArea.Left, row, right, bottom); + ClearRegion (viewport.Left, row, right, bottom); } PositionCursor (); diff --git a/Terminal.Gui/Views/TileView.cs b/Terminal.Gui/Views/TileView.cs index 8ef1c5b4aa..cbc2b9f22e 100644 --- a/Terminal.Gui/Views/TileView.cs +++ b/Terminal.Gui/Views/TileView.cs @@ -156,19 +156,19 @@ public override void LayoutSubviews () return; } - Rectangle contentArea = Bounds; + Rectangle viewport = Viewport; if (HasBorder ()) { - contentArea = new ( - contentArea.X + 1, - contentArea.Y + 1, - Math.Max (0, contentArea.Width - 2), - Math.Max (0, contentArea.Height - 2) - ); + viewport = new ( + viewport.X + 1, + viewport.Y + 1, + Math.Max (0, viewport.Width - 2), + Math.Max (0, viewport.Height - 2) + ); } - Setup (contentArea); + Setup (viewport); base.LayoutSubviews (); } @@ -179,12 +179,13 @@ public override void LayoutSubviews () public override bool OnDrawAdornments () { return false; } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { Driver.SetAttribute (ColorScheme.Normal); - Clear (contentArea); - base.OnDrawContent (contentArea); + Clear (); + + base.OnDrawContent (viewport); var lc = new LineCanvas (); @@ -195,19 +196,19 @@ public override void OnDrawContent (Rectangle contentArea) { if (HasBorder ()) { - lc.AddLine (Point.Empty, Bounds.Width, Orientation.Horizontal, LineStyle); - lc.AddLine (Point.Empty, Bounds.Height, Orientation.Vertical, LineStyle); + lc.AddLine (Point.Empty, Viewport.Width, Orientation.Horizontal, LineStyle); + lc.AddLine (Point.Empty, Viewport.Height, Orientation.Vertical, LineStyle); lc.AddLine ( - new Point (Bounds.Width - 1, Bounds.Height - 1), - -Bounds.Width, + new Point (Viewport.Width - 1, Viewport.Height - 1), + -Viewport.Width, Orientation.Horizontal, LineStyle ); lc.AddLine ( - new Point (Bounds.Width - 1, Bounds.Height - 1), - -Bounds.Height, + new Point (Viewport.Width - 1, Viewport.Height - 1), + -Viewport.Height, Orientation.Vertical, LineStyle ); @@ -217,7 +218,7 @@ public override void OnDrawContent (Rectangle contentArea) { bool isRoot = _splitterLines.Contains (line); - Rectangle screen = line.BoundsToScreen (Rectangle.Empty); + Rectangle screen = line.ViewportToScreen (Rectangle.Empty); Point origin = ScreenToFrame (screen.X, screen.Y); int length = line.Orientation == Orientation.Horizontal ? line.Frame.Width : line.Frame.Height; @@ -241,7 +242,7 @@ public override void OnDrawContent (Rectangle contentArea) Driver.SetAttribute (ColorScheme.Normal); - foreach (KeyValuePair p in lc.GetMap (Bounds)) + foreach (KeyValuePair p in lc.GetMap (Viewport)) { AddRune (p.Key.X, p.Key.Y, p.Value); } @@ -423,7 +424,7 @@ public bool SetSplitterPos (int idx, Pos value) ); } - int fullSpace = _orientation == Orientation.Vertical ? Bounds.Width : Bounds.Height; + int fullSpace = _orientation == Orientation.Vertical ? Viewport.Width : Viewport.Height; if (fullSpace != 0 && !IsValidNewSplitterPos (idx, value, fullSpace)) { @@ -758,9 +759,9 @@ private bool RecursiveContains (IEnumerable haystack, View needle) return false; } - private void Setup (Rectangle contentArea) + private void Setup (Rectangle viewport) { - if (contentArea.IsEmpty || contentArea.Height <= 0 || contentArea.Width <= 0) + if (viewport.IsEmpty || viewport.Height <= 0 || viewport.Width <= 0) { return; } @@ -803,18 +804,20 @@ private void Setup (Rectangle contentArea) if (Orientation == Orientation.Vertical) { - tile.ContentView.X = i == 0 ? contentArea.X : Pos.Right (visibleSplitterLines [i - 1]); - tile.ContentView.Y = contentArea.Y; - tile.ContentView.Height = contentArea.Height; - tile.ContentView.Width = GetTileWidthOrHeight (i, Bounds.Width, visibleTiles, visibleSplitterLines); + tile.ContentView.X = i == 0 ? viewport.X : Pos.Right (visibleSplitterLines [i - 1]); + tile.ContentView.Y = viewport.Y; + tile.ContentView.Height = viewport.Height; + tile.ContentView.Width = GetTileWidthOrHeight (i, Viewport.Width, visibleTiles, visibleSplitterLines); } else { - tile.ContentView.X = contentArea.X; - tile.ContentView.Y = i == 0 ? contentArea.Y : Pos.Bottom (visibleSplitterLines [i - 1]); - tile.ContentView.Width = contentArea.Width; - tile.ContentView.Height = GetTileWidthOrHeight (i, Bounds.Height, visibleTiles, visibleSplitterLines); + tile.ContentView.X = viewport.X; + tile.ContentView.Y = i == 0 ? viewport.Y : Pos.Bottom (visibleSplitterLines [i - 1]); + tile.ContentView.Width = viewport.Width; + tile.ContentView.Height = GetTileWidthOrHeight (i, Viewport.Height, visibleTiles, visibleSplitterLines); } + // BUGBUG: This should not be needed. If any of the pos/dim setters above actually changed values, NeedsDisplay should have already been set. + tile.ContentView.SetNeedsDisplay (); } } @@ -837,7 +840,7 @@ public TileTitleToRender (TileView parent, Tile tile, int depth) /// public Point GetLocalCoordinateForTitle (TileView intoCoordinateSpace) { - Rectangle screen = Tile.ContentView.BoundsToScreen (Rectangle.Empty); + Rectangle screen = Tile.ContentView.ViewportToScreen (Rectangle.Empty); return intoCoordinateSpace.ScreenToFrame (screen.X, screen.Y - 1); } @@ -845,7 +848,7 @@ internal string GetTrimmedTitle () { Dim spaceDim = Tile.ContentView.Width; - int spaceAbs = spaceDim.Anchor (Parent.Bounds.Width); + int spaceAbs = spaceDim.Anchor (Parent.Viewport.Width); var title = $" {Tile.Title} "; @@ -893,13 +896,13 @@ public void DrawSplitterSymbol () { if (dragPosition is { } || CanFocus) { - Point location = moveRuneRenderLocation ?? new Point (Bounds.Width / 2, Bounds.Height / 2); + Point location = moveRuneRenderLocation ?? new Point (Viewport.Width / 2, Viewport.Height / 2); AddRune (location.X, location.Y, Glyphs.Diamond); } } - protected internal override bool OnMouseEvent (MouseEvent mouseEvent) + protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { if (!dragPosition.HasValue && mouseEvent.Flags == MouseFlags.Button1Pressed) { @@ -919,7 +922,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { moveRuneRenderLocation = new Point ( 0, - Math.Max (1, Math.Min (Bounds.Height - 2, mouseEvent.Y)) + Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Y)) ); } } @@ -943,7 +946,7 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) { int dx = mouseEvent.X - dragPosition.Value.X; Parent.SetSplitterPos (Idx, Offset (X, dx)); - moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Bounds.Height - 2, mouseEvent.Y))); + moveRuneRenderLocation = new Point (0, Math.Max (1, Math.Min (Viewport.Height - 2, mouseEvent.Y))); } Parent.SetNeedsDisplay (); @@ -969,9 +972,9 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent) return false; } - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); DrawSplitterSymbol (); } @@ -988,7 +991,7 @@ public override void PositionCursor () { base.PositionCursor (); - Point location = moveRuneRenderLocation ?? new Point (Bounds.Width / 2, Bounds.Height / 2); + Point location = moveRuneRenderLocation ?? new Point (Viewport.Width / 2, Viewport.Height / 2); Move (location.X, location.Y); } @@ -1032,10 +1035,10 @@ private bool FinalisePosition (Pos oldValue, Pos newValue) { if (Orientation == Orientation.Horizontal) { - return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Bounds.Height)); + return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Viewport.Height)); } - return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Bounds.Width)); + return Parent.SetSplitterPos (Idx, ConvertToPosFactor (newValue, Parent.Viewport.Width)); } return Parent.SetSplitterPos (Idx, newValue); @@ -1071,8 +1074,8 @@ private Pos Offset (Pos pos, int delta) { int posAbsolute = pos.Anchor ( Orientation == Orientation.Horizontal - ? Parent.Bounds.Height - : Parent.Bounds.Width + ? Parent.Viewport.Height + : Parent.Viewport.Width ); return posAbsolute + delta; diff --git a/Terminal.Gui/Views/Toplevel.cs b/Terminal.Gui/Views/Toplevel.cs index 69bcb2d4db..e5f4b64b14 100644 --- a/Terminal.Gui/Views/Toplevel.cs +++ b/Terminal.Gui/Views/Toplevel.cs @@ -29,7 +29,7 @@ public partial class Toplevel : View /// public Toplevel () { - Arrangement = ViewArrangement.Movable; + Arrangement = ViewArrangement.Fixed; Width = Dim.Fill (); Height = Dim.Fill (); @@ -249,7 +249,7 @@ public virtual void OnAlternateForwardKeyChanged (KeyChangedEventArgs e) } /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (!Visible) { @@ -268,12 +268,12 @@ public override void OnDrawContent (Rectangle contentArea) { foreach (Toplevel top in Application.OverlappedChildren.AsEnumerable ().Reverse ()) { - if (top.Frame.IntersectsWith (Bounds)) + if (top.Frame.IntersectsWith (Viewport)) { if (top != this && !top.IsCurrentTop && !OutsideTopFrame (top) && top.Visible) { top.SetNeedsLayout (); - top.SetNeedsDisplay (top.Bounds); + top.SetNeedsDisplay (top.Viewport); top.Draw (); top.OnRenderLineCanvas (); } @@ -284,20 +284,20 @@ public override void OnDrawContent (Rectangle contentArea) // This should not be here, but in base foreach (View view in Subviews) { - if (view.Frame.IntersectsWith (Bounds) && !OutsideTopFrame (this)) + if (view.Frame.IntersectsWith (Viewport) && !OutsideTopFrame (this)) { //view.SetNeedsLayout (); - view.SetNeedsDisplay (view.Bounds); + view.SetNeedsDisplay (); view.SetSubViewNeedsDisplay (); } } - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); // This is causing the menus drawn incorrectly if UseSubMenusSingleFrame is true //if (this.MenuBar is { } && this.MenuBar.IsMenuOpen && this.MenuBar.openMenu is { }) { // // TODO: Hack until we can get compositing working right. - // this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Bounds); + // this.MenuBar.openMenu.Redraw (this.MenuBar.openMenu.Viewport); //} } } @@ -380,6 +380,7 @@ public override void PositionCursor () /// The Toplevel to adjust. public virtual void PositionToplevel (Toplevel top) { + View superView = GetLocationEnsuringFullVisibility ( top, top.Frame.X, @@ -547,7 +548,7 @@ public virtual void RequestStop () /// to dispose objects after calling . /// public event EventHandler Unloaded; - + internal void AddMenuStatusBar (View view) { if (view is MenuBar) diff --git a/Terminal.Gui/Views/TreeView/TreeView.cs b/Terminal.Gui/Views/TreeView/TreeView.cs index 941948737b..2eb67267c1 100644 --- a/Terminal.Gui/Views/TreeView/TreeView.cs +++ b/Terminal.Gui/Views/TreeView/TreeView.cs @@ -742,10 +742,10 @@ public void EnsureVisible (T model) //if user has scrolled up too far to see their selection ScrollOffsetVertical = idx; } - else if (idx >= ScrollOffsetVertical + Bounds.Height - leaveSpace) + else if (idx >= ScrollOffsetVertical + Viewport.Height - leaveSpace) { //if user has scrolled off bottom of visible tree - ScrollOffsetVertical = Math.Max (0, idx + 1 - (Bounds.Height - leaveSpace)); + ScrollOffsetVertical = Math.Max (0, idx + 1 - (Viewport.Height - leaveSpace)); } } @@ -868,12 +868,12 @@ public int GetContentWidth (bool visible) } // If control has no height to it then there is no visible area for content - if (Bounds.Height == 0) + if (Viewport.Height == 0) { return 0; } - return map.Skip (ScrollOffsetVertical).Take (Bounds.Height).Max (b => b.GetWidth (Driver)); + return map.Skip (ScrollOffsetVertical).Take (Viewport.Height).Max (b => b.GetWidth (Driver)); } return map.Max (b => b.GetWidth (Driver)); @@ -886,13 +886,13 @@ public int GetContentWidth (bool visible) /// If you have screen coordinates then use to translate these into the client area of /// the . /// - /// The row of the of the . + /// The row of the of the . /// The object currently displayed on this row or null. public T GetObjectOnRow (int row) { return HitTest (row)?.Model; } /// /// - /// Returns the Y coordinate within the of the tree at which + /// Returns the Y coordinate within the of the tree at which /// would be displayed or null if it is not currently exposed (e.g. its parent is collapsed). /// /// @@ -967,7 +967,7 @@ public void GoTo (T toSelect) public void GoToEnd () { IReadOnlyCollection> map = BuildLineMap (); - ScrollOffsetVertical = Math.Max (0, map.Count - Bounds.Height + 1); + ScrollOffsetVertical = Math.Max (0, map.Count - Viewport.Height + 1); SelectedObject = map.LastOrDefault ()?.Model; SetNeedsDisplay (); @@ -1129,12 +1129,12 @@ protected internal override bool OnMouseEvent (MouseEvent me) /// Moves the selection down by the height of the control (1 page). /// True if the navigation should add the covered nodes to the selected current selection. /// - public void MovePageDown (bool expandSelection = false) { AdjustSelection (Bounds.Height, expandSelection); } + public void MovePageDown (bool expandSelection = false) { AdjustSelection (Viewport.Height, expandSelection); } /// Moves the selection up by the height of the control (1 page). /// True if the navigation should add the covered nodes to the selected current selection. /// - public void MovePageUp (bool expandSelection = false) { AdjustSelection (-Bounds.Height, expandSelection); } + public void MovePageUp (bool expandSelection = false) { AdjustSelection (-Viewport.Height, expandSelection); } /// /// This event is raised when an object is activated e.g. by double clicking or pressing @@ -1143,7 +1143,7 @@ protected internal override bool OnMouseEvent (MouseEvent me) public event EventHandler> ObjectActivated; /// - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { if (roots is null) { @@ -1160,7 +1160,7 @@ public override void OnDrawContent (Rectangle contentArea) IReadOnlyCollection> map = BuildLineMap (); - for (var line = 0; line < Bounds.Height; line++) + for (var line = 0; line < Viewport.Height; line++) { int idxToRender = ScrollOffsetVertical + line; @@ -1168,14 +1168,14 @@ public override void OnDrawContent (Rectangle contentArea) if (idxToRender < map.Count) { // Render the line - map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Bounds.Width); + map.ElementAt (idxToRender).Draw (Driver, ColorScheme, line, Viewport.Width); } else { // Else clear the line to prevent stale symbols due to scrolling etc Move (0, line); Driver.SetAttribute (GetNormalColor ()); - Driver.AddStr (new string (' ', Bounds.Width)); + Driver.AddStr (new string (' ', Viewport.Width)); } } } @@ -1247,7 +1247,7 @@ public override void PositionCursor () int idx = map.IndexOf (b => b.Model.Equals (SelectedObject)); // if currently selected line is visible - if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Bounds.Height) + if (idx - ScrollOffsetVertical >= 0 && idx - ScrollOffsetVertical < Viewport.Height) { Move (0, idx - ScrollOffsetVertical); } diff --git a/Terminal.sln.DotSettings b/Terminal.sln.DotSettings index d04aeb7c3e..3bc1fab5d4 100644 --- a/Terminal.sln.DotSettings +++ b/Terminal.sln.DotSettings @@ -386,10 +386,15 @@ <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> <Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Private" Description="Static readonly fields (private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Instance" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Instance fields (not private)"><ElementKinds><Kind Name="FIELD" /><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static fields (not private)"><ElementKinds><Kind Name="FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> + <Policy><Descriptor Staticness="Static" AccessRightKinds="Protected, ProtectedInternal, Internal, Public, PrivateProtected" Description="Static readonly fields (not private)"><ElementKinds><Kind Name="READONLY_FIELD" /></ElementKinds></Descriptor><Policy Inspect="True" Prefix="_" Suffix="" Style="aaBb" /></Policy> True True True True + True True #FFCF9D32 True diff --git a/UICatalog/Scenarios/ASCIICustomButton.cs b/UICatalog/Scenarios/ASCIICustomButton.cs index 8051e08ea6..a1c5695332 100644 --- a/UICatalog/Scenarios/ASCIICustomButton.cs +++ b/UICatalog/Scenarios/ASCIICustomButton.cs @@ -86,14 +86,14 @@ public void CustomInitialize () var fillText = new StringBuilder (); - for (var i = 0; i < Bounds.Height; i++) + for (var i = 0; i < Viewport.Height; i++) { if (i > 0) { fillText.AppendLine (""); } - for (var j = 0; j < Bounds.Width; j++) + for (var j = 0; j < Viewport.Width; j++) { fillText.Append ("█"); } diff --git a/UICatalog/Scenarios/AdornmentExperiments.cs b/UICatalog/Scenarios/AdornmentExperiments.cs deleted file mode 100644 index a41c959613..0000000000 --- a/UICatalog/Scenarios/AdornmentExperiments.cs +++ /dev/null @@ -1,55 +0,0 @@ -using Terminal.Gui; - -namespace UICatalog.Scenarios; - -[ScenarioMetadata ("Adornment Experiments", "Playground for Adornment experiments")] -[ScenarioCategory ("Controls")] -public class AdornmentExperiments : Scenario -{ - private ViewDiagnosticFlags _diagnosticFlags; - - private View _frameView; - - public override void Init () - { - Application.Init (); - ConfigurationManager.Themes.Theme = Theme; - ConfigurationManager.Apply (); - Top = new (); - Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; - - _diagnosticFlags = View.Diagnostics; - //View.Diagnostics = ViewDiagnosticFlags.MouseEnter; - - _frameView = new View () - { - Title = "Frame View", - X = 0, - Y = 0, - Width = Dim.Percent (90), - Height = Dim.Percent (90), - CanFocus = true, - }; - Top.Add (_frameView); - _frameView.Initialized += FrameView_Initialized; - - Top.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; - } - - private void FrameView_Initialized (object sender, System.EventArgs e) - { - _frameView.Border.Thickness = new (1, 1, 1, 1); - _frameView.Padding.Thickness = new (0, 10, 0, 0); - _frameView.Padding.ColorScheme = Colors.ColorSchemes ["Error"]; - - var label = new Label () - { - Text = "In Padding", - X = Pos.Center (), - Y = 0, - BorderStyle = LineStyle.Dashed - }; - _frameView.Padding.Add (label); - } - -} diff --git a/UICatalog/Scenarios/Adornments.cs b/UICatalog/Scenarios/Adornments.cs index 302eda6236..27aefbddc7 100644 --- a/UICatalog/Scenarios/Adornments.cs +++ b/UICatalog/Scenarios/Adornments.cs @@ -13,15 +13,30 @@ public class Adornments : Scenario { private ViewDiagnosticFlags _diagnosticFlags; - public override void Init () + public override void Main () { Application.Init (); - ConfigurationManager.Themes.Theme = Theme; - ConfigurationManager.Apply (); - Top = new (); - Top.ColorScheme = Colors.ColorSchemes [TopLevelColorScheme]; - var view = new Window { Title = "The _Window" }; + _diagnosticFlags = View.Diagnostics; + + Window app = new () + { + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", + }; + + var editor = new AdornmentsEditor (); + app.Add (editor); + + var window = new Window + { + Title = "The _Window", + Arrangement = ViewArrangement.Movable, + X = Pos.Right(editor), + Width = Dim.Percent (60), + Height = Dim.Percent (80), + }; + app.Add (window); + var tf1 = new TextField { Width = 10, Text = "TextField" }; var color = new ColorPicker { Title = "BG", BoxHeight = 1, BoxWidth = 1, X = Pos.AnchorEnd (11) }; color.BorderStyle = LineStyle.RoundedDotted; @@ -40,7 +55,7 @@ public override void Init () var button = new Button { X = Pos.Center (), Y = Pos.Center (), Text = "Press me!" }; button.Accept += (s, e) => - MessageBox.Query (20, 7, "Hi", $"Am I a {view.GetType ().Name}?", "Yes", "No"); + MessageBox.Query (20, 7, "Hi", $"Am I a {window.GetType ().Name}?", "Yes", "No"); var label = new TextView { @@ -64,45 +79,39 @@ public override void Init () Text = "Label\nY=AnchorEnd(3),Height=Dim.Fill()" }; - view.Margin.Data = "Margin"; - view.Margin.Thickness = new (3); + window.Margin.Data = "Margin"; + window.Margin.Thickness = new (3); - view.Border.Data = "Border"; - view.Border.Thickness = new (3); + window.Border.Data = "Border"; + window.Border.Thickness = new (3); - view.Padding.Data = "Padding"; - view.Padding.Thickness = new (3); + window.Padding.Data = "Padding"; + window.Padding.Thickness = new (3); - view.Add (tf1, color, button, label, btnButtonInWindow, tv); - - var editor = new AdornmentsEditor + var longLabel = new Label () { - Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", - ColorScheme = Colors.ColorSchemes [TopLevelColorScheme] + X = 40, Y = 5, Title = "This is long text (in a label) that should clip.", - //BorderStyle = LineStyle.None, }; - view.X = 36; - view.Y = 0; - view.Width = Dim.Percent (60); - view.Height = Dim.Percent (80); + longLabel.TextFormatter.WordWrap = true; + window.Add (tf1, color, button, label, btnButtonInWindow, tv, longLabel); - editor.Initialized += (s, e) => { editor.ViewToEdit = view; }; + editor.Initialized += (s, e) => { editor.ViewToEdit = window; }; - view.Initialized += (s, e) => + window.Initialized += (s, e) => { - var labelInPadding = new Label () { X = 1, Y = 0, Title = "_Text:" }; - view.Padding.Add (labelInPadding); + var labelInPadding = new Label () { X = 1, Y = 0, Title = "_Text:" }; + window.Padding.Add (labelInPadding); var textFieldInPadding = new TextField () { X = Pos.Right (labelInPadding) + 1, Y = Pos.Top (labelInPadding), Width = 15, Text = "some text" }; textFieldInPadding.Accept += (s, e) => MessageBox.Query (20, 7, "TextField", textFieldInPadding.Text, "Ok"); - view.Padding.Add (textFieldInPadding); + window.Padding.Add (textFieldInPadding); var btnButtonInPadding = new Button { X = Pos.Center (), Y = 0, Text = "_Button in Padding" }; btnButtonInPadding.Accept += (s, e) => MessageBox.Query (20, 7, "Hi", "Button in Padding Pressed!", "Ok"); btnButtonInPadding.BorderStyle = LineStyle.Dashed; - btnButtonInPadding.Border.Thickness = new (1,1,1,1); - view.Padding.Add (btnButtonInPadding); + btnButtonInPadding.Border.Thickness = new (1, 1, 1, 1); + window.Padding.Add (btnButtonInPadding); #if SUBVIEW_BASED_BORDER btnButtonInPadding.Border.CloseButton.Visible = true; @@ -118,16 +127,17 @@ public override void Init () #endif }; - Top.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; + app.Closed += (s, e) => View.Diagnostics = _diagnosticFlags; - Application.Run (editor); - editor.Dispose (); + Application.Run (app); + app.Dispose (); Application.Shutdown (); } - public override void Run () { } - + /// + /// Provides a composable UI for editing the settings of an Adornment. + /// public class AdornmentEditor : View { private readonly ColorPicker _backgroundColorPicker = new () @@ -148,12 +158,12 @@ public class AdornmentEditor : View SuperViewRendersLineCanvas = true }; - private TextField _bottomEdit; - private bool _isUpdating; - private TextField _leftEdit; - private TextField _rightEdit; + private Buttons.NumericUpDown _topEdit; + private Buttons.NumericUpDown _leftEdit; + private Buttons.NumericUpDown _bottomEdit; + private Buttons.NumericUpDown _rightEdit; private Thickness _thickness; - private TextField _topEdit; + private bool _isUpdating; public AdornmentEditor () { @@ -161,7 +171,6 @@ public AdornmentEditor () BorderStyle = LineStyle.Double; Initialized += AdornmentEditor_Initialized; } - public Attribute Color { get => new (_foregroundColorPicker.SelectedColor, _backgroundColorPicker.SelectedColor); @@ -183,31 +192,15 @@ public Thickness Thickness } _thickness = value; - ThicknessChanged?.Invoke (this, new() { Thickness = Thickness }); + ThicknessChanged?.Invoke (this, new () { Thickness = Thickness }); if (IsInitialized) { _isUpdating = true; - - if (_topEdit.Text != _thickness.Top.ToString ()) - { - _topEdit.Text = _thickness.Top.ToString (); - } - - if (_leftEdit.Text != _thickness.Left.ToString ()) - { - _leftEdit.Text = _thickness.Left.ToString (); - } - - if (_rightEdit.Text != _thickness.Right.ToString ()) - { - _rightEdit.Text = _thickness.Right.ToString (); - } - - if (_bottomEdit.Text != _thickness.Bottom.ToString ()) - { - _bottomEdit.Text = _thickness.Bottom.ToString (); - } + _topEdit.Value = _thickness.Top; + _leftEdit.Value = _thickness.Left; + _rightEdit.Value = _thickness.Right; + _bottomEdit.Value = _thickness.Bottom; _isUpdating = false; } @@ -219,49 +212,44 @@ public Thickness Thickness private void AdornmentEditor_Initialized (object sender, EventArgs e) { - var editWidth = 3; - - _topEdit = new() { X = Pos.Center (), Y = 0, Width = editWidth }; + _topEdit = new () + { + X = Pos.Center (), Y = 0 + }; - _topEdit.Accept += Edit_Accept; + _topEdit.ValueChanging += Top_ValueChanging; Add (_topEdit); - _leftEdit = new() + _leftEdit = new () { - X = Pos.Left (_topEdit) - editWidth, Y = Pos.Bottom (_topEdit), Width = editWidth + X = Pos.Left (_topEdit) - Pos.Function (() => _topEdit.Digits) - 2, Y = Pos.Bottom (_topEdit) }; - _leftEdit.Accept += Edit_Accept; + _leftEdit.ValueChanging += Left_ValueChanging; Add (_leftEdit); - _rightEdit = new() { X = Pos.Right (_topEdit), Y = Pos.Bottom (_topEdit), Width = editWidth }; + _rightEdit = new () { X = Pos.Right (_leftEdit) + 5, Y = Pos.Bottom (_topEdit) }; - _rightEdit.Accept += Edit_Accept; + _rightEdit.ValueChanging += Right_ValueChanging; Add (_rightEdit); - _bottomEdit = new() { X = Pos.Center (), Y = Pos.Bottom (_leftEdit), Width = editWidth }; + _bottomEdit = new () { X = Pos.Center (), Y = Pos.Bottom (_leftEdit) }; - _bottomEdit.Accept += Edit_Accept; + _bottomEdit.ValueChanging += Bottom_ValueChanging; Add (_bottomEdit); - var copyTop = new Button { X = Pos.Center () + 1, Y = Pos.Bottom (_bottomEdit), Text = "Cop_y Top" }; + var copyTop = new Button { X = Pos.Center (), Y = Pos.Bottom (_bottomEdit), Text = "Cop_y Top" }; copyTop.Accept += (s, e) => { Thickness = new (Thickness.Top); - - if (string.IsNullOrEmpty (_topEdit.Text)) - { - _topEdit.Text = "0"; - } - - _bottomEdit.Text = _leftEdit.Text = _rightEdit.Text = _topEdit.Text; + _leftEdit.Value = _rightEdit.Value = _bottomEdit.Value = _topEdit.Value; }; Add (copyTop); // Foreground ColorPicker. _foregroundColorPicker.X = -1; - _foregroundColorPicker.Y = Pos.Bottom (copyTop) + 1; + _foregroundColorPicker.Y = Pos.Bottom (copyTop); _foregroundColorPicker.SelectedColor = Color.Foreground.GetClosestNamedColor (); _foregroundColorPicker.ColorChanged += (o, a) => @@ -289,29 +277,69 @@ private void AdornmentEditor_Initialized (object sender, EventArgs e) ); Add (_backgroundColorPicker); - _topEdit.Text = $"{Thickness.Top}"; - _leftEdit.Text = $"{Thickness.Left}"; - _rightEdit.Text = $"{Thickness.Right}"; - _bottomEdit.Text = $"{Thickness.Bottom}"; + _topEdit.Value = Thickness.Top; + _leftEdit.Value = Thickness.Left; + _rightEdit.Value = Thickness.Right; + _bottomEdit.Value = Thickness.Bottom; LayoutSubviews (); - Height = GetAdornmentsThickness ().Vertical + 4 + 4; + Height = GetAdornmentsThickness ().Vertical + 4 + 3; Width = GetAdornmentsThickness ().Horizontal + _foregroundColorPicker.Frame.Width * 2 - 3; } - private void Edit_Accept (object sender, CancelEventArgs e) + private void Top_ValueChanging (object sender, StateEventArgs e) { - e.Cancel = true; + if (e.NewValue < 0) + { + e.Cancel = true; - Thickness = new ( - int.Parse (_leftEdit.Text), - int.Parse (_topEdit.Text), - int.Parse (_rightEdit.Text), - int.Parse (_bottomEdit.Text)); + return; + } + + Thickness.Top = e.NewValue; + } + + private void Left_ValueChanging (object sender, StateEventArgs e) + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + Thickness.Left = e.NewValue; + } + + private void Right_ValueChanging (object sender, StateEventArgs e) + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + Thickness.Right = e.NewValue; + } + + private void Bottom_ValueChanging (object sender, StateEventArgs e) + { + if (e.NewValue < 0) + { + e.Cancel = true; + + return; + } + + Thickness.Bottom = e.NewValue; } } - public class AdornmentsEditor : Window + /// + /// Provides an editor UI for the Margin, Border, and Padding of a View. + /// + public class AdornmentsEditor : View { private AdornmentEditor _borderEditor; private CheckBox _diagCheckBox; @@ -320,6 +348,15 @@ public class AdornmentsEditor : Window private AdornmentEditor _paddingEditor; private View _viewToEdit; + public AdornmentsEditor () + { + ColorScheme = Colors.ColorSchemes ["Dialog"]; + + // TOOD: Use Dim.Auto + Width = 36; + Height = Dim.Fill (); + } + public View ViewToEdit { get => _viewToEdit; @@ -328,26 +365,26 @@ public View ViewToEdit _origTitle = value.Title; _viewToEdit = value; - _marginEditor = new() + _marginEditor = new () { X = 0, Y = 0, Title = "_Margin", Thickness = _viewToEdit.Margin.Thickness, - Color = new (_viewToEdit.Margin.ColorScheme.Normal), + Color = new (_viewToEdit.Margin.ColorScheme?.Normal ?? ColorScheme.Normal), SuperViewRendersLineCanvas = true }; _marginEditor.ThicknessChanged += Editor_ThicknessChanged; _marginEditor.AttributeChanged += Editor_AttributeChanged; Add (_marginEditor); - _borderEditor = new() + _borderEditor = new () { X = Pos.Left (_marginEditor), Y = Pos.Bottom (_marginEditor), Title = "B_order", Thickness = _viewToEdit.Border.Thickness, - Color = new (_viewToEdit.Border.ColorScheme.Normal), + Color = new (_viewToEdit.Border.ColorScheme?.Normal ?? ColorScheme.Normal), SuperViewRendersLineCanvas = true }; _borderEditor.ThicknessChanged += Editor_ThicknessChanged; @@ -411,7 +448,7 @@ public View ViewToEdit { if (ckbTitle.Checked == true) { - _viewToEdit.Title = _origTitle; + //_viewToEdit.Title = _origTitle; } else { @@ -420,13 +457,13 @@ public View ViewToEdit }; Add (ckbTitle); - _paddingEditor = new() + _paddingEditor = new () { X = Pos.Left (_borderEditor), Y = Pos.Bottom (rbBorderStyle), Title = "_Padding", Thickness = _viewToEdit.Padding.Thickness, - Color = new (_viewToEdit.Padding.ColorScheme.Normal), + Color = new (_viewToEdit.Padding.ColorScheme?.Normal ?? ColorScheme.Normal), SuperViewRendersLineCanvas = true }; _paddingEditor.ThicknessChanged += Editor_ThicknessChanged; @@ -450,7 +487,6 @@ public View ViewToEdit }; Add (_diagCheckBox); - Add (_viewToEdit); _viewToEdit.LayoutComplete += (s, e) => { diff --git a/UICatalog/Scenarios/Animation.cs b/UICatalog/Scenarios/Animation.cs index e1a7391122..80bebdec2f 100644 --- a/UICatalog/Scenarios/Animation.cs +++ b/UICatalog/Scenarios/Animation.cs @@ -176,16 +176,16 @@ private class ImageView : View private Rectangle oldSize = Rectangle.Empty; public void NextFrame () { currentFrame = (currentFrame + 1) % frameCount; } - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - base.OnDrawContent (contentArea); + base.OnDrawContent (viewport); - if (oldSize != Bounds) + if (oldSize != Viewport) { // Invalidate cached images now size has changed matchSizes = new Image [frameCount]; brailleCache = new string [frameCount]; - oldSize = Bounds; + oldSize = Viewport; } Image imgScaled = matchSizes [currentFrame]; @@ -196,7 +196,7 @@ public override void OnDrawContent (Rectangle contentArea) Image imgFull = fullResImages [currentFrame]; // keep aspect ratio - int newSize = Math.Min (Bounds.Width, Bounds.Height); + int newSize = Math.Min (Viewport.Width, Viewport.Height); // generate one matchSizes [currentFrame] = imgScaled = imgFull.Clone ( diff --git a/UICatalog/Scenarios/BackgroundWorkerCollection.cs b/UICatalog/Scenarios/BackgroundWorkerCollection.cs index e6f51d6f94..e2d4b8c780 100644 --- a/UICatalog/Scenarios/BackgroundWorkerCollection.cs +++ b/UICatalog/Scenarios/BackgroundWorkerCollection.cs @@ -36,6 +36,7 @@ private class OverlappedMain : Toplevel public OverlappedMain () { + Arrangement = ViewArrangement.Movable; Data = "OverlappedMain"; IsOverlappedContainer = true; @@ -258,6 +259,8 @@ public StagingUIController (Staging staging, List list) : this () public StagingUIController () { + Arrangement = ViewArrangement.Movable; + X = Pos.Center (); Y = Pos.Center (); Width = Dim.Percent (85); @@ -303,7 +306,7 @@ public StagingUIController () LayoutStarted += (s, e) => { int btnsWidth = _start.Frame.Width + _close.Frame.Width + 2 - 1; - int shiftLeft = Math.Max ((Bounds.Width - btnsWidth) / 2 - 2, 0); + int shiftLeft = Math.Max ((Viewport.Width - btnsWidth) / 2 - 2, 0); shiftLeft += _close.Frame.Width + 1; _close.X = Pos.AnchorEnd (shiftLeft); @@ -338,6 +341,8 @@ private class WorkerApp : Toplevel public WorkerApp () { + Arrangement = ViewArrangement.Movable; + Data = "WorkerApp"; Title = "Worker collection Log"; diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs index 068c15a7c2..f405e063c7 100644 --- a/UICatalog/Scenarios/Buttons.cs +++ b/UICatalog/Scenarios/Buttons.cs @@ -1,5 +1,8 @@ -using System.Collections.Generic; +using System; +using System.Collections.Generic; +using System.ComponentModel; using System.Text; +using JetBrains.Annotations; using Terminal.Gui; namespace UICatalog.Scenarios; @@ -13,8 +16,9 @@ public override void Main () { Window main = new () { - Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}", + Title = $"{Application.QuitKey} to Quit - Scenario: {GetName ()}" }; + // Add a label & text field so we can demo IsDefault var editLabel = new Label { X = 0, Y = 0, TabStop = true, Text = "TextField (to demo IsDefault):" }; main.Add (editLabel); @@ -32,19 +36,19 @@ public override void Main () var swapButton = new Button { X = 50, Text = "S_wap Default (Absolute Layout)" }; swapButton.Accept += (s, e) => - { - defaultButton.IsDefault = !defaultButton.IsDefault; - swapButton.IsDefault = !swapButton.IsDefault; - }; + { + defaultButton.IsDefault = !defaultButton.IsDefault; + swapButton.IsDefault = !swapButton.IsDefault; + }; main.Add (swapButton); static void DoMessage (Button button, string txt) { button.Accept += (s, e) => - { - string btnText = button.Text; - MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No"); - }; + { + string btnText = button.Text; + MessageBox.Query ("Message", $"Did you click {txt}?", "Yes", "No"); + }; } var colorButtonsLabel = new Label { X = 0, Y = Pos.Bottom (editLabel) + 1, Text = "Color Buttons:" }; @@ -75,20 +79,20 @@ static void DoMessage (Button button, string txt) Button button; main.Add ( - button = new Button - { - X = 2, - Y = Pos.Bottom (colorButtonsLabel) + 1, - Text = - "A super l_öng Button that will probably expose a bug in clipping or wrapping of text. Will it?" - } - ); + button = new () + { + X = 2, + Y = Pos.Bottom (colorButtonsLabel) + 1, + Text = + "A super l_öng Button that will probably expose a bug in clipping or wrapping of text. Will it?" + } + ); DoMessage (button, button.Text); // Note the 'N' in 'Newline' will be the hotkey main.Add ( - button = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "a Newline\nin the button" } - ); + button = new () { X = 2, Y = Pos.Bottom (button) + 1, Text = "a Newline\nin the button" } + ); button.Accept += (s, e) => MessageBox.Query ("Message", "Question?", "Yes", "No"); var textChanger = new Button { X = 2, Y = Pos.Bottom (button) + 1, Text = "Te_xt Changer" }; @@ -96,13 +100,13 @@ static void DoMessage (Button button, string txt) textChanger.Accept += (s, e) => textChanger.Text += "!"; main.Add ( - button = new Button - { - X = Pos.Right (textChanger) + 2, - Y = Pos.Y (textChanger), - Text = "Lets see if this will move as \"Text Changer\" grows" - } - ); + button = new () + { + X = Pos.Right (textChanger) + 2, + Y = Pos.Y (textChanger), + Text = "Lets see if this will move as \"Text Changer\" grows" + } + ); var removeButton = new Button { @@ -112,12 +116,12 @@ static void DoMessage (Button button, string txt) // This in interesting test case because `moveBtn` and below are laid out relative to this one! removeButton.Accept += (s, e) => - { - // Now this throw a InvalidOperationException on the TopologicalSort method as is expected. - //main.Remove (removeButton); + { + // Now this throw a InvalidOperationException on the TopologicalSort method as is expected. + //main.Remove (removeButton); - removeButton.Visible = false; - }; + removeButton.Visible = false; + }; var computedFrame = new FrameView { @@ -142,12 +146,12 @@ static void DoMessage (Button button, string txt) }; moveBtn.Accept += (s, e) => - { - moveBtn.X = moveBtn.Frame.X + 5; + { + moveBtn.X = moveBtn.Frame.X + 5; - // This is already fixed with the call to SetNeedDisplay() in the Pos Dim. - //computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly - }; + // This is already fixed with the call to SetNeedDisplay() in the Pos Dim. + //computedFrame.LayoutSubviews (); // BUGBUG: This call should not be needed. View.X is not causing relayout correctly + }; computedFrame.Add (moveBtn); // Demonstrates how changing the View.Frame property can SIZE Views (#583) @@ -163,11 +167,11 @@ static void DoMessage (Button button, string txt) }; sizeBtn.Accept += (s, e) => - { - sizeBtn.Width = sizeBtn.Frame.Width + 5; + { + sizeBtn.Width = sizeBtn.Frame.Width + 5; - //computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly - }; + //computedFrame.LayoutSubviews (); // FIXED: This call should not be needed. View.X is not causing relayout correctly + }; computedFrame.Add (sizeBtn); var absoluteFrame = new FrameView @@ -184,14 +188,14 @@ static void DoMessage (Button button, string txt) var moveBtnA = new Button { ColorScheme = Colors.ColorSchemes ["Error"], Text = "Move This Button via Frame" }; moveBtnA.Accept += (s, e) => - { - moveBtnA.Frame = new Rectangle ( - moveBtnA.Frame.X + 5, - moveBtnA.Frame.Y, - moveBtnA.Frame.Width, - moveBtnA.Frame.Height - ); - }; + { + moveBtnA.Frame = new ( + moveBtnA.Frame.X + 5, + moveBtnA.Frame.Y, + moveBtnA.Frame.Width, + moveBtnA.Frame.Height + ); + }; absoluteFrame.Add (moveBtnA); // Demonstrates how changing the View.Frame property can SIZE Views (#583) @@ -201,14 +205,14 @@ static void DoMessage (Button button, string txt) }; sizeBtnA.Accept += (s, e) => - { - sizeBtnA.Frame = new Rectangle ( - sizeBtnA.Frame.X, - sizeBtnA.Frame.Y, - sizeBtnA.Frame.Width + 5, - sizeBtnA.Frame.Height - ); - }; + { + sizeBtnA.Frame = new ( + sizeBtnA.Frame.X, + sizeBtnA.Frame.Y, + sizeBtnA.Frame.Width + 5, + sizeBtnA.Frame.Height + ); + }; absoluteFrame.Add (sizeBtnA); var label = new Label @@ -289,154 +293,273 @@ string MoveHotkey (string txt) main.Add (moveUnicodeHotKeyBtn); radioGroup.SelectedItemChanged += (s, args) => - { - switch (args.SelectedItem) - { - case 0: - moveBtn.TextAlignment = TextAlignment.Left; - sizeBtn.TextAlignment = TextAlignment.Left; - moveBtnA.TextAlignment = TextAlignment.Left; - sizeBtnA.TextAlignment = TextAlignment.Left; - moveHotKeyBtn.TextAlignment = TextAlignment.Left; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left; - - break; - case 1: - moveBtn.TextAlignment = TextAlignment.Right; - sizeBtn.TextAlignment = TextAlignment.Right; - moveBtnA.TextAlignment = TextAlignment.Right; - sizeBtnA.TextAlignment = TextAlignment.Right; - moveHotKeyBtn.TextAlignment = TextAlignment.Right; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right; - - break; - case 2: - moveBtn.TextAlignment = TextAlignment.Centered; - sizeBtn.TextAlignment = TextAlignment.Centered; - moveBtnA.TextAlignment = TextAlignment.Centered; - sizeBtnA.TextAlignment = TextAlignment.Centered; - moveHotKeyBtn.TextAlignment = TextAlignment.Centered; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered; - - break; - case 3: - moveBtn.TextAlignment = TextAlignment.Justified; - sizeBtn.TextAlignment = TextAlignment.Justified; - moveBtnA.TextAlignment = TextAlignment.Justified; - sizeBtnA.TextAlignment = TextAlignment.Justified; - moveHotKeyBtn.TextAlignment = TextAlignment.Justified; - moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified; - - break; - } - }; - - label = new Label () + { + switch (args.SelectedItem) + { + case 0: + moveBtn.TextAlignment = TextAlignment.Left; + sizeBtn.TextAlignment = TextAlignment.Left; + moveBtnA.TextAlignment = TextAlignment.Left; + sizeBtnA.TextAlignment = TextAlignment.Left; + moveHotKeyBtn.TextAlignment = TextAlignment.Left; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Left; + + break; + case 1: + moveBtn.TextAlignment = TextAlignment.Right; + sizeBtn.TextAlignment = TextAlignment.Right; + moveBtnA.TextAlignment = TextAlignment.Right; + sizeBtnA.TextAlignment = TextAlignment.Right; + moveHotKeyBtn.TextAlignment = TextAlignment.Right; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Right; + + break; + case 2: + moveBtn.TextAlignment = TextAlignment.Centered; + sizeBtn.TextAlignment = TextAlignment.Centered; + moveBtnA.TextAlignment = TextAlignment.Centered; + sizeBtnA.TextAlignment = TextAlignment.Centered; + moveHotKeyBtn.TextAlignment = TextAlignment.Centered; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Centered; + + break; + case 3: + moveBtn.TextAlignment = TextAlignment.Justified; + sizeBtn.TextAlignment = TextAlignment.Justified; + moveBtnA.TextAlignment = TextAlignment.Justified; + sizeBtnA.TextAlignment = TextAlignment.Justified; + moveHotKeyBtn.TextAlignment = TextAlignment.Justified; + moveUnicodeHotKeyBtn.TextAlignment = TextAlignment.Justified; + + break; + } + }; + + label = new () { X = 0, Y = Pos.Bottom (moveUnicodeHotKeyBtn) + 1, Title = "_Numeric Up/Down (press-and-hold):", }; - var downButton = new Button () - { - CanFocus = false, - AutoSize = false, - X = Pos.Right(label)+1, - Y = Pos.Top (label), - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = $"{CM.Glyphs.DownArrow}", - WantContinuousButtonPressed = true, - }; - var numericEdit = new TextField () + var numericUpDown = new NumericUpDown { - Text = "1966", - X = Pos.Right (downButton), - Y = Pos.Top (downButton), + Value = 69, + X = Pos.Right (label) + 1, + Y = Pos.Top (label), Width = 5, - Height = 1, + Height = 1 }; - var upButton = new Button () - { - CanFocus = false, - AutoSize = false, - X = Pos.Right (numericEdit), - Y = Pos.Top (numericEdit), - Height = 1, - Width = 1, - NoPadding = true, - NoDecorations = true, - Title = $"{CM.Glyphs.UpArrow}", - WantContinuousButtonPressed = true, - }; - downButton.Accept += (s, e) => - { - numericEdit.Text = $"{int.Parse(numericEdit.Text) - 1}"; - }; - upButton.Accept += (s, e) => - { - numericEdit.Text = $"{int.Parse (numericEdit.Text) + 1}"; - }; + numericUpDown.ValueChanged += NumericUpDown_ValueChanged; - main.Add (label, downButton, numericEdit, upButton); + void NumericUpDown_ValueChanged (object sender, StateEventArgs e) { } - label = new Label () + main.Add (label, numericUpDown); + + label = new () { X = 0, - Y = Pos.Bottom (label) + 1, - Title = "_No Repeat:", + Y = Pos.Bottom (numericUpDown) + 1, + Title = "_No Repeat:" }; - int noRepeatAcceptCount = 0; - var noRepeatButton = new Button () + var noRepeatAcceptCount = 0; + + var noRepeatButton = new Button { X = Pos.Right (label) + 1, Y = Pos.Top (label), Title = $"Accept Cou_nt: {noRepeatAcceptCount}", - WantContinuousButtonPressed = false, + WantContinuousButtonPressed = false }; - noRepeatButton.Accept += (s, e) => - { - noRepeatButton.Title = $"Accept Cou_nt: {++noRepeatAcceptCount}"; - }; - main.Add(label, noRepeatButton); + noRepeatButton.Accept += (s, e) => { noRepeatButton.Title = $"Accept Cou_nt: {++noRepeatAcceptCount}"; }; + main.Add (label, noRepeatButton); - label = new Label () + label = new () { X = 0, Y = Pos.Bottom (label) + 1, - Title = "_Repeat (press-and-hold):", + Title = "_Repeat (press-and-hold):" }; - int acceptCount = 0; - var repeatButton = new Button () + var acceptCount = 0; + + var repeatButton = new Button { X = Pos.Right (label) + 1, Y = Pos.Top (label), Title = $"Accept Co_unt: {acceptCount}", - WantContinuousButtonPressed = true, + WantContinuousButtonPressed = true }; - repeatButton.Accept += (s, e) => - { - repeatButton.Title = $"Accept Co_unt: {++acceptCount}"; - }; + repeatButton.Accept += (s, e) => { repeatButton.Title = $"Accept Co_unt: {++acceptCount}"; }; - var enableCB = new CheckBox () + var enableCB = new CheckBox { X = Pos.Right (repeatButton) + 1, Y = Pos.Top (repeatButton), Title = "Enabled", - Checked = true, + Checked = true }; - enableCB.Toggled += (s, e) => - { - repeatButton.Enabled = !repeatButton.Enabled; - }; - main.Add(label, repeatButton, enableCB); + enableCB.Toggled += (s, e) => { repeatButton.Enabled = !repeatButton.Enabled; }; + main.Add (label, repeatButton, enableCB); main.Ready += (s, e) => radioGroup.Refresh (); Application.Run (main); main.Dispose (); } + + /// + /// Enables the user to increase or decrease a value by clicking on the up or down buttons. + /// + /// + /// Supports the following types: , , , , . + /// Supports only one digit of precision. + /// + public class NumericUpDown : View + { + private readonly Button _down; + // TODO: Use a TextField instead of a Label + private readonly View _number; + private readonly Button _up; + + public NumericUpDown () + { + Type type = typeof (T); + if (!(type == typeof (int) || type == typeof (long) || type == typeof (float) || type == typeof (double) || type == typeof (decimal))) + { + throw new InvalidOperationException ("T must be a numeric type that supports addition and subtraction."); + } + + // TODO: Use Dim.Auto for the Width and Height + Height = 1; + Width = Dim.Function (() => Digits + 2); // button + 3 for number + button + + _down = new () + { + AutoSize = false, + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = $"{CM.Glyphs.DownArrow}", + WantContinuousButtonPressed = true, + CanFocus = false, + }; + + _number = new () + { + Text = Value.ToString (), + AutoSize = false, + X = Pos.Right (_down), + Y = Pos.Top (_down), + Width = Dim.Function (() => Digits), + Height = 1, + TextAlignment = TextAlignment.Centered, + CanFocus = true + }; + + _up = new () + { + AutoSize = false, + X = Pos.AnchorEnd (1), + Y = Pos.Top (_number), + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = $"{CM.Glyphs.UpArrow}", + WantContinuousButtonPressed = true, + CanFocus = false, + }; + + CanFocus = true; + + _down.Accept += OnDownButtonOnAccept; + _up.Accept += OnUpButtonOnAccept; + + Add (_down, _number, _up); + + + AddCommand (Command.ScrollUp, () => + { + Value = (dynamic)Value + 1; + _number.Text = Value.ToString (); + + return true; + }); + AddCommand (Command.ScrollDown, () => + { + Value = (dynamic)Value - 1; + _number.Text = Value.ToString (); + + return true; + }); + + KeyBindings.Add (Key.CursorUp, Command.ScrollUp); + KeyBindings.Add (Key.CursorDown, Command.ScrollDown); + + return; + + void OnDownButtonOnAccept (object s, CancelEventArgs e) + { + InvokeCommand (Command.ScrollDown); + } + + void OnUpButtonOnAccept (object s, CancelEventArgs e) + { + InvokeCommand (Command.ScrollUp); + } + } + + private void _up_Enter (object sender, FocusEventArgs e) + { + throw new NotImplementedException (); + } + + private T _value; + + /// + /// The value that will be incremented or decremented. + /// + public T Value + { + get => _value; + set + { + if (_value.Equals (value)) + { + return; + } + + T oldValue = value; + StateEventArgs args = new StateEventArgs (_value, value); + ValueChanging?.Invoke (this, args); + + if (args.Cancel) + { + return; + } + + _value = value; + _number.Text = _value.ToString (); + ValueChanged?.Invoke (this, new (oldValue, _value)); + } + } + + /// + /// Fired when the value is about to change. Set to true to prevent the change. + /// + [CanBeNull] + public event EventHandler> ValueChanging; + + /// + /// Fired when the value has changed. + /// + [CanBeNull] + public event EventHandler> ValueChanged; + + /// + /// The number of digits to display. The will be resized to fit this number of characters plus the buttons. The default is 3. + /// + public int Digits { get; set; } = 3; + } } + diff --git a/UICatalog/Scenarios/CharacterMap.cs b/UICatalog/Scenarios/CharacterMap.cs index c6ff8d9c27..b0d7a03ca5 100644 --- a/UICatalog/Scenarios/CharacterMap.cs +++ b/UICatalog/Scenarios/CharacterMap.cs @@ -1,3 +1,5 @@ +#define OTHER_CONTROLS + using System; using System.Collections.Generic; using System.ComponentModel; @@ -17,12 +19,15 @@ namespace UICatalog.Scenarios; /// /// This Scenario demonstrates building a custom control (a class deriving from View) that: - Provides a /// "Character Map" application (like Windows' charmap.exe). - Helps test unicode character rendering in Terminal.Gui - -/// Illustrates how to use ScrollView to do infinite scrolling +/// Illustrates how to do infinite scrolling /// -[ScenarioMetadata ("Character Map", "Unicode viewer demonstrating the ScrollView control.")] +[ScenarioMetadata ("Character Map", "Unicode viewer demonstrating infinite content, scrolling, and Unicode.")] [ScenarioCategory ("Text and Formatting")] +[ScenarioCategory ("Drawing")] [ScenarioCategory ("Controls")] -[ScenarioCategory ("ScrollView")] +[ScenarioCategory ("Layout")] +[ScenarioCategory ("Scrolling")] + public class CharacterMap : Scenario { public Label _errorLabel; @@ -30,17 +35,26 @@ public class CharacterMap : Scenario private CharMap _charMap; // Don't create a Window, just return the top-level view - public override void Init () + public override void Main () { Application.Init (); - Top = new (); - Top.ColorScheme = Colors.ColorSchemes ["Base"]; - } - public override void Setup () - { - _charMap = new() { X = 0, Y = 1, Height = Dim.Fill () }; - Top.Add (_charMap); + var top = new Window + { + BorderStyle = LineStyle.None + }; + + _charMap = new () + { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + top.Add (_charMap); + +#if OTHER_CONTROLS + _charMap.Y = 1; var jumpLabel = new Label { @@ -49,19 +63,19 @@ public override void Setup () HotKeySpecifier = (Rune)'_', Text = "_Jump To Code Point:" }; - Top.Add (jumpLabel); + top.Add (jumpLabel); var jumpEdit = new TextField { X = Pos.Right (jumpLabel) + 1, Y = Pos.Y (_charMap), Width = 10, Caption = "e.g. 01BE3" }; - Top.Add (jumpEdit); + top.Add (jumpEdit); - _errorLabel = new() + _errorLabel = new () { X = Pos.Right (jumpEdit) + 1, Y = Pos.Y (_charMap), ColorScheme = Colors.ColorSchemes ["error"], Text = "err" }; - Top.Add (_errorLabel); + top.Add (_errorLabel); #if TEXT_CHANGED_TO_JUMP jumpEdit.TextChanged += JumpEdit_TextChanged; @@ -76,8 +90,7 @@ void JumpEditOnAccept (object sender, CancelEventArgs e) e.Cancel = true; } #endif - _categoryList = new() { X = Pos.Right (_charMap), Y = Pos.Bottom (jumpLabel), Height = Dim.Fill () }; - + _categoryList = new () { X = Pos.Right (_charMap), Y = Pos.Bottom (jumpLabel), Height = Dim.Fill () }; _categoryList.FullRowSelect = true; //jumpList.Style.ShowHeaders = false; @@ -120,10 +133,10 @@ void JumpEditOnAccept (object sender, CancelEventArgs e) _categoryList.Style.ColumnStyles.Add ( 0, - new() { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName } + new () { MaxWidth = longestName, MinWidth = longestName, MinAcceptableWidth = longestName } ); - _categoryList.Style.ColumnStyles.Add (1, new() { MaxWidth = 1, MinWidth = 6 }); - _categoryList.Style.ColumnStyles.Add (2, new() { MaxWidth = 1, MinWidth = 6 }); + _categoryList.Style.ColumnStyles.Add (1, new () { MaxWidth = 1, MinWidth = 6 }); + _categoryList.Style.ColumnStyles.Add (2, new () { MaxWidth = 1, MinWidth = 6 }); _categoryList.Width = _categoryList.Style.ColumnStyles.Sum (c => c.Value.MinWidth) + 4; @@ -133,10 +146,7 @@ void JumpEditOnAccept (object sender, CancelEventArgs e) _charMap.StartCodePoint = table.Data.ToArray () [args.NewRow].Start; }; - Top.Add (_categoryList); - - _charMap.SelectedCodePoint = 0; - _charMap.SetFocus (); + top.Add (_categoryList); // TODO: Replace this with Dim.Auto when that's ready _categoryList.Initialized += _categoryList_Initialized; @@ -162,7 +172,14 @@ void JumpEditOnAccept (object sender, CancelEventArgs e) ) ] }; - Top.Add (menu); + top.Add (menu); +#endif // OTHER_CONTROLS + + _charMap.SelectedCodePoint = 0; + _charMap.SetFocus (); + + Application.Run (top); + top.Dispose (); } private void _categoryList_Initialized (object sender, EventArgs e) { _charMap.Width = Dim.Fill () - _categoryList.Width; } @@ -203,7 +220,7 @@ private EnumerableTableSource CreateCategoryTable (int sortByColum return new ( sortedRanges, - new() + new () { { $"Category{categorySort}", s => s.Category }, { $"Start{startSort}", s => $"{s.Start:x5}" }, @@ -296,7 +313,7 @@ private void JumpEdit_TextChanged (object sender, StateEventArgs e) } } -internal class CharMap : ScrollView +internal class CharMap : View { private const CursorVisibility _cursor = CursorVisibility.Default; private const int COLUMN_WIDTH = 3; @@ -311,10 +328,7 @@ public CharMap () ColorScheme = Colors.ColorSchemes ["Dialog"]; CanFocus = true; - ContentSize = new ( - RowWidth, - (MaxCodePoint / 16 + (ShowHorizontalScrollIndicator ? 2 : 1)) * _rowHeight - ); + ContentSize = new (RowWidth, (MaxCodePoint / 16 + 2) * _rowHeight); AddCommand ( Command.ScrollUp, @@ -325,6 +339,8 @@ public CharMap () SelectedCodePoint -= 16; } + ScrollVertical (-_rowHeight); + return true; } ); @@ -333,11 +349,16 @@ public CharMap () Command.ScrollDown, () => { - if (SelectedCodePoint < MaxCodePoint - 16) + if (SelectedCodePoint <= MaxCodePoint - 16) { SelectedCodePoint += 16; } + if (Cursor.Y >= Viewport.Height) + { + ScrollVertical (_rowHeight); + } + return true; } ); @@ -351,6 +372,11 @@ public CharMap () SelectedCodePoint--; } + if (Cursor.X > RowLabelWidth + 1) + { + ScrollHorizontal (-COLUMN_WIDTH); + } + return true; } ); @@ -364,6 +390,11 @@ public CharMap () SelectedCodePoint++; } + if (Cursor.X >= Viewport.Width) + { + ScrollHorizontal (COLUMN_WIDTH); + } + return true; } ); @@ -372,8 +403,9 @@ public CharMap () Command.PageUp, () => { - int page = (Bounds.Height / _rowHeight - 1) * 16; + int page = (Viewport.Height - 1 / _rowHeight) * 16; SelectedCodePoint -= Math.Min (page, SelectedCodePoint); + Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; return true; } @@ -383,8 +415,9 @@ public CharMap () Command.PageDown, () => { - int page = (Bounds.Height / _rowHeight - 1) * 16; + int page = (Viewport.Height - 1 / _rowHeight) * 16; SelectedCodePoint += Math.Min (page, MaxCodePoint - SelectedCodePoint); + Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; return true; } @@ -405,11 +438,11 @@ public CharMap () () => { SelectedCodePoint = MaxCodePoint; + Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; return true; } ); - KeyBindings.Add (Key.Enter, Command.Accept); AddCommand ( Command.Accept, @@ -421,7 +454,116 @@ public CharMap () } ); + KeyBindings.Add (Key.Enter, Command.Accept); + 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); + MouseClick += Handle_MouseClick; + MouseEvent += Handle_MouseEvent; + + // Prototype scrollbars + Padding.Thickness = new (0, 0, 1, 1); + + var up = new Button + { + AutoSize = false, + X = Pos.AnchorEnd (1), + Y = 0, + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = CM.Glyphs.UpArrow.ToString (), + WantContinuousButtonPressed = true, + CanFocus = false + }; + up.Accept += (sender, args) => { args.Cancel = ScrollVertical (-1) == true; }; + + var down = new Button + { + AutoSize = false, + X = Pos.AnchorEnd (1), + Y = Pos.AnchorEnd (2), + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = CM.Glyphs.DownArrow.ToString (), + WantContinuousButtonPressed = true, + CanFocus = false + }; + down.Accept += (sender, args) => { ScrollVertical (1); }; + + var left = new Button + { + AutoSize = false, + X = 0, + Y = Pos.AnchorEnd (1), + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = CM.Glyphs.LeftArrow.ToString (), + WantContinuousButtonPressed = true, + CanFocus = false + }; + left.Accept += (sender, args) => { ScrollHorizontal (-1); }; + + var right = new Button + { + AutoSize = false, + X = Pos.AnchorEnd (2), + Y = Pos.AnchorEnd (1), + Height = 1, + Width = 1, + NoPadding = true, + NoDecorations = true, + Title = CM.Glyphs.RightArrow.ToString (), + WantContinuousButtonPressed = true, + CanFocus = false + }; + right.Accept += (sender, args) => { ScrollHorizontal (1); }; + + Padding.Add (up, down, left, right); + } + + private void Handle_MouseEvent (object sender, MouseEventEventArgs e) + { + if (e.MouseEvent.Flags == MouseFlags.WheeledDown) + { + ScrollVertical (1); + e.Handled = true; + + return; + } + + if (e.MouseEvent.Flags == MouseFlags.WheeledUp) + { + ScrollVertical (-1); + e.Handled = true; + + return; + } + + if (e.MouseEvent.Flags == MouseFlags.WheeledRight) + { + ScrollHorizontal (1); + e.Handled = true; + + return; + } + + if (e.MouseEvent.Flags == MouseFlags.WheeledLeft) + { + ScrollHorizontal (-1); + e.Handled = true; + } } /// Gets the coordinates of the Cursor based on the SelectedCodePoint in screen coordinates @@ -429,16 +571,16 @@ public Point Cursor { get { - int row = SelectedCodePoint / 16 * _rowHeight + ContentOffset.Y + 1; + int row = SelectedCodePoint / 16 * _rowHeight - Viewport.Y + 1; - int col = SelectedCodePoint % 16 * COLUMN_WIDTH + ContentOffset.X + RowLabelWidth + 1; // + 1 for padding + int col = SelectedCodePoint % 16 * COLUMN_WIDTH - Viewport.X + RowLabelWidth + 1; // + 1 for padding between label and first column return new (col, row); } set => throw new NotImplementedException (); } - public static int MaxCodePoint => 0x10FFFF; + public static int MaxCodePoint = UnicodeRange.Ranges.Max (r => r.End); /// /// Specifies the starting offset for the character map. The default is 0x2500 which is the Box Drawing @@ -449,6 +591,11 @@ public int SelectedCodePoint get => _selected; set { + if (_selected == value) + { + return; + } + _selected = value; if (IsInitialized) @@ -456,36 +603,28 @@ public int SelectedCodePoint int row = SelectedCodePoint / 16 * _rowHeight; int col = SelectedCodePoint % 16 * COLUMN_WIDTH; - int height = Bounds.Height - (ShowHorizontalScrollIndicator ? 2 : 1); - - if (row + ContentOffset.Y < 0) + if (row - Viewport.Y < 0) { // Moving up. - ContentOffset = new (ContentOffset.X, row); + Viewport = Viewport with { Y = row }; } - else if (row + ContentOffset.Y >= height) + else if (row - Viewport.Y >= Viewport.Height) { // Moving down. - ContentOffset = new ( - ContentOffset.X, - Math.Min (row, row - height + _rowHeight) - ); + Viewport = Viewport with { Y = row - Viewport.Height }; } - int width = Bounds.Width / COLUMN_WIDTH * COLUMN_WIDTH - (ShowVerticalScrollIndicator ? RowLabelWidth + 1 : RowLabelWidth); + int width = Viewport.Width / COLUMN_WIDTH * COLUMN_WIDTH - RowLabelWidth; - if (col + ContentOffset.X < 0) + if (col - Viewport.X < 0) { // Moving left. - ContentOffset = new (col, ContentOffset.Y); + Viewport = Viewport with { X = col }; } - else if (col + ContentOffset.X >= width) + else if (col - Viewport.X >= width) { // Moving right. - ContentOffset = new ( - Math.Min (col, col - width + COLUMN_WIDTH), - ContentOffset.Y - ); + Viewport = Viewport with { X = col - width }; } } @@ -515,6 +654,7 @@ public int StartCodePoint { _start = value; SelectedCodePoint = value; + Viewport = Viewport with { Y = SelectedCodePoint / 16 * _rowHeight }; SetNeedsDisplay (); } } @@ -523,98 +663,71 @@ public int StartCodePoint private static int RowWidth => RowLabelWidth + COLUMN_WIDTH * 16; public event EventHandler Hover; - public override void OnDrawContent (Rectangle contentArea) + public override void OnDrawContent (Rectangle viewport) { - if (contentArea.Height == 0 || contentArea.Width == 0) + if (viewport.Height == 0 || viewport.Width == 0) { return; } - // Call the base (ScrollView) to draw the scrollbars. Do this ahead of our own drawing so that - // any wide or tall glyphs actually render over the scrollbars (on platforms like Windows Terminal) that - // does this correctly. - base.OnDrawContent (contentArea); - - Rectangle viewport = new ( - ContentOffset, - new ( - Math.Max (Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0), 0), - Math.Max (Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0), 0) - ) - ); - - Rectangle oldClip = ClipToBounds (); - - if (ShowHorizontalScrollIndicator) - { - // ClipToBounds doesn't know about the scroll indicators, so if off, subtract one from height - Driver.Clip = new (Driver.Clip.Location, new (Driver.Clip.Size.Width, Driver.Clip.Size.Height - 1)); - } - - if (ShowVerticalScrollIndicator) - { - // ClipToBounds doesn't know about the scroll indicators, so if off, subtract one from width - Driver.Clip = new (Driver.Clip.Location, new (Driver.Clip.Size.Width - 1, Driver.Clip.Size.Height)); - } + Clear (); - int cursorCol = Cursor.X - ContentOffset.X - RowLabelWidth - 1; - int cursorRow = Cursor.Y - ContentOffset.Y - 1; + int cursorCol = Cursor.X + Viewport.X - RowLabelWidth - 1; + int cursorRow = Cursor.Y + Viewport.Y - 1; Driver.SetAttribute (GetHotNormalColor ()); Move (0, 0); Driver.AddStr (new (' ', RowLabelWidth + 1)); + int firstColumnX = RowLabelWidth - Viewport.X; + + // Header for (var hexDigit = 0; hexDigit < 16; hexDigit++) { - int x = ContentOffset.X + RowLabelWidth + hexDigit * COLUMN_WIDTH; + int x = firstColumnX + hexDigit * COLUMN_WIDTH; if (x > RowLabelWidth - 2) { Move (x, 0); Driver.SetAttribute (GetHotNormalColor ()); Driver.AddStr (" "); - - Driver.SetAttribute ( - HasFocus && cursorCol + ContentOffset.X + RowLabelWidth == x - ? ColorScheme.HotFocus - : GetHotNormalColor () - ); + Driver.SetAttribute (HasFocus && cursorCol + firstColumnX == x ? ColorScheme.HotFocus : GetHotNormalColor ()); Driver.AddStr ($"{hexDigit:x}"); Driver.SetAttribute (GetHotNormalColor ()); Driver.AddStr (" "); } } - int firstColumnX = viewport.X + RowLabelWidth; - // Even though the Clip is set to prevent us from drawing on the row potentially occupied by the horizontal // scroll bar, we do the smart thing and not actually draw that row if not necessary. - for (var y = 1; y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0); y++) + for (var y = 1; y < Viewport.Height; y++) { // What row is this? - int row = (y - ContentOffset.Y - 1) / _rowHeight; + int row = (y + Viewport.Y - 1) / _rowHeight; int val = row * 16; if (val > MaxCodePoint) { - continue; + break; } Move (firstColumnX + COLUMN_WIDTH, y); Driver.SetAttribute (GetNormalColor ()); - // Note, this code naïvely draws all columns, even if the viewport is smaller than - // the needed width. We rely on Clip to ensure we don't draw past the viewport. - // If we were *really* worried about performance, we'd optimize this code to only draw the - // parts of the row that are actually visible in the viewport. for (var col = 0; col < 16; col++) { int x = firstColumnX + COLUMN_WIDTH * col + 1; + if (x < 0 || x > Viewport.Width - 1) + { + continue; + } + Move (x, y); - if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) + // If we're at the cursor position, and we don't have focus, invert the colors. + if (row == cursorRow && x == cursorCol && !HasFocus) { Driver.SetAttribute (GetFocusColor ()); } @@ -629,9 +742,9 @@ public override void OnDrawContent (Rectangle contentArea) int width = rune.GetColumns (); - // are we at first row of the row? - if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) + if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0) { + // Draw the rune if (width > 0) { Driver.AddRune (rune); @@ -666,25 +779,24 @@ public override void OnDrawContent (Rectangle contentArea) } else { + // Draw the width of the rune Driver.SetAttribute (ColorScheme.HotNormal); Driver.AddStr ($"{width}"); } - if (cursorRow + ContentOffset.Y + 1 == y && cursorCol + ContentOffset.X + firstColumnX + 1 == x && !HasFocus) + // If we're at the cursor position, and we don't have focus, revert the colors to normal + if (row == cursorRow && x == cursorCol && !HasFocus) { Driver.SetAttribute (GetNormalColor ()); } } + // Draw row label (U+XXXX_) Move (0, y); - Driver.SetAttribute ( - HasFocus && cursorRow + ContentOffset.Y + 1 == y - ? ColorScheme.HotFocus - : ColorScheme.HotNormal - ); + Driver.SetAttribute (HasFocus && y + Viewport.Y - 1 == cursorRow ? ColorScheme.HotFocus : ColorScheme.HotNormal); - if (!ShowGlyphWidths || (y - ContentOffset.Y) % _rowHeight > 0) + if (!ShowGlyphWidths || (y + Viewport.Y) % _rowHeight > 0) { Driver.AddStr ($"U+{val / 16:x5}_ "); } @@ -693,8 +805,6 @@ public override void OnDrawContent (Rectangle contentArea) Driver.AddStr (new (' ', RowLabelWidth)); } } - - Driver.Clip = oldClip; } public override bool OnEnter (View view) @@ -718,9 +828,9 @@ public override void PositionCursor () { if (HasFocus && Cursor.X >= RowLabelWidth - && Cursor.X < Bounds.Width - (ShowVerticalScrollIndicator ? 1 : 0) + && Cursor.X < Viewport.Width && Cursor.Y > 0 - && Cursor.Y < Bounds.Height - (ShowHorizontalScrollIndicator ? 1 : 0)) + && Cursor.Y < Viewport.Height) { Driver.SetCursorVisibility (_cursor); Move (Cursor.X, Cursor.Y); @@ -760,6 +870,8 @@ private void Handle_MouseClick (object sender, MouseEventEventArgs args) return; } + args.Handled = true; + if (me.Y == 0) { me.Y = Cursor.Y; @@ -773,8 +885,8 @@ private void Handle_MouseClick (object sender, MouseEventEventArgs args) me.X = Cursor.X; } - int row = (me.Y - 1 - ContentOffset.Y) / _rowHeight; // -1 for header - int col = (me.X - RowLabelWidth - ContentOffset.X) / COLUMN_WIDTH; + int row = (me.Y - 1 - -Viewport.Y) / _rowHeight; // -1 for header + int col = (me.X - RowLabelWidth - -Viewport.X) / COLUMN_WIDTH; if (col > 15) { @@ -812,7 +924,7 @@ private void Handle_MouseClick (object sender, MouseEventEventArgs args) { SelectedCodePoint = val; - _contextMenu = new() + _contextMenu = new () { Position = new (me.X + 1, me.Y + 1), MenuItems = new ( @@ -846,6 +958,7 @@ private void ShowDetails () { var client = new UcdApiClient (); var decResponse = string.Empty; + var getCodePointError = string.Empty; var waitIndicator = new Dialog { @@ -854,7 +967,7 @@ private void ShowDetails () Y = Pos.Center (), Height = 7, Width = 50, - Buttons = [new() { Text = "Cancel" }] + Buttons = [new () { Text = "Cancel" }] }; var errorLabel = new Label @@ -877,12 +990,13 @@ private void ShowDetails () try { decResponse = await client.GetCodepointDec (SelectedCodePoint); + Application.Invoke (() => waitIndicator.RequestStop ()); } catch (HttpRequestException e) { + getCodePointError = errorLabel.Text = e.Message; Application.Invoke (() => waitIndicator.RequestStop ()); } - }; Application.Run (waitIndicator); waitIndicator.Dispose (); @@ -939,46 +1053,46 @@ private void ShowDetails () var label = new Label { Text = "IsAscii: ", X = 0, Y = 0 }; dlg.Add (label); - label = new() { Text = $"{rune.IsAscii}", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = $"{rune.IsAscii}", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = ", Bmp: ", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = ", Bmp: ", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = $"{rune.IsBmp}", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = $"{rune.IsBmp}", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = ", CombiningMark: ", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = ", CombiningMark: ", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = $"{rune.IsCombiningMark ()}", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = $"{rune.IsCombiningMark ()}", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = ", SurrogatePair: ", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = ", SurrogatePair: ", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = $"{rune.IsSurrogatePair ()}", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = $"{rune.IsSurrogatePair ()}", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = ", Plane: ", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = ", Plane: ", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = $"{rune.Plane}", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = $"{rune.Plane}", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = "Columns: ", X = 0, Y = Pos.Bottom (label) }; + label = new () { Text = "Columns: ", X = 0, Y = Pos.Bottom (label) }; dlg.Add (label); - label = new() { Text = $"{rune.GetColumns ()}", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = $"{rune.GetColumns ()}", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = ", Utf16SequenceLength: ", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = ", Utf16SequenceLength: ", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() { Text = $"{rune.Utf16SequenceLength}", X = Pos.Right (label), Y = Pos.Top (label) }; + label = new () { Text = $"{rune.Utf16SequenceLength}", X = Pos.Right (label), Y = Pos.Top (label) }; dlg.Add (label); - label = new() + label = new () { Text = $"Code Point Information from {UcdApiClient.BaseUrl}codepoint/dec/{SelectedCodePoint}:", diff --git a/UICatalog/Scenarios/Clipping.cs b/UICatalog/Scenarios/Clipping.cs index 4cdaff4bba..0aba6ece9a 100644 --- a/UICatalog/Scenarios/Clipping.cs +++ b/UICatalog/Scenarios/Clipping.cs @@ -4,6 +4,8 @@ namespace UICatalog.Scenarios; [ScenarioMetadata ("Clipping", "Used to test that things clip correctly")] [ScenarioCategory ("Tests")] +[ScenarioCategory ("Drawing")] +[ScenarioCategory ("Scrolling")] public class Clipping : Scenario { public override void Init () @@ -30,8 +32,9 @@ public override void Setup () scrollView.ContentSize = new (200, 100); //ContentOffset = Point.Empty, - //scrollView.ShowVerticalScrollIndicator = true; - //scrollView.ShowHorizontalScrollIndicator = true; + scrollView.AutoHideScrollBars = true; + scrollView.ShowVerticalScrollIndicator = true; + scrollView.ShowHorizontalScrollIndicator = true; var embedded1 = new View { diff --git a/UICatalog/Scenarios/ColorPicker.cs b/UICatalog/Scenarios/ColorPicker.cs index 32fdd26d3c..8ae76c5dc6 100644 --- a/UICatalog/Scenarios/ColorPicker.cs +++ b/UICatalog/Scenarios/ColorPicker.cs @@ -44,8 +44,8 @@ public override void Setup () backgroundColorPicker = new ColorPicker { Title = "Background Color", - Y = Pos.Center (), - X = Pos.Center (), + // TODO: Replace with Pos.AnchorEnd () when #2900 is done + X = Pos.AnchorEnd ((8 * 4) + 2), // 8 box * 4 width + 2 for border BoxHeight = 1, BoxWidth = 4, BorderStyle = LineStyle.Single diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs index 682e89a5b7..d51311c90c 100644 --- a/UICatalog/Scenarios/ComputedLayout.cs +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -57,12 +57,12 @@ public override void Setup () Top.LayoutComplete += (s, a) => { horizontalRuler.Text = - rule.Repeat ((int)Math.Ceiling (horizontalRuler.Bounds.Width / (double)rule.Length)) [ - ..horizontalRuler.Bounds.Width]; + rule.Repeat ((int)Math.Ceiling (horizontalRuler.Viewport.Width / (double)rule.Length)) [ + ..horizontalRuler.Viewport.Width]; verticalRuler.Text = - vrule.Repeat ((int)Math.Ceiling (verticalRuler.Bounds.Height * 2 / (double)rule.Length)) - [..(verticalRuler.Bounds.Height * 2)]; + vrule.Repeat ((int)Math.Ceiling (verticalRuler.Viewport.Height * 2 / (double)rule.Length)) + [..(verticalRuler.Viewport.Height * 2)]; }; Top.Add (verticalRuler); diff --git a/UICatalog/Scenarios/ContentScrolling.cs b/UICatalog/Scenarios/ContentScrolling.cs new file mode 100644 index 0000000000..67f61d3c8b --- /dev/null +++ b/UICatalog/Scenarios/ContentScrolling.cs @@ -0,0 +1,402 @@ +using System; +using System.ComponentModel; +using System.Linq; +using Terminal.Gui; +using static UICatalog.Scenarios.Adornments; + +namespace UICatalog.Scenarios; + +[ScenarioMetadata ("Content Scrolling", "Demonstrates using View.Viewport and View.ContentSize to scroll content.")] +[ScenarioCategory ("Layout")] +[ScenarioCategory ("Drawing")] +[ScenarioCategory ("Scrolling")] +public class ContentScrolling : Scenario +{ + private ViewDiagnosticFlags _diagnosticFlags; + + public class ScrollingDemoView : FrameView + { + public ScrollingDemoView () + { + Width = Dim.Fill (); + Height = Dim.Fill (); + ColorScheme = Colors.ColorSchemes ["Base"]; + Text = "Text (ScrollingDemoView.Text). This is long text.\nThe second line.\n3\n4\n5th line\nLine 6. This is a longer line that should wrap automatically."; + CanFocus = true; + BorderStyle = LineStyle.Rounded; + Arrangement = ViewArrangement.Fixed; + + ContentSize = new (60, 40); + ViewportSettings |= ViewportSettings.ClearContentOnly; + ViewportSettings |= ViewportSettings.ClipContentOnly; + + // 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)); + + // 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); + + // Add a status label to the border that shows Viewport and ContentSize values. Bit of a hack. + // TODO: Move to Padding with controls + Border.Add (new Label { AutoSize = false, X = 20 }); + 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); + } + } + + private void VirtualDemoView_LayoutComplete (object sender, LayoutEventArgs e) + { + Label status = Border.Subviews.OfType