// // ConsoleDriver.cs: Base class for Terminal.Gui ConsoleDriver implementations. // using System.Text; using System; using System.Collections.Generic; using System.Diagnostics; using static Terminal.Gui.ColorScheme; namespace Terminal.Gui; /// /// Base class for Terminal.Gui ConsoleDriver implementations. /// /// /// There are currently four implementations: /// - (for Unix and Mac) /// - /// - that uses the .NET Console API /// - for unit testing. /// public abstract class ConsoleDriver { /// /// Set this to true in any unit tests that attempt to test drivers other than FakeDriver. /// /// public ColorTests () /// { /// ConsoleDriver.RunningUnitTests = true; /// } /// /// internal static bool RunningUnitTests { get; set; } /// /// Prepare the driver and set the key and mouse events handlers. /// /// The main loop. /// The handler for ProcessKey /// The handler for key down events /// The handler for key up events /// The handler for mouse events public abstract void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler); /// /// The handler fired when the terminal is resized. /// protected Action TerminalResized; /// /// The number of columns visible in the terminal. /// public virtual int Cols { get; internal set; } /// /// The number of rows visible in the terminal. /// public virtual int Rows { get; internal set; } /// /// The leftmost column in the terminal. /// public virtual int Left { get; internal set; } = 0; /// /// The topmost row in the terminal. /// public virtual int Top { get; internal set; } = 0; /// /// Get the operating system clipboard. /// public IClipboard Clipboard { get; internal set; } /// /// The contents of the application output. The driver outputs this buffer to the terminal when /// is called. /// /// The format of the array is rows, columns, and 3 values on the last column: Rune, Attribute and Dirty Flag /// /// //public int [,,] Contents { get; internal set; } ///// ///// The contents of the application output. The driver outputs this buffer to the terminal when ///// is called. ///// ///// The format of the array is rows, columns. The first index is the row, the second index is the column. ///// ///// public Cell [,] Contents { get; internal set; } /// /// Initializes the driver /// /// Method to invoke when the terminal is resized. public abstract void Init (Action terminalResized); /// /// Gets the column last set by . and /// are used by and to determine where to add content. /// public int Col { get; internal set; } /// /// Gets the row last set by . and /// are used by and to determine where to add content. /// public int Row { get; internal set; } /// /// 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) { Col = col; Row = row; } /// /// 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); } /// /// Adds the specified rune to the display at the current cursor position. /// /// /// /// When the method returns, will be incremented by the number of columns required, /// even if the new column value is outside of the or screen dimensions defined by . /// /// /// If requires more than one column, and plus the number of columns needed /// exceeds the or screen dimensions, the default Unicode replacement character (U+FFFD) will be added instead. /// /// /// Rune to add. public void AddRune (Rune rune) { int runeWidth = -1; var validLocation = IsValidLocation (Col, Row); if (validLocation) { rune = rune.MakePrintable (); runeWidth = rune.GetColumns (); if (runeWidth == 0 && rune.IsCombiningMark () && Col > 0) { // This is a combining character, and we are not at the beginning of the line. // TODO: Remove hard-coded [0] once combining pairs is supported // Convert Runes to string and concatenate string combined = Contents [Row, Col - 1].Runes [0].ToString () + rune.ToString (); // Normalize to Form C (Canonical Composition) string normalized = combined.Normalize (NormalizationForm.FormC); Contents [Row, Col - 1].Runes = new List { (Rune)normalized [0] }; ; Contents [Row, Col - 1].Attribute = CurrentAttribute; Contents [Row, Col - 1].IsDirty = true; //Col--; } else { Contents [Row, Col].Attribute = CurrentAttribute; Contents [Row, Col].IsDirty = true; if (Col > 0) { // Check if cell to left has a wide glyph if (Contents [Row, Col - 1].Runes [0].GetColumns () > 1) { // Invalidate cell to left Contents [Row, Col - 1].Runes = new List { Rune.ReplacementChar }; Contents [Row, Col - 1].IsDirty = true; } } if (runeWidth < 1) { Contents [Row, Col].Runes = new List { Rune.ReplacementChar }; } else if (runeWidth == 1) { Contents [Row, Col].Runes = new List { rune }; if (Col < Clip.Right - 1) { Contents [Row, Col + 1].IsDirty = true; } } else if (runeWidth == 2) { if (Col == Clip.Right - 1) { // We're at the right edge of the clip, so we can't display a wide character. // TODO: Figure out if it is better to show a replacement character or ' ' Contents [Row, Col].Runes = new List { Rune.ReplacementChar }; } else { Contents [Row, Col].Runes = new List { rune }; if (Col < Clip.Right - 1) { // Invalidate cell to right so that it doesn't get drawn // TODO: Figure out if it is better to show a replacement character or ' ' Contents [Row, Col + 1].Runes = new List { Rune.ReplacementChar }; Contents [Row, Col + 1].IsDirty = true; } } } else { // This is a non-spacing character, so we don't need to do anything Contents [Row, Col].Runes = new List { (Rune)' ' }; Contents [Row, Col].IsDirty = false; } _dirtyLines [Row] = true; } } if (runeWidth is < 0 or > 0) { Col++; } if (runeWidth > 1) { Debug.Assert (runeWidth <= 2); if (validLocation && Col < Clip.Right) { // This is a double-width character, and we are not at the end of the line. // Col now points to the second column of the character. Ensure it doesn't // Get rendered. Contents [Row, Col].IsDirty = false; Contents [Row, Col].Attribute = CurrentAttribute; // TODO: Determine if we should wipe this out (for now now) //Contents [Row, Col].Runes [0] = (Rune)' '; } Col++; } } /// /// Adds the specified to the display at the current cursor position. This method /// is a convenience method that calls with the constructor. /// /// Character to add. public void AddRune (char c) => AddRune (new Rune (c)); /// /// Adds the to the display at the cursor position. /// /// /// /// When the method returns, will be incremented by the number of columns required, /// unless the new column value is outside of the or screen dimensions defined by . /// /// /// If requires more columns than are available, the output will be clipped. /// /// /// String. public void AddStr (string str) { foreach (var rune in str.EnumerateRunes ()) { AddRune (rune); } } Rect _clip; /// /// Tests whether the specified coordinate are valid for drawing. /// /// The column. /// The row. /// if the coordinate is outside of the /// screen bounds or outside of . otherwise. public bool IsValidLocation (int col, int row) => col >= 0 && row >= 0 && col < Cols && row < Rows && Clip.Contains (col, row); /// /// Gets or sets the clip rectangle that and are /// subject to. /// /// The rectangle describing the bounds of . public Rect Clip { get => _clip; set => _clip = value; } /// /// Updates the screen to reflect all the changes that have been done to the display buffer /// public abstract void Refresh (); /// /// Sets the position of the terminal cursor to and . /// public abstract void UpdateCursor (); /// /// Gets the terminal cursor visibility. /// /// The current /// upon success public abstract bool GetCursorVisibility (out CursorVisibility visibility); /// /// Sets the terminal cursor visibility. /// /// The wished /// upon success public abstract bool SetCursorVisibility (CursorVisibility visibility); /// /// Determines if the terminal cursor should be visible or not and sets it accordingly. /// /// upon success public abstract bool EnsureCursorVisibility (); // As performance is a concern, we keep track of the dirty lines and only refresh those. // This is in addition to the dirty flag on each cell. internal bool [] _dirtyLines; /// /// 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]; Clip = new Rect (0, 0, Cols, Rows); _dirtyLines = new bool [Rows]; lock (Contents) { // Can raise an exception while is still resizing. try { for (var row = 0; row < Rows; row++) { for (var c = 0; c < Cols; c++) { Contents [row, c] = new Cell () { Runes = new List { (Rune)' ' }, Attribute = new Attribute (Color.White, Color.Black), IsDirty = true }; _dirtyLines [row] = true; } } } catch (IndexOutOfRangeException) { } } } /// /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. /// public abstract void UpdateScreen (); #region Color Handling /// /// Gets whether the supports TrueColor output. /// public virtual bool SupportsTrueColor { get => true; } bool _force16Colors = false; // TODO: Make this a ConfiguationManager setting on Application /// /// Gets or sets whether the should use 16 colors instead of the default TrueColors. /// /// /// Will be forced to if is , indicating /// that the cannot support TrueColor. /// public virtual bool Force16Colors { get => _force16Colors || !SupportsTrueColor; set { _force16Colors = (value || !SupportsTrueColor); Refresh (); } } Attribute _currentAttribute; /// /// The that will be used for the next or call. /// public Attribute CurrentAttribute { get => _currentAttribute; set { if (value is { Initialized: false, HasValidColors: true } && Application.Driver != null) { _currentAttribute = Application.Driver.MakeAttribute (value.Foreground, value.Background); return; } if (!value.Initialized) Debug.WriteLine ("ConsoleDriver.CurrentAttribute: Attributes must be initialized before use."); _currentAttribute = value; } } /// /// Selects the specified attribute as the attribute to use for future calls to AddRune and AddString. /// /// /// Implementations should call base.SetAttribute(c). /// /// C. public Attribute SetAttribute (Attribute c) { var prevAttribute = CurrentAttribute; CurrentAttribute = c; return prevAttribute; } /// /// Make the attribute for the foreground and background colors. /// /// Foreground. /// Background. /// public virtual Attribute MakeAttribute (Color fore, Color back) { return MakeColor (fore, back); } /// /// Gets the foreground and background colors based on a platform-dependent color value. /// /// The platform-dependent color value. /// The foreground. /// The background. internal abstract void GetColors (int value, out Color foreground, out Color background); /// /// Gets the current . /// /// The current attribute. public Attribute GetAttribute () => CurrentAttribute; /// /// Makes an . /// /// The foreground color. /// The background color. /// The attribute for the foreground and background colors. public abstract Attribute MakeColor (Color foreground, Color background); /// /// Ensures all s in are correctly /// initialized by the driver. /// public void InitializeColorSchemes () { // Ensure all Attributes are initialized by the driver foreach (var s in Colors.ColorSchemes) { s.Value.Initialize (); } } #endregion /// /// Enables diagnostic functions /// [Flags] public enum DiagnosticFlags : uint { /// /// All diagnostics off /// Off = 0b_0000_0000, /// /// When enabled, will draw a /// ruler in the frame for any side with a padding value greater than 0. /// FrameRuler = 0b_0000_0001, /// /// When enabled, will draw a /// 'L', 'R', 'T', and 'B' when clearing 's instead of ' '. /// FramePadding = 0b_0000_0010, } /// /// Set flags to enable/disable diagnostics. /// public static DiagnosticFlags Diagnostics { get; set; } /// /// 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 (); /// /// Simulates a key press. /// /// The key character. /// The key. /// If simulates the Shift key being pressed. /// If simulates the Alt key being pressed. /// If simulates the Ctrl key being pressed. public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool ctrl); // TODO: Move FillRect to ./Drawing /// /// Fills the specified rectangle with the specified rune. /// /// /// public void FillRect (Rect rect, Rune rune = default) { for (var r = rect.Y; r < rect.Y + rect.Height; r++) { for (var c = rect.X; c < rect.X + rect.Width; c++) { Application.Driver.Move (c, r); Application.Driver.AddRune (rune == default ? new Rune (' ') : rune); } } } /// /// Fills the specified rectangle with the specified . This method /// is a convenience method that calls . /// /// /// public void FillRect (Rect rect, char c) => FillRect (rect, new Rune (c)); /// /// Ends the execution of the console driver. /// public abstract void End (); /// /// Returns the name of the driver and relevant library version information. /// /// public virtual string GetVersionInfo () => GetType ().Name; } /// /// Terminal Cursor Visibility settings. /// /// /// Hex value are set as 0xAABBCCDD where : /// /// AA stand for the TERMINFO DECSUSR parameter value to be used under Linux and MacOS /// BB stand for the NCurses curs_set parameter value to be used under Linux and MacOS /// CC stand for the CONSOLE_CURSOR_INFO.bVisible parameter value to be used under Windows /// DD stand for the CONSOLE_CURSOR_INFO.dwSize parameter value to be used under Windows /// public enum CursorVisibility { /// /// Cursor caret has default /// /// Works under Xterm-like terminal otherwise this is equivalent to . This default directly depends of the XTerm user configuration settings so it could be Block, I-Beam, Underline with possible blinking. Default = 0x00010119, /// /// Cursor caret is hidden /// Invisible = 0x03000019, /// /// Cursor caret is normally shown as a blinking underline bar _ /// Underline = 0x03010119, /// /// Cursor caret is normally shown as a underline bar _ /// /// Under Windows, this is equivalent to UnderlineFix = 0x04010119, /// /// Cursor caret is displayed a blinking vertical bar | /// /// Works under Xterm-like terminal otherwise this is equivalent to Vertical = 0x05010119, /// /// Cursor caret is displayed a blinking vertical bar | /// /// Works under Xterm-like terminal otherwise this is equivalent to VerticalFix = 0x06010119, /// /// Cursor caret is displayed as a blinking block ▉ /// Box = 0x01020164, /// /// Cursor caret is displayed a block ▉ /// /// Works under Xterm-like terminal otherwise this is equivalent to BoxFix = 0x02020164, }