// // ConsoleDriver.cs: Definition for the Console Driver API // // Authors: // Miguel de Icaza (miguel@gnome.org) // // Define this to enable diagnostics drawing for Window Frames using NStack; using System; using System.Collections.Generic; using System.Diagnostics; using System.Linq; using System.Runtime.CompilerServices; using System.Threading.Tasks; using Unix.Terminal; namespace Terminal.Gui { /// /// Basic colors that can be used to set the foreground and background colors in console applications. /// public enum Color { /// /// The black color. /// Black, /// /// The blue color. /// Blue, /// /// The green color. /// Green, /// /// The cyan color. /// Cyan, /// /// The red color. /// Red, /// /// The magenta color. /// Magenta, /// /// The brown color. /// Brown, /// /// The gray color. /// Gray, /// /// The dark gray color. /// DarkGray, /// /// The bright bBlue color. /// BrightBlue, /// /// The bright green color. /// BrightGreen, /// /// The bright cyan color. /// BrightCyan, /// /// The bright red color. /// BrightRed, /// /// The bright magenta color. /// BrightMagenta, /// /// The bright yellow color. /// BrightYellow, /// /// The White color. /// White } /// /// Attributes are used as elements that contain both a foreground and a background or platform specific features /// /// /// s are needed to map colors to terminal capabilities that might lack colors, on color /// scenarios, they encode both the foreground and the background color and are used in the /// class to define color schemes that can be used in your application. /// public struct Attribute { /// /// The color attribute value. /// public int Value { get; } /// /// The foreground color. /// public Color Foreground { get; } /// /// The background color. /// public Color Background { get; } /// /// Initializes a new instance of the struct with only the value passed to /// and trying to get the colors if defined. /// /// Value. public Attribute (int value) { Color foreground = default; Color background = default; if (Application.Driver != null) { Application.Driver.GetColors (value, out foreground, out background); } Value = value; Foreground = foreground; Background = background; } /// /// Initializes a new instance of the struct. /// /// Value. /// Foreground /// Background public Attribute (int value, Color foreground, Color background) { Value = value; Foreground = foreground; Background = background; } /// /// Initializes a new instance of the struct. /// /// Foreground /// Background public Attribute (Color foreground = new Color (), Color background = new Color ()) { Value = Make (foreground, background).Value; Foreground = foreground; Background = background; } /// /// Initializes a new instance of the struct /// with the same colors for the foreground and background. /// /// The color. public Attribute (Color color) : this (color, color) { } /// /// Implicit conversion from an to the underlying Int32 representation /// /// The integer value stored in the attribute. /// The attribute to convert public static implicit operator int (Attribute c) => c.Value; /// /// Implicitly convert an integer value into an /// /// An attribute with the specified integer value. /// value public static implicit operator Attribute (int v) => new Attribute (v); /// /// Creates an from the specified foreground and background. /// /// The make. /// Foreground color to use. /// Background color to use. public static Attribute Make (Color foreground, Color background) { if (Application.Driver == null) throw new InvalidOperationException ("The Application has not been initialized"); return Application.Driver.MakeAttribute (foreground, background); } /// /// Gets the current from the driver. /// /// The current attribute. public static Attribute Get () { if (Application.Driver == null) throw new InvalidOperationException ("The Application has not been initialized"); return Application.Driver.GetAttribute (); } } /// /// Color scheme definitions, they cover some common scenarios and are used /// typically in containers such as and to set the scheme that is used by all the /// views contained inside. /// public class ColorScheme : IEquatable { Attribute _normal; Attribute _focus; Attribute _hotNormal; Attribute _hotFocus; Attribute _disabled; internal string caller = ""; /// /// The default color for text, when the view is not focused. /// public Attribute Normal { get { return _normal; } set { _normal = SetAttribute (value); } } /// /// The color for text when the view has the focus. /// public Attribute Focus { get { return _focus; } set { _focus = SetAttribute (value); } } /// /// The color for the hotkey when a view is not focused /// public Attribute HotNormal { get { return _hotNormal; } set { _hotNormal = SetAttribute (value); } } /// /// The color for the hotkey when the view is focused. /// public Attribute HotFocus { get { return _hotFocus; } set { _hotFocus = SetAttribute (value); } } /// /// The default color for text, when the view is disabled. /// public Attribute Disabled { get { return _disabled; } set { _disabled = SetAttribute (value); } } bool preparingScheme = false; Attribute SetAttribute (Attribute attribute, [CallerMemberName] string callerMemberName = null) { if (!Application._initialized && !preparingScheme) return attribute; if (preparingScheme) return attribute; preparingScheme = true; switch (caller) { case "TopLevel": switch (callerMemberName) { case "Normal": HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background); break; case "Focus": HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background); break; case "HotNormal": HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background); break; case "HotFocus": HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background); if (Focus.Foreground != attribute.Background) Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background); break; } break; case "Base": switch (callerMemberName) { case "Normal": HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background); break; case "Focus": HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background); break; case "HotNormal": HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background); Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background); break; case "HotFocus": HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background); if (Focus.Foreground != attribute.Background) Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background); break; } break; case "Menu": switch (callerMemberName) { case "Normal": if (Focus.Background != attribute.Background) Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background); HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background); Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background); break; case "Focus": Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background); HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background); break; case "HotNormal": if (Focus.Background != attribute.Background) HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background); Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background); Disabled = Application.Driver.MakeAttribute (Disabled.Foreground, attribute.Background); break; case "HotFocus": HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background); if (Focus.Foreground != attribute.Background) Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background); break; case "Disabled": if (Focus.Background != attribute.Background) HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background); Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background); HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background); break; } break; case "Dialog": switch (callerMemberName) { case "Normal": if (Focus.Background != attribute.Background) Focus = Application.Driver.MakeAttribute (attribute.Foreground, Focus.Background); HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background); break; case "Focus": Normal = Application.Driver.MakeAttribute (attribute.Foreground, Normal.Background); HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background); break; case "HotNormal": if (Focus.Background != attribute.Background) HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, HotFocus.Background); if (Normal.Foreground != attribute.Background) Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background); break; case "HotFocus": HotNormal = Application.Driver.MakeAttribute (attribute.Foreground, HotNormal.Background); if (Focus.Foreground != attribute.Background) Focus = Application.Driver.MakeAttribute (Focus.Foreground, attribute.Background); break; } break; case "Error": switch (callerMemberName) { case "Normal": HotNormal = Application.Driver.MakeAttribute (HotNormal.Foreground, attribute.Background); HotFocus = Application.Driver.MakeAttribute (HotFocus.Foreground, attribute.Background); break; case "HotNormal": case "HotFocus": HotFocus = Application.Driver.MakeAttribute (attribute.Foreground, attribute.Background); Normal = Application.Driver.MakeAttribute (Normal.Foreground, attribute.Background); break; } break; } preparingScheme = false; return attribute; } /// /// Compares two objects for equality. /// /// /// true if the two objects are equal public override bool Equals (object obj) { return Equals (obj as ColorScheme); } /// /// Compares two objects for equality. /// /// /// true if the two objects are equal public bool Equals (ColorScheme other) { return other != null && EqualityComparer.Default.Equals (_normal, other._normal) && EqualityComparer.Default.Equals (_focus, other._focus) && EqualityComparer.Default.Equals (_hotNormal, other._hotNormal) && EqualityComparer.Default.Equals (_hotFocus, other._hotFocus) && EqualityComparer.Default.Equals (_disabled, other._disabled); } /// /// Returns a hashcode for this instance. /// /// hashcode for this instance public override int GetHashCode () { int hashCode = -1242460230; hashCode = hashCode * -1521134295 + _normal.GetHashCode (); hashCode = hashCode * -1521134295 + _focus.GetHashCode (); hashCode = hashCode * -1521134295 + _hotNormal.GetHashCode (); hashCode = hashCode * -1521134295 + _hotFocus.GetHashCode (); hashCode = hashCode * -1521134295 + _disabled.GetHashCode (); return hashCode; } /// /// Compares two objects for equality. /// /// /// /// true if the two objects are equivalent public static bool operator == (ColorScheme left, ColorScheme right) { return EqualityComparer.Default.Equals (left, right); } /// /// Compares two objects for inequality. /// /// /// /// true if the two objects are not equivalent public static bool operator != (ColorScheme left, ColorScheme right) { return !(left == right); } } /// /// The default s for the application. /// public static class Colors { static Colors () { // Use reflection to dynamically create the default set of ColorSchemes from the list defined // by the class. ColorSchemes = typeof (Colors).GetProperties () .Where (p => p.PropertyType == typeof (ColorScheme)) .Select (p => new KeyValuePair (p.Name, new ColorScheme ())) // (ColorScheme)p.GetValue (p))) .ToDictionary (t => t.Key, t => t.Value); } /// /// The application toplevel color scheme, for the default toplevel views. /// /// /// /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["TopLevel"]; /// /// public static ColorScheme TopLevel { get => GetColorScheme (); set => SetColorScheme (value); } /// /// The base color scheme, for the default toplevel views. /// /// /// /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Base"]; /// /// public static ColorScheme Base { get => GetColorScheme (); set => SetColorScheme (value); } /// /// The dialog color scheme, for standard popup dialog boxes /// /// /// /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Dialog"]; /// /// public static ColorScheme Dialog { get => GetColorScheme (); set => SetColorScheme (value); } /// /// The menu bar color /// /// /// /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Menu"]; /// /// public static ColorScheme Menu { get => GetColorScheme (); set => SetColorScheme (value); } /// /// The color scheme for showing errors. /// /// /// /// This API will be deprecated in the future. Use instead (e.g. edit.ColorScheme = Colors.ColorSchemes["Error"]; /// /// public static ColorScheme Error { get => GetColorScheme (); set => SetColorScheme (value); } static ColorScheme GetColorScheme ([CallerMemberName] string callerMemberName = null) { return ColorSchemes [callerMemberName]; } static void SetColorScheme (ColorScheme colorScheme, [CallerMemberName] string callerMemberName = null) { ColorSchemes [callerMemberName] = colorScheme; colorScheme.caller = callerMemberName; } /// /// Provides the defined s. /// public static Dictionary ColorSchemes { get; } } /// /// Cursors Visibility that are displayed /// // // Hexa value are set as 0xAABBCCDD where : // // AA stand for the TERMINFO DECSUSR parameter value to be used under Linux & MacOS // BB stand for the NCurses curs_set parameter value to be used under Linux & 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, } ///// ///// Special characters that can be drawn with ///// //public enum SpecialChar { // /// // /// Horizontal line character. // /// // HLine, // /// // /// Vertical line character. // /// // VLine, // /// // /// Stipple pattern // /// // Stipple, // /// // /// Diamond character // /// // Diamond, // /// // /// Upper left corner // /// // ULCorner, // /// // /// Lower left corner // /// // LLCorner, // /// // /// Upper right corner // /// // URCorner, // /// // /// Lower right corner // /// // LRCorner, // /// // /// Left tee // /// // LeftTee, // /// // /// Right tee // /// // RightTee, // /// // /// Top tee // /// // TopTee, // /// // /// The bottom tee. // /// // BottomTee, //} /// /// ConsoleDriver is an abstract class that defines the requirements for a console driver. /// There are currently three implementations: (for Unix and Mac), , and that uses the .NET Console API. /// public abstract class ConsoleDriver { /// /// The handler fired when the terminal is resized. /// protected Action TerminalResized; /// /// The current number of columns in the terminal. /// public abstract int Cols { get; } /// /// The current number of rows in the terminal. /// public abstract int Rows { get; } /// /// The current left in the terminal. /// public abstract int Left { get; } /// /// The current top in the terminal. /// public abstract int Top { get; } /// /// Get the operation system clipboard. /// public abstract IClipboard Clipboard { get; } /// /// If false height is measured by the window height and thus no scrolling. /// If true then height is measured by the buffer height, enabling scrolling. /// public abstract bool HeightAsBuffer { get; set; } /// /// The format is rows, columns and 3 values on the last column: Rune, Attribute and Dirty Flag /// public virtual int [,,] Contents { get; } /// /// Initializes the driver /// /// Method to invoke when the terminal is resized. public abstract void Init (Action terminalResized); /// /// Moves the cursor to the specified column and row. /// /// Column to move the cursor to. /// Row to move the cursor to. public abstract void Move (int col, int row); /// /// Adds the specified rune to the display at the current cursor position. /// /// Rune to add. public abstract void AddRune (Rune rune); /// /// Ensures a Rune is not a control character and can be displayed by translating characters below 0x20 /// to equivalent, printable, Unicode chars. /// /// Rune to translate /// public static Rune MakePrintable (Rune c) { if (c <= 0x1F || (c >= 0X7F && c <= 0x9F)) { // ASCII (C0) control characters. // C1 control characters (https://www.aivosto.com/articles/control-characters.html#c1) return new Rune (c + 0x2400); } return c; } /// /// Ensures that the column and line are in a valid range from the size of the driver. /// /// The column. /// The row. /// The clip. /// trueif it's a valid range,falseotherwise. public bool IsValidContent (int col, int row, Rect clip) => col >= 0 && row >= 0 && col < Cols && row < Rows && clip.Contains (col, row); /// /// Adds the to the display at the cursor position. /// /// String. public abstract void AddStr (ustring str); /// /// 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); /// /// Updates the screen to reflect all the changes that have been done to the display buffer /// public abstract void Refresh (); /// /// Updates the location of the cursor position /// public abstract void UpdateCursor (); /// /// Retreive the cursor caret visibility /// /// The current /// true upon success public abstract bool GetCursorVisibility (out CursorVisibility visibility); /// /// Change the cursor caret visibility /// /// The wished /// true upon success public abstract bool SetCursorVisibility (CursorVisibility visibility); /// /// Ensure the cursor visibility /// /// true upon success public abstract bool EnsureCursorVisibility (); /// /// Ends the execution of the console driver. /// public abstract void End (); /// /// Resizes the clip area when the screen is resized. /// public abstract void ResizeScreen (); /// /// Reset and recreate the contents and the driver buffer. /// public abstract void UpdateOffScreen (); /// /// Redraws the physical screen with the contents that have been queued up via any of the printing commands. /// public abstract void UpdateScreen (); /// /// Selects the specified attribute as the attribute to use for future calls to AddRune, AddString. /// /// C. public abstract void SetAttribute (Attribute c); /// /// Set Colors from limit sets of colors. /// /// Foreground. /// Background. public abstract void SetColors (ConsoleColor foreground, ConsoleColor background); // Advanced uses - set colors to any pre-set pairs, you would need to init_color // that independently with the R, G, B values. /// /// Advanced uses - set colors to any pre-set pairs, you would need to init_color /// that independently with the R, G, B values. /// /// Foreground color identifier. /// Background color identifier. public abstract void SetColors (short foregroundColorId, short backgroundColorId); /// /// Gets the foreground and background colors based on the value. /// /// The value. /// The foreground. /// The background. /// public abstract bool GetColors (int value, out Color foreground, out Color background); /// /// Allows sending keys without typing on a keyboard. /// /// The character key. /// The key. /// If shift key is sending. /// If alt key is sending. /// If control key is sending. public abstract void SendKeys (char keyChar, ConsoleKey key, bool shift, bool alt, bool control); /// /// Set the handler when the terminal is resized. /// /// public void SetTerminalResized (Action terminalResized) { TerminalResized = terminalResized; } /// /// Draws the title for a Window-style view incorporating padding. /// /// Screen relative region where the frame will be drawn. /// The title for the window. The title will only be drawn if title is not null or empty and paddingTop is greater than 0. /// Number of columns to pad on the left (if 0 the border will not appear on the left). /// Number of rows to pad on the top (if 0 the border and title will not appear on the top). /// Number of columns to pad on the right (if 0 the border will not appear on the right). /// Number of rows to pad on the bottom (if 0 the border will not appear on the bottom). /// Not yet implemented. /// public virtual void DrawWindowTitle (Rect region, ustring title, int paddingLeft, int paddingTop, int paddingRight, int paddingBottom, TextAlignment textAlignment = TextAlignment.Left) { var width = region.Width - (paddingLeft + 2) * 2; if (!ustring.IsNullOrEmpty (title) && width > 4 && region.Y + paddingTop <= region.Y + paddingBottom) { Move (region.X + 1 + paddingLeft, region.Y + paddingTop); AddRune (' '); var str = title.Sum (r => Math.Max (Rune.ColumnWidth (r), 1)) >= width ? TextFormatter.Format (title, width - 2, false, false) [0] : title; AddStr (str); AddRune (' '); } } /// /// 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 use /// 'L', 'R', 'T', and 'B' for padding instead of ' '. /// FramePadding = 0b_0000_0010, } /// /// Set flags to enable/disable diagnostics. /// public static DiagnosticFlags Diagnostics { get; set; } /// /// Draws a frame for a window with padding and an optional visible border inside the padding. /// /// Screen relative region where the frame will be drawn. /// Number of columns to pad on the left (if 0 the border will not appear on the left). /// Number of rows to pad on the top (if 0 the border and title will not appear on the top). /// Number of columns to pad on the right (if 0 the border will not appear on the right). /// Number of rows to pad on the bottom (if 0 the border will not appear on the bottom). /// If set to true and any padding dimension is > 0 the border will be drawn. /// If set to true it will clear the content area (the area inside the padding) with the current color, otherwise the content area will be left untouched. /// The to be used if defined. public virtual void DrawWindowFrame (Rect region, int paddingLeft = 0, int paddingTop = 0, int paddingRight = 0, int paddingBottom = 0, bool border = true, bool fill = false, Border borderContent = null) { char clearChar = ' '; char leftChar = clearChar; char rightChar = clearChar; char topChar = clearChar; char bottomChar = clearChar; if ((Diagnostics & DiagnosticFlags.FramePadding) == DiagnosticFlags.FramePadding) { leftChar = 'L'; rightChar = 'R'; topChar = 'T'; bottomChar = 'B'; clearChar = 'C'; } void AddRuneAt (int col, int row, Rune ch) { Move (col, row); AddRune (ch); } // fwidth is count of hLine chars int fwidth = (int)(region.Width - (paddingRight + paddingLeft)); // fheight is count of vLine chars int fheight = (int)(region.Height - (paddingBottom + paddingTop)); // fleft is location of left frame line int fleft = region.X + paddingLeft - 1; // fright is location of right frame line int fright = fleft + fwidth + 1; // ftop is location of top frame line int ftop = region.Y + paddingTop - 1; // fbottom is location of bottom frame line int fbottom = ftop + fheight + 1; var borderStyle = borderContent == null ? BorderStyle.Single : borderContent.BorderStyle; Rune hLine = default, vLine = default, uRCorner = default, uLCorner = default, lLCorner = default, lRCorner = default; if (border) { switch (borderStyle) { case BorderStyle.None: break; case BorderStyle.Single: hLine = HLine; vLine = VLine; uRCorner = URCorner; uLCorner = ULCorner; lLCorner = LLCorner; lRCorner = LRCorner; break; case BorderStyle.Double: hLine = HDLine; vLine = VDLine; uRCorner = URDCorner; uLCorner = ULDCorner; lLCorner = LLDCorner; lRCorner = LRDCorner; break; case BorderStyle.Rounded: hLine = HRLine; vLine = VRLine; uRCorner = URRCorner; uLCorner = ULRCorner; lLCorner = LLRCorner; lRCorner = LRRCorner; break; } } else { hLine = vLine = uRCorner = uLCorner = lLCorner = lRCorner = clearChar; } // Outside top if (paddingTop > 1) { for (int r = region.Y; r < ftop; r++) { for (int c = region.X; c < region.X + region.Width; c++) { AddRuneAt (c, r, topChar); } } } // Outside top-left for (int c = region.X; c < fleft; c++) { AddRuneAt (c, ftop, leftChar); } // Frame top-left corner AddRuneAt (fleft, ftop, paddingTop >= 0 ? (paddingLeft >= 0 ? uLCorner : hLine) : leftChar); // Frame top for (int c = fleft + 1; c < fleft + 1 + fwidth; c++) { AddRuneAt (c, ftop, paddingTop > 0 ? hLine : topChar); } // Frame top-right corner if (fright > fleft) { AddRuneAt (fright, ftop, paddingTop >= 0 ? (paddingRight >= 0 ? uRCorner : hLine) : rightChar); } // Outside top-right corner for (int c = fright + 1; c < fright + paddingRight; c++) { AddRuneAt (c, ftop, rightChar); } // Left, Fill, Right if (fbottom > ftop) { for (int r = ftop + 1; r < fbottom; r++) { // Outside left for (int c = region.X; c < fleft; c++) { AddRuneAt (c, r, leftChar); } // Frame left AddRuneAt (fleft, r, paddingLeft > 0 ? vLine : leftChar); // Fill if (fill) { for (int x = fleft + 1; x < fright; x++) { AddRuneAt (x, r, clearChar); } } // Frame right if (fright > fleft) { var v = vLine; if ((Diagnostics & DiagnosticFlags.FrameRuler) == DiagnosticFlags.FrameRuler) { v = (char)(((int)'0') + ((r - ftop) % 10)); // vLine; } AddRuneAt (fright, r, paddingRight > 0 ? v : rightChar); } // Outside right for (int c = fright + 1; c < fright + paddingRight; c++) { AddRuneAt (c, r, rightChar); } } // Outside Bottom for (int c = region.X; c < region.X + region.Width; c++) { AddRuneAt (c, fbottom, leftChar); } // Frame bottom-left AddRuneAt (fleft, fbottom, paddingLeft > 0 ? lLCorner : leftChar); if (fright > fleft) { // Frame bottom for (int c = fleft + 1; c < fright; c++) { var h = hLine; if ((Diagnostics & DiagnosticFlags.FrameRuler) == DiagnosticFlags.FrameRuler) { h = (char)(((int)'0') + ((c - fleft) % 10)); // hLine; } AddRuneAt (c, fbottom, paddingBottom > 0 ? h : bottomChar); } // Frame bottom-right AddRuneAt (fright, fbottom, paddingRight > 0 ? (paddingBottom > 0 ? lRCorner : hLine) : rightChar); } // Outside right for (int c = fright + 1; c < fright + paddingRight; c++) { AddRuneAt (c, fbottom, rightChar); } } // Out bottom - ensure top is always drawn if we overlap if (paddingBottom > 0) { for (int r = fbottom + 1; r < fbottom + paddingBottom; r++) { for (int c = region.X; c < region.X + region.Width; c++) { AddRuneAt (c, r, bottomChar); } } } } /// /// Draws a frame on the specified region with the specified padding around the frame. /// /// Screen relative region where the frame will be drawn. /// Padding to add on the sides. /// If set to true it will clear the contents with the current color, otherwise the contents will be left untouched. /// This API has been superseded by . /// This API is equivalent to calling DrawWindowFrame(Rect, p - 1, p - 1, p - 1, p - 1). In other words, /// A padding value of 0 means there is actually a one cell border. /// public virtual void DrawFrame (Rect region, int padding, bool fill) { // DrawFrame assumes the border is always at least one row/col thick // DrawWindowFrame assumes a padding of 0 means NO padding and no frame DrawWindowFrame (new Rect (region.X, region.Y, region.Width, region.Height), padding + 1, padding + 1, padding + 1, padding + 1, border: false, fill: fill); } /// /// Suspend the application, typically needs to save the state, suspend the app and upon return, reset the console driver. /// public abstract void Suspend (); Rect clip; /// /// Controls the current clipping region that AddRune/AddStr is subject to. /// /// The clip. public Rect Clip { get => clip; set => this.clip = value; } /// /// Start of mouse moves. /// public abstract void StartReportingMouseMoves (); /// /// Stop reporting mouses moves. /// public abstract void StopReportingMouseMoves (); /// /// Disables the cooked event processing from the mouse driver. At startup, it is assumed mouse events are cooked. /// public abstract void UncookMouse (); /// /// Enables the cooked event processing from the mouse driver /// public abstract void CookMouse (); /// /// Horizontal line character. /// public Rune HLine = '\u2500'; /// /// Vertical line character. /// public Rune VLine = '\u2502'; /// /// Stipple pattern /// public Rune Stipple = '\u2591'; /// /// Diamond character /// public Rune Diamond = '\u25ca'; /// /// Upper left corner /// public Rune ULCorner = '\u250C'; /// /// Lower left corner /// public Rune LLCorner = '\u2514'; /// /// Upper right corner /// public Rune URCorner = '\u2510'; /// /// Lower right corner /// public Rune LRCorner = '\u2518'; /// /// Left tee /// public Rune LeftTee = '\u251c'; /// /// Right tee /// public Rune RightTee = '\u2524'; /// /// Top tee /// public Rune TopTee = '\u252c'; /// /// The bottom tee. /// public Rune BottomTee = '\u2534'; /// /// Checkmark. /// public Rune Checked = '\u221a'; /// /// Un-checked checkmark. /// public Rune UnChecked = '\u2574'; /// /// Selected mark. /// public Rune Selected = '\u25cf'; /// /// Un-selected selected mark. /// public Rune UnSelected = '\u25cc'; /// /// Right Arrow. /// public Rune RightArrow = '\u25ba'; /// /// Left Arrow. /// public Rune LeftArrow = '\u25c4'; /// /// Down Arrow. /// public Rune DownArrow = '\u25bc'; /// /// Up Arrow. /// public Rune UpArrow = '\u25b2'; /// /// Left indicator for default action (e.g. for ). /// public Rune LeftDefaultIndicator = '\u25e6'; /// /// Right indicator for default action (e.g. for ). /// public Rune RightDefaultIndicator = '\u25e6'; /// /// Left frame/bracket (e.g. '[' for ). /// public Rune LeftBracket = '['; /// /// Right frame/bracket (e.g. ']' for ). /// public Rune RightBracket = ']'; /// /// Blocks Segment indicator for meter views (e.g. . /// public Rune BlocksMeterSegment = '\u258c'; /// /// Continuous Segment indicator for meter views (e.g. . /// public Rune ContinuousMeterSegment = '\u2588'; /// /// Horizontal double line character. /// public Rune HDLine = '\u2550'; /// /// Vertical double line character. /// public Rune VDLine = '\u2551'; /// /// Upper left double corner /// public Rune ULDCorner = '\u2554'; /// /// Lower left double corner /// public Rune LLDCorner = '\u255a'; /// /// Upper right double corner /// public Rune URDCorner = '\u2557'; /// /// Lower right double corner /// public Rune LRDCorner = '\u255d'; /// /// Horizontal line character for rounded corners. /// public Rune HRLine = '\u2500'; /// /// Vertical line character for rounded corners. /// public Rune VRLine = '\u2502'; /// /// Upper left rounded corner /// public Rune ULRCorner = '\u256d'; /// /// Lower left rounded corner /// public Rune LLRCorner = '\u2570'; /// /// Upper right rounded corner /// public Rune URRCorner = '\u256e'; /// /// Lower right rounded corner /// public Rune LRRCorner = '\u256f'; /// /// Make the attribute for the foreground and background colors. /// /// Foreground. /// Background. /// public abstract Attribute MakeAttribute (Color fore, Color back); /// /// Gets the current . /// /// The current attribute. public abstract Attribute GetAttribute (); /// /// Make the for the . /// /// The foreground color. /// The background color. /// The attribute for the foreground and background colors. public abstract Attribute MakeColor (Color foreground, Color background); /// /// Create all with the for the console driver. /// /// Flag indicating if colors are supported. public void CreateColors (bool hasColors = true) { Colors.TopLevel = new ColorScheme (); Colors.Base = new ColorScheme (); Colors.Dialog = new ColorScheme (); Colors.Menu = new ColorScheme (); Colors.Error = new ColorScheme (); if (!hasColors) { return; } Colors.TopLevel.Normal = MakeColor (Color.BrightGreen, Color.Black); Colors.TopLevel.Focus = MakeColor (Color.White, Color.Cyan); Colors.TopLevel.HotNormal = MakeColor (Color.Brown, Color.Black); Colors.TopLevel.HotFocus = MakeColor (Color.Blue, Color.Cyan); Colors.TopLevel.Disabled = MakeColor (Color.DarkGray, Color.Black); Colors.Base.Normal = MakeColor (Color.White, Color.Blue); Colors.Base.Focus = MakeColor (Color.Black, Color.Gray); Colors.Base.HotNormal = MakeColor (Color.BrightCyan, Color.Blue); Colors.Base.HotFocus = MakeColor (Color.BrightBlue, Color.Gray); Colors.Base.Disabled = MakeColor (Color.DarkGray, Color.Blue); Colors.Dialog.Normal = MakeColor (Color.Black, Color.Gray); Colors.Dialog.Focus = MakeColor (Color.White, Color.DarkGray); Colors.Dialog.HotNormal = MakeColor (Color.Blue, Color.Gray); Colors.Dialog.HotFocus = MakeColor (Color.BrightYellow, Color.DarkGray); Colors.Dialog.Disabled = MakeColor (Color.Gray, Color.DarkGray); Colors.Menu.Normal = MakeColor (Color.White, Color.DarkGray); Colors.Menu.Focus = MakeColor (Color.White, Color.Black); Colors.Menu.HotNormal = MakeColor (Color.BrightYellow, Color.DarkGray); Colors.Menu.HotFocus = MakeColor (Color.BrightYellow, Color.Black); Colors.Menu.Disabled = MakeColor (Color.Gray, Color.DarkGray); Colors.Error.Normal = MakeColor (Color.Red, Color.White); Colors.Error.Focus = MakeColor (Color.Black, Color.BrightRed); Colors.Error.HotNormal = MakeColor (Color.Black, Color.White); Colors.Error.HotFocus = MakeColor (Color.White, Color.BrightRed); Colors.Error.Disabled = MakeColor (Color.DarkGray, Color.White); } } /// /// Helper class for console drivers to invoke shell commands to interact with the clipboard. /// Used primarily by CursesDriver, but also used in Unit tests which is why it is in /// ConsoleDriver.cs. /// internal static class ClipboardProcessRunner { public static (int exitCode, string result) Bash (string commandLine, string inputText = "", bool waitForOutput = false) { var arguments = $"-c \"{commandLine}\""; var (exitCode, result) = Process ("bash", arguments, inputText, waitForOutput); return (exitCode, result.TrimEnd ()); } public static (int exitCode, string result) Process (string cmd, string arguments, string input = null, bool waitForOutput = true) { var output = string.Empty; using (Process process = new Process { StartInfo = new ProcessStartInfo { FileName = cmd, Arguments = arguments, RedirectStandardOutput = true, RedirectStandardError = true, RedirectStandardInput = true, UseShellExecute = false, CreateNoWindow = true, } }) { var eventHandled = new TaskCompletionSource (); process.Start (); if (!string.IsNullOrEmpty (input)) { process.StandardInput.Write (input); process.StandardInput.Close (); } if (!process.WaitForExit (5000)) { var timeoutError = $@"Process timed out. Command line: {process.StartInfo.FileName} {process.StartInfo.Arguments}."; throw new TimeoutException (timeoutError); } if (waitForOutput && process.StandardOutput.Peek () != -1) { output = process.StandardOutput.ReadToEnd (); } if (process.ExitCode > 0) { output = $@"Process failed to run. Command line: {cmd} {arguments}. Output: {output} Error: {process.StandardError.ReadToEnd ()}"; } return (process.ExitCode, output); } } public static bool DoubleWaitForExit (this System.Diagnostics.Process process) { var result = process.WaitForExit (500); if (result) { process.WaitForExit (); } return result; } public static bool FileExists (this string value) { return !string.IsNullOrEmpty (value) && !value.Contains ("not found"); } } }