diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs index 8d0cd9314..e9e2af5ce 100644 --- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs @@ -30,10 +30,6 @@ public abstract class ConsoleDriver /// public virtual string GetVersionInfo () { return GetType ().Name; } - /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. - /// This is only implemented in . - public abstract void Suspend (); - #region ANSI Esc Sequence Handling // QUESTION: Should this be virtual with a default implementation that does the common stuff? @@ -94,9 +90,6 @@ public abstract class ConsoleDriver } } - /// Updates the screen to reflect all the changes that have been done to the display buffer - public abstract void Refresh (); - /// /// Gets the column last set by . and are used by /// and to determine where to add content. @@ -181,28 +174,6 @@ public abstract class ConsoleDriver /// The topmost row in the terminal. internal virtual int Top { get; set; } = 0; - private Rectangle _clip; - - /// - /// Gets or sets the clip rectangle that and are subject - /// to. - /// - /// 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); - } - } - /// Adds the specified rune to the display at the current cursor position. /// /// @@ -420,19 +391,23 @@ public abstract class ConsoleDriver /// /// The Screen-relative rectangle. /// The Rune used to fill the rectangle - public void FillRect (Rectangle rect, Rune rune = default) + internal void FillRect (Rectangle rect, Rune rune = default) { - rect = Rectangle.Intersect (rect, Clip); - + // BUGBUG: This should be a method on Region + rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen); lock (Contents!) { for (int r = rect.Y; r < rect.Y + rect.Height; r++) { for (int c = rect.X; c < rect.X + rect.Width; c++) { - Contents [r, c] = new () + if (!IsValidLocation (rune, c, r)) { - Rune = rune != default (Rune) ? rune : (Rune)' ', + continue; + } + Contents [r, c] = new Cell + { + Rune = (rune != default ? rune : (Rune)' '), Attribute = CurrentAttribute, IsDirty = true }; _dirtyLines! [r] = true; @@ -498,41 +473,6 @@ public abstract class ConsoleDriver } } - /// Determines if the terminal cursor should be visible or not and sets it accordingly. - /// upon success - public abstract bool EnsureCursorVisibility (); - - /// 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 - internal void FillRect (Rectangle rect, Rune rune = default) - { - // BUGBUG: This should be a method on Region - rect = Rectangle.Intersect (rect, Clip?.GetBounds () ?? Screen); - lock (Contents!) - { - for (int r = rect.Y; r < rect.Y + rect.Height; r++) - { - for (int c = rect.X; c < rect.X + rect.Width; c++) - { - if (!IsValidLocation (rune, c, r)) - { - continue; - } - Contents [r, c] = new Cell - { - Rune = (rune != default ? rune : (Rune)' '), - Attribute = CurrentAttribute, IsDirty = true - }; - _dirtyLines! [r] = true; - } - } - } - } - /// /// Fills the specified rectangle with the specified . This method is a convenience method /// that calls . @@ -554,17 +494,6 @@ public abstract class ConsoleDriver /// upon success public abstract bool GetCursorVisibility (out CursorVisibility visibility); - /// Sets the position of the terminal cursor to and . - public abstract void UpdateCursor (); - - /// Tests if the specified rune is supported by the driver. - /// - /// - /// if the rune can be properly presented; if the driver does not - /// support displaying this rune. - /// - public virtual bool IsRuneSupported (Rune rune) { return Rune.IsValid (rune.Value); } - /// Tests whether the specified coordinate are valid for drawing the specified Rune. /// Used to determine if one or two columns are required. /// The column. @@ -586,27 +515,6 @@ public abstract class ConsoleDriver } } - // TODO: Make internal once Menu is upgraded - /// - /// Updates and to the specified column and row in . - /// Used by and to determine where to add content. - /// - /// - /// This does not move the cursor on the screen, it only updates the internal state of the driver. - /// - /// If or are negative or beyond and - /// , the method still sets those properties. - /// - /// - /// Column to move to. - /// Row to move to. - public virtual void Move (int col, int row) - { - //Debug.Assert (col >= 0 && row >= 0 && col < Contents.GetLength(1) && row < Contents.GetLength(0)); - Col = col; - Row = row; - } - /// Called when the terminal size changes. Fires the event. /// internal void OnSizeChanged (SizeChangedEventArgs args) { SizeChanged?.Invoke (this, args); } @@ -630,6 +538,9 @@ public abstract class ConsoleDriver /// upon success public abstract bool SetCursorVisibility (CursorVisibility visibility); + /// The event fired when the terminal is resized. + public event EventHandler? SizeChanged; + #endregion Cursor Handling /// Suspends the application (e.g. on Linux via SIGTSTP) and upon resume, resets the console driver. diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs index 12d6eacbe..7a570c739 100644 --- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs @@ -70,22 +70,22 @@ internal class CursesDriver : ConsoleDriver if (consoleKey == ConsoleKey.Packet) { - var mod = new ConsoleModifiers (); + //var mod = new ConsoleModifiers (); - if (shift) - { - mod |= ConsoleModifiers.Shift; - } + //if (shift) + //{ + // mod |= ConsoleModifiers.Shift; + //} - if (alt) - { - mod |= ConsoleModifiers.Alt; - } + //if (alt) + //{ + // mod |= ConsoleModifiers.Alt; + //} - if (control) - { - mod |= ConsoleModifiers.Control; - } + //if (control) + //{ + // mod |= ConsoleModifiers.Control; + //} var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); @@ -96,8 +96,8 @@ internal class CursesDriver : ConsoleDriver key = (KeyCode)keyChar; } - OnKeyDown (new Key (key)); - OnKeyUp (new Key (key)); + OnKeyDown (new (key)); + OnKeyUp (new (key)); //OnKeyPressed (new KeyEventArgsEventArgs (key)); } @@ -118,10 +118,10 @@ internal class CursesDriver : ConsoleDriver if (visibility != CursorVisibility.Invisible) { Console.Out.Write ( - EscSeqUtils.CSI_SetCursorStyle ( - (EscSeqUtils.DECSCUSR_Style)(((int)visibility >> 24) - & 0xFF) - ) + AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( + (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style)(((int)visibility >> 24) + & 0xFF) + ) ); } @@ -134,7 +134,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_EnableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); } } @@ -142,7 +142,7 @@ internal class CursesDriver : ConsoleDriver { if (!RunningUnitTests) { - Console.Out.Write (EscSeqUtils.CSI_DisableMouseEvents); + Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); } } @@ -170,14 +170,18 @@ internal class CursesDriver : ConsoleDriver if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) { - Curses.move (Row, Col); - if (Force16Colors) { + Curses.move (Row, Col); + Curses.raw (); Curses.noecho (); Curses.refresh (); } + else + { + _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); + } } } @@ -399,8 +403,6 @@ internal class CursesDriver : ConsoleDriver return updated; } - #endregion Screen and Contents - #region Color Handling public override bool SupportsTrueColor => true; @@ -531,8 +533,6 @@ internal class CursesDriver : ConsoleDriver #endregion - #region Cursor Support - private CursorVisibility? _currentCursorVisibility; private CursorVisibility? _initialCursorVisibility; @@ -568,118 +568,6 @@ internal class CursesDriver : ConsoleDriver return true; } - /// - public override bool SetCursorVisibility (CursorVisibility visibility) - { - if (_initialCursorVisibility.HasValue == false) - { - return false; - } - - if (!RunningUnitTests) - { - Curses.curs_set (((int)visibility >> 16) & 0x000000FF); - } - - if (visibility != CursorVisibility.Invisible) - { - Console.Out.Write ( - AnsiEscapeSequenceRequestUtils.CSI_SetCursorStyle ( - (AnsiEscapeSequenceRequestUtils.DECSCUSR_Style)(((int)visibility >> 24) - & 0xFF) - ) - ); - } - - _currentCursorVisibility = visibility; - - return true; - } - - public override void UpdateCursor () - { - EnsureCursorVisibility (); - - if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows) - { - if (Force16Colors) - { - Curses.move (Row, Col); - - Curses.raw (); - Curses.noecho (); - Curses.refresh (); - } - else - { - _mainLoopDriver?.WriteRaw (AnsiEscapeSequenceRequestUtils.CSI_SetCursorPosition (Row + 1, Col + 1)); - } - } - } - - #endregion Cursor Support - - #region Keyboard Support - - public override void SendKeys (char keyChar, ConsoleKey consoleKey, bool shift, bool alt, bool control) - { - KeyCode key; - - if (consoleKey == ConsoleKey.Packet) - { - //var mod = new ConsoleModifiers (); - - //if (shift) - //{ - // mod |= ConsoleModifiers.Shift; - //} - - //if (alt) - //{ - // mod |= ConsoleModifiers.Alt; - //} - - //if (control) - //{ - // mod |= ConsoleModifiers.Control; - //} - - var cKeyInfo = new ConsoleKeyInfo (keyChar, consoleKey, shift, alt, control); - cKeyInfo = ConsoleKeyMapping.DecodeVKPacketToKConsoleKeyInfo (cKeyInfo); - key = ConsoleKeyMapping.MapConsoleKeyInfoToKeyCode (cKeyInfo); - } - else - { - key = (KeyCode)keyChar; - } - - OnKeyDown (new (key)); - OnKeyUp (new (key)); - - //OnKeyPressed (new KeyEventArgsEventArgs (key)); - } - - #endregion Keyboard Support - - #region Mouse Support - public void StartReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_EnableMouseEvents); - } - } - - public void StopReportingMouseMoves () - { - if (!RunningUnitTests) - { - Console.Out.Write (AnsiEscapeSequenceRequestUtils.CSI_DisableMouseEvents); - } - } - - #endregion Mouse Support - private bool SetCursorPosition (int col, int row) { // + 1 is needed because non-Windows is based on 1 instead of 0 and @@ -875,8 +763,6 @@ internal class CursesDriver : ConsoleDriver return false; } - #region Low-Level Unix Stuff - private readonly ManualResetEventSlim _waitAnsiResponse = new (false); private CancellationTokenSource? _ansiResponseTokenSource; @@ -1028,6 +914,4 @@ internal static class Platform [DllImport ("libc")] private static extern int uname (nint buf); -} - -#endregion Low-Level Unix Stuff +} \ No newline at end of file diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs index 81f9f57e4..7aa943310 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetDriver.cs @@ -14,12 +14,6 @@ internal class NetDriver : ConsoleDriver public bool IsWinPlatform { get; private set; } public NetWinVTConsole? NetWinConsole { get; private set; } - public override void Refresh () - { - UpdateScreen (); - UpdateCursor (); - } - public override void Suspend () { if (Environment.OSVersion.Platform != PlatformID.Unix) @@ -52,17 +46,16 @@ internal class NetDriver : ConsoleDriver StartReportingMouseMoves (); } - #region Screen and Contents - - public override void UpdateScreen () + public override bool UpdateScreen () { + bool updated = false; if (RunningUnitTests || _winSizeChanging || Console.WindowHeight < 1 || Contents?.Length != Rows * Cols || Rows != Console.WindowHeight) { - return; + return updated; } var top = 0; @@ -80,7 +73,7 @@ internal class NetDriver : ConsoleDriver { if (Console.WindowHeight < 1) { - return; + return updated; } if (!_dirtyLines! [row]) @@ -90,9 +83,10 @@ internal class NetDriver : ConsoleDriver if (!SetCursorPosition (0, row)) { - return; + return updated; } + updated = true; _dirtyLines [row] = false; output.Clear (); @@ -138,30 +132,33 @@ internal class NetDriver : ConsoleDriver { output.Append ( AnsiEscapeSequenceRequestUtils.CSI_SetGraphicsRendition ( - MapColors ( - (ConsoleColor)attr.Background.GetClosestNamedColor16 (), - false - ), - MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ()) - ) + MapColors ( + (ConsoleColor)attr.Background + .GetClosestNamedColor16 (), + false + ), + MapColors ( + (ConsoleColor)attr.Foreground + .GetClosestNamedColor16 ()) + ) ); } else { output.Append ( AnsiEscapeSequenceRequestUtils.CSI_SetForegroundColorRGB ( - attr.Foreground.R, - attr.Foreground.G, - attr.Foreground.B - ) + attr.Foreground.R, + attr.Foreground.G, + attr.Foreground.B + ) ); output.Append ( AnsiEscapeSequenceRequestUtils.CSI_SetBackgroundColorRGB ( - attr.Background.R, - attr.Background.G, - attr.Background.B - ) + attr.Background.R, + attr.Background.G, + attr.Background.B + ) ); } } @@ -199,7 +196,7 @@ internal class NetDriver : ConsoleDriver Console.Write (output); } - foreach (SixelToRender s in Application.Sixel) + foreach (var s in Application.Sixel) { if (!string.IsNullOrWhiteSpace (s.SixelData)) { @@ -221,9 +218,9 @@ internal class NetDriver : ConsoleDriver lastCol += outputWidth; outputWidth = 0; } - } - #endregion Screen and Contents + return updated; + } #region Init/End/MainLoop @@ -821,7 +818,7 @@ internal class NetDriver : ConsoleDriver } } - private void ResizeScreen () + public virtual void ResizeScreen () { // Not supported on Unix. if (IsWinPlatform) @@ -846,18 +843,17 @@ internal class NetDriver : ConsoleDriver } #pragma warning restore CA1416 } - // INTENT: Why are these eating the exceptions? // Comments would be good here. catch (IOException) { // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); } catch (ArgumentOutOfRangeException) { // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); } } else @@ -866,7 +862,7 @@ internal class NetDriver : ConsoleDriver } // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); } #endregion Low-Level DotNet tuff diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs index 0b51d2f4d..36490b32f 100644 --- a/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs +++ b/Terminal.Gui/ConsoleDrivers/NetDriver/NetMainLoop.cs @@ -29,19 +29,20 @@ internal class NetMainLoop : IMainLoopDriver { ArgumentNullException.ThrowIfNull (consoleDriver); - _netEvents = new (consoleDriver); + if (!ConsoleDriver.RunningUnitTests) + { + _netEvents = new (consoleDriver); + } } void IMainLoopDriver.Setup (MainLoop mainLoop) { _mainLoop = mainLoop; - if (ConsoleDriver.RunningUnitTests) + if (!ConsoleDriver.RunningUnitTests) { - return; + Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); } - - Task.Run (NetInputHandler, _inputHandlerTokenSource.Token); } void IMainLoopDriver.Wakeup () { _eventReady.Set (); } diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs index 63229ae09..50480b153 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsConsole.cs @@ -517,33 +517,33 @@ internal class WindowsConsole _inputReadyCancellationTokenSource = null; } - //internal Size GetConsoleBufferWindow (out Point position) - //{ - // if (_screenBuffer == nint.Zero) - // { - // position = Point.Empty; + internal Size GetConsoleBufferWindow (out Point position) + { + if (_outputHandle == nint.Zero) + { + position = Point.Empty; - // return Size.Empty; - // } + return Size.Empty; + } - // var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); - // csbi.cbSize = (uint)Marshal.SizeOf (csbi); + var csbi = new CONSOLE_SCREEN_BUFFER_INFOEX (); + csbi.cbSize = (uint)Marshal.SizeOf (csbi); - // if (!GetConsoleScreenBufferInfoEx (_screenBuffer, ref csbi)) - // { - // //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); - // position = Point.Empty; + if (!GetConsoleScreenBufferInfoEx (_outputHandle, ref csbi)) + { + //throw new System.ComponentModel.Win32Exception (Marshal.GetLastWin32Error ()); + position = Point.Empty; - // return Size.Empty; - // } + return Size.Empty; + } - // Size sz = new ( - // csbi.srWindow.Right - csbi.srWindow.Left + 1, - // csbi.srWindow.Bottom - csbi.srWindow.Top + 1); - // position = new (csbi.srWindow.Left, csbi.srWindow.Top); + Size sz = new ( + csbi.srWindow.Right - csbi.srWindow.Left + 1, + csbi.srWindow.Bottom - csbi.srWindow.Top + 1); + position = new (csbi.srWindow.Left, csbi.srWindow.Top); - // return sz; - //} + return sz; + } internal Size GetConsoleOutputWindow (out Point position) { diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs index d7275e541..34afbc056 100644 --- a/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs +++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver/WindowsDriver.cs @@ -117,16 +117,6 @@ internal class WindowsDriver : ConsoleDriver public override bool IsRuneSupported (Rune rune) { return base.IsRuneSupported (rune) && rune.IsBmp; } - public override void Refresh () - { - if (!RunningUnitTests) - { - UpdateScreen (); - //WinConsole?.SetInitialCursorVisibility (); - UpdateCursor (); - } - } - public override void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control) { var input = new WindowsConsole.InputRecord @@ -289,6 +279,11 @@ internal class WindowsDriver : ConsoleDriver public override void UpdateCursor () { + if (RunningUnitTests) + { + return; + } + if (Col < 0 || Row < 0 || Col >= Cols || Row >= Rows) { GetCursorVisibility (out CursorVisibility cursorVisibility); @@ -382,13 +377,14 @@ internal class WindowsDriver : ConsoleDriver #endregion Cursor Handling - public override void UpdateScreen () + public override bool UpdateScreen () { - Size windowSize = WinConsole?.GetConsoleOutputWindow (out Point _) ?? new Size (Cols, Rows); + bool updated = false; + Size windowSize = WinConsole?.GetConsoleBufferWindow (out Point _) ?? new Size (Cols, Rows); if (!windowSize.IsEmpty && (windowSize.Width != Cols || windowSize.Height != Rows)) { - return; + return updated; } var bufferCoords = new WindowsConsole.Coord @@ -405,6 +401,7 @@ internal class WindowsDriver : ConsoleDriver } _dirtyLines [row] = false; + updated = true; for (var col = 0; col < Cols; col++) { @@ -463,6 +460,8 @@ internal class WindowsDriver : ConsoleDriver } WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); + + return updated; } internal override void End () @@ -502,6 +501,7 @@ internal class WindowsDriver : ConsoleDriver Size winSize = WinConsole.GetConsoleOutputWindow (out Point _); Cols = winSize.Width; Rows = winSize.Height; + OnSizeChanged (new SizeChangedEventArgs (new (Cols, Rows))); } WindowsConsole.SmallRect.MakeEmpty (ref _damageRegion); @@ -524,7 +524,7 @@ internal class WindowsDriver : ConsoleDriver _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); _damageRegion = new WindowsConsole.SmallRect { @@ -613,11 +613,12 @@ internal class WindowsDriver : ConsoleDriver Cols = inputEvent.WindowBufferSizeEvent._size.X; Rows = inputEvent.WindowBufferSizeEvent._size.Y; + Application.Screen = new (0, 0, Cols, Rows); ResizeScreen (); ClearContents (); Application.Top?.SetNeedsLayout (); - Application.Refresh (); + Application.LayoutAndDraw (); break; #endif @@ -961,7 +962,7 @@ internal class WindowsDriver : ConsoleDriver { _outputBuffer = new WindowsConsole.ExtendedCharInfo [Rows * Cols]; // CONCURRENCY: Unsynchronized access to Clip is not safe. - Clip = new (0, 0, Cols, Rows); + Clip = new (Screen); _damageRegion = new WindowsConsole.SmallRect { diff --git a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs index dc307ecd8..f281dc461 100644 --- a/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs +++ b/UICatalog/Scenarios/AnsiEscapeSequenceRequests.cs @@ -414,16 +414,16 @@ public sealed class AnsiEscapeSequenceRequests : Scenario // _graphView.ScrollOffset = new PointF(,0); if (_sentSeries.Points.Count > 0 || _answeredSeries.Points.Count > 0) { - _graphView.SetNeedsDisplay (); + _graphView.SetNeedsDraw (); } } private void UpdateResponses () { _lblSummary.Text = GetSummary (); - _lblSummary.SetNeedsDisplay (); + _lblSummary.SetNeedsDraw (); _lblErrorSummary.Text = GetSummaryErrors (); - _lblErrorSummary.SetNeedsDisplay (); + _lblErrorSummary.SetNeedsDraw (); } }