diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml new file mode 100644 index 000000000..f734ea7b5 --- /dev/null +++ b/.github/workflows/dotnetcore.yml @@ -0,0 +1,25 @@ +name: .NET Core + +on: + push: + branches: [ master ] + pull_request: + branches: [ master ] + +jobs: + build: + + runs-on: ubuntu-latest + + steps: + - uses: actions/checkout@v2 + - name: Setup .NET Core + uses: actions/setup-dotnet@v1 + with: + dotnet-version: 3.1.101 + - name: Install dependencies + run: dotnet restore + - name: Build + run: dotnet build --configuration Release --no-restore + - name: Test + run: dotnet test --no-restore --verbosity normal diff --git a/Designer/Designer.csproj b/Designer/Designer.csproj index 545c2b0b9..84de5ca1d 100644 --- a/Designer/Designer.csproj +++ b/Designer/Designer.csproj @@ -8,6 +8,7 @@ Designer Designer v4.7.2 + win-x86 diff --git a/Example/Example.csproj b/Example/Example.csproj index dc751b396..001459c47 100644 --- a/Example/Example.csproj +++ b/Example/Example.csproj @@ -8,6 +8,7 @@ Terminal Terminal v4.7.2 + win-x64 diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index ad1fb8549..1d405ffc1 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -899,10 +899,7 @@ namespace Terminal.Gui { Move (frame.X, frame.Y); } - /// - /// Gets or sets a value indicating whether this has focus. - /// - /// true if has focus; otherwise, false. + /// public override bool HasFocus { get { return base.HasFocus; @@ -925,12 +922,14 @@ namespace Terminal.Gui { } } + /// public override bool OnEnter () { Enter?.Invoke (this, new EventArgs ()); return base.OnEnter (); } + /// public override bool OnLeave () { Leave?.Invoke (this, new EventArgs ()); @@ -1072,11 +1071,11 @@ namespace Terminal.Gui { } /// - /// Invoked when a character key is pressed and occurs after the key down event. + /// Invoked when a character key is pressed and occurs after the key up event. /// public event EventHandler KeyPress; - /// Contains the details about the key that produced the event. + /// public override bool ProcessKey (KeyEvent keyEvent) { KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); @@ -1086,7 +1085,7 @@ namespace Terminal.Gui { return false; } - /// Contains the details about the key that produced the event. + /// public override bool ProcessHotKey (KeyEvent keyEvent) { KeyPress?.Invoke (this, new KeyEventEventArgs (keyEvent)); @@ -1098,7 +1097,7 @@ namespace Terminal.Gui { return false; } - /// Contains the details about the key that produced the event. + /// public override bool ProcessColdKey (KeyEvent keyEvent) { KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); @@ -1145,6 +1144,7 @@ namespace Terminal.Gui { return false; } + /// /// Finds the first view in the hierarchy that wants to get the focus if nothing is currently focused, otherwise, it does nothing. /// @@ -1415,15 +1415,13 @@ namespace Terminal.Gui { layoutNeeded = false; } - /// - /// Returns a that represents the current . - /// - /// A that represents the current . + /// public override string ToString () { return $"{GetType ().Name}({Id})({Frame})"; } + /// public override bool OnMouseEnter (MouseEvent mouseEvent) { if (!base.OnMouseEnter (mouseEvent)) { @@ -1433,6 +1431,7 @@ namespace Terminal.Gui { return true; } + /// public override bool OnMouseLeave (MouseEvent mouseEvent) { if (!base.OnMouseLeave (mouseEvent)) { @@ -1475,10 +1474,25 @@ namespace Terminal.Gui { /// public class Toplevel : View { /// - /// This flag is checked on each iteration of the mainloop and it continues - /// running until this flag is set to false. + /// Gets or sets whether the for this is running or not. Setting + /// this property to false will cause the MainLoop to exit. /// - public bool Running; + public bool Running { get; set; } + + /// + /// Fired once the Toplevel's has started it's first iteration. + /// Subscribe to this event to perform tasks when the has been laid out and focus has been set. + /// changes. A Ready event handler is a good place to finalize initialization after calling `(topLevel)`. + /// + public event EventHandler Ready; + + /// + /// Called from Application.RunLoop after the has entered it's first iteration of the loop. + /// + internal virtual void OnReady () + { + Ready?.Invoke (this, EventArgs.Empty); + } /// /// Initializes a new instance of the class with the specified absolute layout. @@ -1513,6 +1527,10 @@ namespace Terminal.Gui { return new Toplevel (new Rect (0, 0, Driver.Cols, Driver.Rows)); } + /// + /// Gets or sets a value indicating whether this can focus. + /// + /// true if can focus; otherwise, false. public override bool CanFocus { get => true; } @@ -1534,6 +1552,7 @@ namespace Terminal.Gui { /// public bool HasStatusBar { get; set; } + /// public override bool ProcessKey (KeyEvent keyEvent) { if (base.ProcessKey (keyEvent)) @@ -1585,6 +1604,7 @@ namespace Terminal.Gui { return false; } + /// public override void Add (View view) { if (this == Application.Top) { @@ -1596,6 +1616,7 @@ namespace Terminal.Gui { base.Add (view); } + /// public override void Remove (View view) { if (this == Application.Top) { @@ -1607,6 +1628,7 @@ namespace Terminal.Gui { base.Remove (view); } + /// public override void RemoveAll () { if (this == Application.Top) { @@ -1661,6 +1683,7 @@ namespace Terminal.Gui { } } + /// public override void Redraw (Rect region) { Application.CurrentView = this; @@ -1843,6 +1866,7 @@ namespace Terminal.Gui { contentView.RemoveAll (); } + /// public override void Redraw (Rect bounds) { Application.CurrentView = this; @@ -1878,6 +1902,7 @@ namespace Terminal.Gui { // internal static Point? dragPosition; Point start; + /// public override bool MouseEvent (MouseEvent mouseEvent) { // FIXED:The code is currently disabled, because the @@ -2414,8 +2439,15 @@ namespace Terminal.Gui { if (state.Toplevel == null) throw new ObjectDisposedException ("state"); + bool firstIteration = true; for (state.Toplevel.Running = true; state.Toplevel.Running;) { if (MainLoop.EventsPending (wait)) { + // Notify Toplevel it's ready + if (firstIteration) { + state.Toplevel.OnReady (); + } + firstIteration = false; + MainLoop.MainIteration (); Iteration?.Invoke (null, EventArgs.Empty); } else if (wait == false) @@ -2431,7 +2463,7 @@ namespace Terminal.Gui { } } - internal static bool DebugDrawBounds; + internal static bool DebugDrawBounds = false; // Need to look into why this does not work properly. static void DrawBounds (View v) diff --git a/Terminal.Gui/Dialogs/Dialog.cs b/Terminal.Gui/Dialogs/Dialog.cs index a1f809b97..2aef337d5 100644 --- a/Terminal.Gui/Dialogs/Dialog.cs +++ b/Terminal.Gui/Dialogs/Dialog.cs @@ -61,7 +61,9 @@ namespace Terminal.Gui { Add (button); } - + /// + /// Lays out the subviews for the Dialog. + /// public override void LayoutSubviews () { base.LayoutSubviews (); @@ -86,6 +88,7 @@ namespace Terminal.Gui { } } + /// public override bool ProcessKey (KeyEvent kb) { switch (kb.Key) { diff --git a/Terminal.Gui/Dialogs/FileDialog.cs b/Terminal.Gui/Dialogs/FileDialog.cs index 069b8eea4..7c9b54374 100644 --- a/Terminal.Gui/Dialogs/FileDialog.cs +++ b/Terminal.Gui/Dialogs/FileDialog.cs @@ -212,9 +212,9 @@ namespace Terminal.Gui { } } - public Action<(string,bool)> SelectedChanged; - public Action DirectoryChanged; - public Action FileChanged; + public Action<(string, bool)> SelectedChanged { get; set; } + public Action DirectoryChanged { get; set; } + public Action FileChanged { get; set; } void SelectionChanged () { @@ -494,6 +494,7 @@ namespace Terminal.Gui { internal bool canceled; + /// public override void WillPresent () { base.WillPresent (); diff --git a/Terminal.Gui/Drivers/ConsoleDriver.cs b/Terminal.Gui/Drivers/ConsoleDriver.cs index 92be65a21..32f488674 100644 --- a/Terminal.Gui/Drivers/ConsoleDriver.cs +++ b/Terminal.Gui/Drivers/ConsoleDriver.cs @@ -466,9 +466,11 @@ namespace Terminal.Gui { /// /// 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); /// diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs index 1ecfb47a6..95a658108 100644 --- a/Terminal.Gui/Drivers/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver.cs @@ -17,6 +17,7 @@ namespace Terminal.Gui { /// This is the Curses driver for the gui.cs/Terminal framework. /// public class CursesDriver : ConsoleDriver { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public override int Cols => Curses.Cols; public override int Rows => Curses.Lines; @@ -37,7 +38,7 @@ namespace Terminal.Gui { } } - static bool sync; + static bool sync = false; public override void AddRune (Rune rune) { if (Clip.Contains (ccol, crow)) { @@ -51,7 +52,7 @@ namespace Terminal.Gui { if (sync) Application.Driver.Refresh (); ccol++; - var runeWidth = Rune.ColumnWidth(rune); + var runeWidth = Rune.ColumnWidth (rune); if (runeWidth > 1) { for (int i = 1; i < runeWidth; i++) { ccol++; @@ -66,8 +67,13 @@ namespace Terminal.Gui { AddRune (rune); } - public override void Refresh () => Curses.refresh (); - public override void UpdateCursor () => Curses.refresh (); + public override void Refresh () { + Curses.refresh (); + if (Curses.CheckWinChange ()) { + TerminalResized?.Invoke (); + } + } + public override void UpdateCursor () => Refresh (); public override void End () => Curses.endwin (); public override void UpdateScreen () => window.redrawwin (); public override void SetAttribute (Attribute c) => Curses.attrset (c.value); @@ -186,7 +192,7 @@ namespace Terminal.Gui { }; } - void ProcessInput (Action keyHandler, Action mouseHandler) + void ProcessInput (Action keyHandler, Action keyUpHandler, Action mouseHandler) { int wch; var code = Curses.get_wch (out wch); @@ -206,6 +212,7 @@ namespace Terminal.Gui { return; } keyHandler (new KeyEvent (MapCursesKey (wch))); + keyUpHandler (new KeyEvent (MapCursesKey (wch))); return; } @@ -213,7 +220,7 @@ namespace Terminal.Gui { if (wch == 27) { Curses.timeout (200); - code = Curses.get_wch (out wch); + code = Curses.get_wch (out int wch2); if (code == Curses.KEY_CODE_YES) keyHandler (new KeyEvent (Key.AltMask | MapCursesKey (wch))); if (code == 0) { @@ -221,23 +228,28 @@ namespace Terminal.Gui { // The ESC-number handling, debatable. // Simulates the AltMask itself by pressing Alt + Space. - if (wch == (int)Key.Space) + if (wch2 == (int)Key.Space) key = new KeyEvent (Key.AltMask); - else if (wch - (int)Key.Space >= 'A' && wch - (int)Key.Space <= 'Z') - key = new KeyEvent ((Key)((uint)Key.AltMask + (wch - (int)Key.Space))); - else if (wch >= '1' && wch <= '9') - key = new KeyEvent ((Key)((int)Key.F1 + (wch - '0' - 1))); - else if (wch == '0') + else if (wch2 - (int)Key.Space >= 'A' && wch2 - (int)Key.Space <= 'Z') + key = new KeyEvent ((Key)((uint)Key.AltMask + (wch2 - (int)Key.Space))); + else if (wch2 >= '1' && wch <= '9') + key = new KeyEvent ((Key)((int)Key.F1 + (wch2 - '0' - 1))); + else if (wch2 == '0') key = new KeyEvent (Key.F10); - else if (wch == 27) - key = new KeyEvent ((Key)wch); + else if (wch2 == 27) + key = new KeyEvent ((Key)wch2); else - key = new KeyEvent (Key.AltMask | (Key)wch); + key = new KeyEvent (Key.AltMask | (Key)wch2); keyHandler (key); - } else + } else { keyHandler (new KeyEvent (Key.Esc)); - } else + } + } else { keyHandler (new KeyEvent ((Key)wch)); + } + // Cause OnKeyUp and OnKeyPressed. Note that the special handling for ESC above + // will not impact KeyUp. + keyUpHandler (new KeyEvent ((Key)wch)); } public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) @@ -246,7 +258,7 @@ namespace Terminal.Gui { Curses.timeout (-1); (mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => { - ProcessInput (keyHandler, mouseHandler); + ProcessInput (keyHandler, keyUpHandler, mouseHandler); return true; }); @@ -308,7 +320,7 @@ namespace Terminal.Gui { Colors.Menu.Focus = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_BLACK); Colors.Menu.HotNormal = Curses.A_BOLD | MakeColor (Curses.COLOR_YELLOW, Curses.COLOR_CYAN); Colors.Menu.Normal = Curses.A_BOLD | MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN); - Colors.Menu.Disabled = MakeColor(Curses.COLOR_WHITE, Curses.COLOR_CYAN); + Colors.Menu.Disabled = MakeColor (Curses.COLOR_WHITE, Curses.COLOR_CYAN); Colors.Dialog.Normal = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_WHITE); Colors.Dialog.Focus = MakeColor (Curses.COLOR_BLACK, Curses.COLOR_CYAN); @@ -490,6 +502,7 @@ namespace Terminal.Gui { killpg (0, signal); return true; } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } } diff --git a/Terminal.Gui/Drivers/NetDriver.cs b/Terminal.Gui/Drivers/NetDriver.cs index 96224c11a..3b7fa188d 100644 --- a/Terminal.Gui/Drivers/NetDriver.cs +++ b/Terminal.Gui/Drivers/NetDriver.cs @@ -37,7 +37,7 @@ namespace Terminal.Gui { dirtyLine [row] = true; } - static bool sync; + static bool sync = false; public NetDriver () { @@ -328,6 +328,7 @@ namespace Terminal.Gui { if (map == (Key)0xffffffff) return; keyHandler (new KeyEvent (map)); + keyUpHandler (new KeyEvent (map)); }; } diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 5fc86fa78..343182dc0 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -97,7 +97,7 @@ namespace Terminal.Gui { public void Cleanup () { ConsoleMode = originalConsoleMode; - ContinueListeningForConsoleEvents = false; + //ContinueListeningForConsoleEvents = false; if (!SetConsoleActiveScreenBuffer (OutputHandle)) { var err = Marshal.GetLastWin32Error (); Console.WriteLine ("Error: {0}", err); @@ -109,7 +109,7 @@ namespace Terminal.Gui { ScreenBuffer = IntPtr.Zero; } - bool ContinueListeningForConsoleEvents = true; + //bool ContinueListeningForConsoleEvents = true; public uint ConsoleMode { get { @@ -426,7 +426,7 @@ namespace Terminal.Gui { } internal class WindowsDriver : ConsoleDriver, Mono.Terminal.IMainLoopDriver { - static bool sync; + static bool sync = false; ManualResetEventSlim eventReady = new ManualResetEventSlim (false); ManualResetEventSlim waitForProbe = new ManualResetEventSlim (false); MainLoop mainLoop; @@ -440,15 +440,10 @@ namespace Terminal.Gui { public WindowsDriver () { - Colors.TopLevel = new ColorScheme (); - - Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black); - Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); - Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); - Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.DarkCyan); - winConsole = new WindowsConsole (); + SetupColorsAndBorders (); + cols = Console.WindowWidth; rows = Console.WindowHeight; WindowsConsole.SmallRect.MakeEmpty (ref damageRegion); @@ -459,6 +454,54 @@ namespace Terminal.Gui { Task.Run ((Action)WindowsInputHandler); } + private void SetupColorsAndBorders () + { + Colors.TopLevel = new ColorScheme (); + Colors.Base = new ColorScheme (); + Colors.Dialog = new ColorScheme (); + Colors.Menu = new ColorScheme (); + Colors.Error = new ColorScheme (); + + Colors.TopLevel.Normal = MakeColor (ConsoleColor.Green, ConsoleColor.Black); + Colors.TopLevel.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkCyan); + Colors.TopLevel.HotNormal = MakeColor (ConsoleColor.DarkYellow, ConsoleColor.Black); + Colors.TopLevel.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkCyan); + + Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkBlue); + Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); + Colors.Base.HotNormal = MakeColor (ConsoleColor.DarkCyan, ConsoleColor.DarkBlue); + Colors.Base.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); + + Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.DarkGray); + Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black); + Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.DarkGray); + Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black); + Colors.Menu.Disabled = MakeColor (ConsoleColor.Gray, ConsoleColor.DarkGray); + + Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); + Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkGray); + Colors.Dialog.HotNormal = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.Gray); + Colors.Dialog.HotFocus = MakeColor (ConsoleColor.DarkBlue, ConsoleColor.DarkGray); + + Colors.Error.Normal = MakeColor (ConsoleColor.DarkRed, ConsoleColor.White); + Colors.Error.Focus = MakeColor (ConsoleColor.White, ConsoleColor.DarkRed); + Colors.Error.HotNormal = MakeColor (ConsoleColor.Black, ConsoleColor.White); + Colors.Error.HotFocus = MakeColor (ConsoleColor.Black, ConsoleColor.DarkRed); + + HLine = '\u2500'; + VLine = '\u2502'; + Stipple = '\u2592'; + Diamond = '\u25c6'; + ULCorner = '\u250C'; + LLCorner = '\u2514'; + URCorner = '\u2510'; + LRCorner = '\u2518'; + LeftTee = '\u251c'; + RightTee = '\u2524'; + TopTee = '\u22a4'; + BottomTee = '\u22a5'; + } + [StructLayout (LayoutKind.Sequential)] public struct ConsoleKeyInfoEx { public ConsoleKeyInfo consoleKeyInfo; @@ -564,11 +607,24 @@ namespace Terminal.Gui { case WindowsConsole.EventType.Key: var map = MapKey (ToConsoleKeyInfoEx (inputEvent.KeyEvent)); if (map == (Key)0xffffffff) { - KeyEvent key = default; + KeyEvent key = new KeyEvent (); + // Shift = VK_SHIFT = 0x10 // Ctrl = VK_CONTROL = 0x11 // Alt = VK_MENU = 0x12 + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.CapslockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.CapslockOn; + } + + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.ScrolllockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.ScrolllockOn; + } + + if (inputEvent.KeyEvent.dwControlKeyState.HasFlag (WindowsConsole.ControlKeyState.NumlockOn)) { + inputEvent.KeyEvent.dwControlKeyState &= ~WindowsConsole.ControlKeyState.NumlockOn; + } + switch (inputEvent.KeyEvent.dwControlKeyState) { case WindowsConsole.ControlKeyState.RightAltPressed: case WindowsConsole.ControlKeyState.RightAltPressed | @@ -617,10 +673,10 @@ namespace Terminal.Gui { keyUpHandler (key); } else { if (inputEvent.KeyEvent.bKeyDown) { - // Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event keyDownHandler (new KeyEvent (map)); - keyHandler (new KeyEvent (map)); } else { + // Key Up - Fire KeyDown Event and KeyStroke (ProcessKey) Event + keyHandler (new KeyEvent (map)); keyUpHandler (new KeyEvent (map)); } } @@ -807,6 +863,8 @@ namespace Terminal.Gui { } else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved) { mouseFlag = MouseFlags.ReportMousePosition; + } else if (mouseEvent.ButtonState == 0 && mouseEvent.EventFlags == 0) { + mouseFlag = 0; } mouseFlag = SetControlKeyStates (mouseEvent, mouseFlag); @@ -916,6 +974,9 @@ namespace Terminal.Gui { case ConsoleKey.OemComma: case ConsoleKey.OemPlus: case ConsoleKey.OemMinus: + if (keyInfo.KeyChar == 0) + return Key.Unknown; + return (Key)((uint)keyInfo.KeyChar); } @@ -969,48 +1030,10 @@ namespace Terminal.Gui { public override void Init (Action terminalResized) { TerminalResized = terminalResized; - - Colors.Base = new ColorScheme (); - Colors.Dialog = new ColorScheme (); - Colors.Menu = new ColorScheme (); - Colors.Error = new ColorScheme (); - - HLine = '\u2500'; - VLine = '\u2502'; - Stipple = '\u2592'; - Diamond = '\u25c6'; - ULCorner = '\u250C'; - LLCorner = '\u2514'; - URCorner = '\u2510'; - LRCorner = '\u2518'; - LeftTee = '\u251c'; - RightTee = '\u2524'; - TopTee = '\u22a4'; - BottomTee = '\u22a5'; - - Colors.Base.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Blue); - Colors.Base.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan); - Colors.Base.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Blue); - Colors.Base.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan); - - Colors.Menu.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Cyan); - Colors.Menu.Focus = MakeColor (ConsoleColor.White, ConsoleColor.Black); - Colors.Menu.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Cyan); - Colors.Menu.HotFocus = MakeColor (ConsoleColor.Yellow, ConsoleColor.Black); - Colors.Menu.Disabled = MakeColor (ConsoleColor.DarkGray, ConsoleColor.Cyan); - - Colors.Dialog.Normal = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); - Colors.Dialog.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Cyan); - Colors.Dialog.HotNormal = MakeColor (ConsoleColor.Blue, ConsoleColor.Gray); - Colors.Dialog.HotFocus = MakeColor (ConsoleColor.Blue, ConsoleColor.Cyan); - - Colors.Error.Normal = MakeColor (ConsoleColor.White, ConsoleColor.Red); - Colors.Error.Focus = MakeColor (ConsoleColor.Black, ConsoleColor.Gray); - Colors.Error.HotNormal = MakeColor (ConsoleColor.Yellow, ConsoleColor.Red); - Colors.Error.HotFocus = Colors.Error.HotNormal; - Console.Clear (); + SetupColorsAndBorders (); } + void ResizeScreen () { OutputBuffer = new WindowsConsole.CharInfo [Rows * Cols]; diff --git a/Terminal.Gui/Event.cs b/Terminal.Gui/Event.cs index c4e9b9e1a..169b60084 100644 --- a/Terminal.Gui/Event.cs +++ b/Terminal.Gui/Event.cs @@ -45,7 +45,7 @@ namespace Terminal.Gui { ControlSpace = 0, /// - /// The key code for the user pressing Control-A + /// The key code for the user pressing Control-A /// ControlA = 1, /// @@ -288,8 +288,7 @@ namespace Terminal.Gui { /// /// Describes a keyboard event. /// - public struct KeyEvent { - + public class KeyEvent { /// /// Symb olid definition for the key. /// @@ -321,6 +320,10 @@ namespace Terminal.Gui { //public bool IsCtrl => ((uint)Key >= 1) && ((uint)Key <= 26); public bool IsCtrl => (Key & Key.CtrlMask) != 0; + public KeyEvent () + { + Key = Key.Unknown; + } /// /// Constructs a new KeyEvent from the provided Key value - can be a rune cast into a Key value /// @@ -328,6 +331,28 @@ namespace Terminal.Gui { { Key = k; } + + public override string ToString () + { + string msg = ""; + var key = this.Key; + if ((this.Key & Key.ShiftMask) != 0) { + msg += "Shift-"; + } + if ((this.Key & Key.CtrlMask) != 0) { + msg += "Ctrl-"; + } + if ((this.Key & Key.AltMask) != 0) { + msg += "Alt-"; + } + + if (string.IsNullOrEmpty (msg)) { + msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"{key}")}"; + } else { + msg += $"{(((uint)this.KeyValue & (uint)Key.CharMask) > 27 ? $"{(char)this.KeyValue}" : $"")}"; + } + return msg; + } } /// @@ -486,7 +511,7 @@ namespace Terminal.Gui { /// Returns a that represents the current . /// /// A that represents the current . - public override string ToString() + public override string ToString () { return $"({X},{Y}:{Flags}"; } diff --git a/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs b/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs index 09882dbfd..92c0bbca2 100644 --- a/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs +++ b/Terminal.Gui/MonoCurses/UnmanagedLibrary.cs @@ -37,7 +37,11 @@ namespace Mono.Terminal.Internal { const string XamarinIOSObjectClassName = "Foundation.NSObject, Xamarin.iOS"; static bool IsWindows, IsLinux, IsMacOS; static bool Is64Bit; +#if GUICS + static bool IsMono; +#else static bool IsMono, IsUnity, IsXamarinIOS, IsXamarinAndroid, IsXamarin; +#endif static bool IsNetCore; public static bool IsMacOSPlatform => IsMacOS; @@ -75,7 +79,7 @@ namespace Mono.Terminal.Internal { IsNetCore = Type.GetType ("System.MathF") != null; } #if GUICS - IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false; + //IsUnity = IsXamarinIOS = IsXamarinAndroid = IsXamarin = false; #else IsUnity = Type.GetType (UnityEngineApplicationClassName) != null; IsXamarinIOS = Type.GetType (XamarinIOSObjectClassName) != null; diff --git a/Terminal.Gui/MonoCurses/binding.cs b/Terminal.Gui/MonoCurses/binding.cs index bd9167801..de1ca1f7e 100644 --- a/Terminal.Gui/MonoCurses/binding.cs +++ b/Terminal.Gui/MonoCurses/binding.cs @@ -47,6 +47,7 @@ using System.Runtime.InteropServices; using Mono.Terminal.Internal; namespace Unix.Terminal { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public partial class Curses { [StructLayout (LayoutKind.Sequential)] @@ -61,7 +62,7 @@ namespace Unix.Terminal { static IntPtr curses_handle, curscr_ptr, lines_ptr, cols_ptr; // If true, uses the DllImport into "ncurses", otherwise "libncursesw.so.5" - static bool use_naked_driver; + //static bool use_naked_driver; static UnmanagedLibrary curses_library; static NativeMethods methods; @@ -451,4 +452,5 @@ namespace Unix.Terminal { mousemask = lib.GetNativeMethodDelegate ("mousemask"); } } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/Terminal.Gui/MonoCurses/constants.cs b/Terminal.Gui/MonoCurses/constants.cs index c2da74157..df19c30e2 100644 --- a/Terminal.Gui/MonoCurses/constants.cs +++ b/Terminal.Gui/MonoCurses/constants.cs @@ -5,6 +5,7 @@ using System; namespace Unix.Terminal { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public partial class Curses { public const int A_NORMAL = unchecked((int)0x0); public const int A_STANDOUT = unchecked((int)0x10000); @@ -139,4 +140,5 @@ namespace Unix.Terminal { return 0 + n * 256; } } +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } diff --git a/Terminal.Gui/MonoCurses/handles.cs b/Terminal.Gui/MonoCurses/handles.cs index 85a1b6ac2..e81528ade 100644 --- a/Terminal.Gui/MonoCurses/handles.cs +++ b/Terminal.Gui/MonoCurses/handles.cs @@ -31,6 +31,7 @@ using System.Runtime.InteropServices; namespace Unix.Terminal { public partial class Curses { +#pragma warning disable CS1591 // Missing XML comment for publicly visible type or member public class Window { public readonly IntPtr Handle; static Window curscr; @@ -167,7 +168,8 @@ namespace Unix.Terminal { Handle = handle; } } - + +#pragma warning restore CS1591 // Missing XML comment for publicly visible type or member } } diff --git a/Terminal.Gui/MonoCurses/mainloop.cs b/Terminal.Gui/MonoCurses/mainloop.cs index 21bd1571a..5a9c49360 100644 --- a/Terminal.Gui/MonoCurses/mainloop.cs +++ b/Terminal.Gui/MonoCurses/mainloop.cs @@ -53,6 +53,10 @@ namespace Mono.Terminal { /// true, if there were pending events, false otherwise. /// If set to true wait until an event is available, otherwise return immediately. bool EventsPending (bool wait); + + /// + /// The interation function. + /// void MainIteration (); } diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs index 39788ea78..8fd4e2a2c 100644 --- a/Terminal.Gui/Views/Button.cs +++ b/Terminal.Gui/Views/Button.cs @@ -153,6 +153,7 @@ namespace Terminal.Gui { Text = text; } + /// public override void Redraw (Rect region) { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); @@ -166,6 +167,7 @@ namespace Terminal.Gui { } } + /// public override void PositionCursor () { Move (hot_pos == -1 ? 1 : hot_pos, 0); @@ -181,6 +183,7 @@ namespace Terminal.Gui { return false; } + /// public override bool ProcessHotKey (KeyEvent kb) { if (kb.IsAlt) @@ -189,6 +192,7 @@ namespace Terminal.Gui { return false; } + /// public override bool ProcessColdKey (KeyEvent kb) { if (IsDefault && kb.KeyValue == '\n') { @@ -199,6 +203,7 @@ namespace Terminal.Gui { return CheckKey (kb); } + /// public override bool ProcessKey (KeyEvent kb) { var c = kb.KeyValue; @@ -210,6 +215,7 @@ namespace Terminal.Gui { return base.ProcessKey (kb); } + /// public override bool MouseEvent(MouseEvent me) { if (me.Flags == MouseFlags.Button1Clicked) { diff --git a/Terminal.Gui/Views/Checkbox.cs b/Terminal.Gui/Views/Checkbox.cs index 5ae8bb428..06cb26ab3 100644 --- a/Terminal.Gui/Views/Checkbox.cs +++ b/Terminal.Gui/Views/Checkbox.cs @@ -99,6 +99,7 @@ namespace Terminal.Gui { } } + /// public override void Redraw (Rect region) { Driver.SetAttribute (HasFocus ? ColorScheme.Focus : ColorScheme.Normal); @@ -113,11 +114,13 @@ namespace Terminal.Gui { } } + /// public override void PositionCursor () { Move (1, 0); } + /// public override bool ProcessKey (KeyEvent kb) { if (kb.KeyValue == ' ') { @@ -132,6 +135,7 @@ namespace Terminal.Gui { return base.ProcessKey (kb); } + /// public override bool MouseEvent (MouseEvent me) { if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) diff --git a/Terminal.Gui/Views/Clipboard.cs b/Terminal.Gui/Views/Clipboard.cs index eb1860f10..e0615da01 100644 --- a/Terminal.Gui/Views/Clipboard.cs +++ b/Terminal.Gui/Views/Clipboard.cs @@ -2,7 +2,13 @@ using NStack; namespace Terminal.Gui { + /// + /// + /// public static class Clipboard { + /// + /// + /// public static ustring Contents { get; set; } } } diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs index 1f1915a10..0c82ea499 100644 --- a/Terminal.Gui/Views/DateField.cs +++ b/Terminal.Gui/Views/DateField.cs @@ -41,7 +41,7 @@ namespace Terminal.Gui { { CultureInfo cultureInfo = CultureInfo.CurrentCulture; sepChar = cultureInfo.DateTimeFormat.DateSeparator; - longFormat = $" {cultureInfo.DateTimeFormat.ShortDatePattern}"; + longFormat = GetLongFormat (cultureInfo.DateTimeFormat.ShortDatePattern); shortFormat = GetShortFormat(longFormat); this.isShort = isShort; CursorPosition = 1; @@ -55,7 +55,21 @@ namespace Terminal.Gui { Text = e; } - string GetShortFormat(string lf) + string GetLongFormat (string lf) + { + ustring [] frm = ustring.Make (lf).Split (ustring.Make (sepChar)); + for (int i = 0; i < frm.Length; i++) { + if (frm [i].Contains ("M") && frm [i].Length < 2) + lf = lf.Replace ("M", "MM"); + if (frm [i].Contains ("d") && frm [i].Length < 2) + lf = lf.Replace ("d", "dd"); + if (frm [i].Contains ("y") && frm [i].Length < 4) + lf = lf.Replace ("yy", "yyyy"); + } + return $" {lf}"; + } + + string GetShortFormat (string lf) { return lf.Replace("yyyy", "yy"); } diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs index 313179268..63aea9470 100644 --- a/Terminal.Gui/Views/HexView.cs +++ b/Terminal.Gui/Views/HexView.cs @@ -98,6 +98,7 @@ namespace Terminal.Gui { const int bsize = 4; int bytesPerLine; + /// public override Rect Frame { get => base.Frame; set { @@ -128,6 +129,7 @@ namespace Terminal.Gui { return buffer [offset]; } + /// public override void Redraw (Rect region) { Attribute currentAttribute; @@ -280,6 +282,7 @@ namespace Terminal.Gui { RedisplayLine (position); } + /// public override bool ProcessKey (KeyEvent keyEvent) { switch (keyEvent.Key) { diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs index f87d941ee..b265fccda 100644 --- a/Terminal.Gui/Views/Label.cs +++ b/Terminal.Gui/Views/Label.cs @@ -124,7 +124,7 @@ namespace Terminal.Gui { for (int i = 0; i < spaces; i++) s.Append (' '); if (extras > 0) { - s.Append ('_'); + //s.Append ('_'); extras--; } } @@ -160,6 +160,7 @@ namespace Terminal.Gui { lineResult.Add(ClipAndJustify(textStr[lp, textLen], width, talign)); } + /// public override void Redraw (Rect region) { if (recalcPending) @@ -178,8 +179,11 @@ namespace Terminal.Gui { int x; switch (textAlignment) { case TextAlignment.Left: + x = Frame.Left; + break; case TextAlignment.Justified: - x = 0; + Recalc (); + x = Frame.Left; break; case TextAlignment.Right: x = Frame.Right - str.Length; diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 02deb08ad..309c408e0 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -294,6 +294,11 @@ namespace Terminal.Gui { /// public event Action SelectedChanged; + /// + /// This event is raised on Enter key or Double Click to open the selected item. + /// + public event EventHandler OpenSelectedItem; + /// /// Handles cursor movement for this view, passes all other events. /// @@ -325,10 +330,19 @@ namespace Terminal.Gui { return true; else break; + + case Key.Enter: + OpenSelectedItem?.Invoke (this, new EventArgs ()); + break; + } return base.ProcessKey (kb); } + /// + /// + /// + /// public virtual bool AllowsAll () { if (!allowsMarking) @@ -344,6 +358,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MarkUnmarkRow(){ if (AllowsAll ()) { Source.SetMark(SelectedItem, !Source.IsMarked(SelectedItem)); @@ -354,6 +372,10 @@ namespace Terminal.Gui { return false; } + /// + /// + /// + /// public virtual bool MovePageUp(){ int n = (selected - Frame.Height); if (n < 0) @@ -369,6 +391,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MovePageDown(){ var n = (selected + Frame.Height); if (n > source.Count) @@ -387,6 +413,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MoveDown(){ if (selected + 1 < source.Count){ selected++; @@ -400,6 +430,10 @@ namespace Terminal.Gui { return true; } + /// + /// + /// + /// public virtual bool MoveUp(){ if (selected > 0){ selected--; @@ -424,9 +458,10 @@ namespace Terminal.Gui { Move (0, selected - top); } + /// public override bool MouseEvent(MouseEvent me) { - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)) + if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) return false; if (!HasFocus) @@ -444,9 +479,10 @@ namespace Terminal.Gui { SetNeedsDisplay (); return true; } - if (SelectedChanged != null) - SelectedChanged(); + SelectedChanged?.Invoke (); SetNeedsDisplay (); + if (me.Flags == MouseFlags.Button1DoubleClicked) + OpenSelectedItem?.Invoke (this, new EventArgs ()); return true; } } @@ -460,6 +496,10 @@ namespace Terminal.Gui { BitArray marks; int count; + /// + /// constructor + /// + /// public ListWrapper (IList source) { count = source.Count; @@ -467,6 +507,9 @@ namespace Terminal.Gui { this.src = source; } + /// + /// Count of items. + /// public int Count => src.Count; void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width) @@ -487,6 +530,16 @@ namespace Terminal.Gui { } } + /// + /// Renders an item in the the list. + /// + /// + /// + /// + /// + /// + /// + /// public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width) { container.Move (col, line); @@ -499,6 +552,11 @@ namespace Terminal.Gui { RenderUstr (driver, t.ToString (), col, line, width); } + /// + /// Returns true of the item is marked. false if not. + /// + /// + /// public bool IsMarked (int item) { if (item >= 0 && item < count) @@ -506,6 +564,11 @@ namespace Terminal.Gui { return false; } + /// + /// Sets the marked state of an item. + /// + /// + /// public void SetMark (int item, bool value) { if (item >= 0 && item < count) diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 0e40bd391..16b7c0b87 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -21,6 +21,9 @@ namespace Terminal.Gui { /// public class MenuItem { + /// + /// constructor + /// public MenuItem () { Title = ""; @@ -207,10 +210,10 @@ namespace Terminal.Gui { return len; } - /// - /// Gets or sets the title to display. - /// - /// The title. + ///// + ///// Gets or sets the title to display. + ///// + ///// The title. //public ustring Title { get; set; } /// @@ -357,7 +360,7 @@ namespace Terminal.Gui { public override bool ProcessHotKey (KeyEvent keyEvent) { // To ncurses simulate a AltMask key pressing Alt+Space because - // it can´t detect an alone special key down was pressed. + // it can�t detect an alone special key down was pressed. if (keyEvent.IsAlt && keyEvent.Key == Key.AltMask) { OnKeyDown (keyEvent); return true; @@ -455,7 +458,7 @@ namespace Terminal.Gui { } host.handled = false; bool disabled; - if (me.Flags == MouseFlags.Button1Pressed || me.Flags == MouseFlags.Button1DoubleClicked) { + if (me.Flags == MouseFlags.Button1Clicked) { disabled = false; if (me.Y < 1) return true; @@ -559,6 +562,7 @@ namespace Terminal.Gui { } bool openedByAltKey; + /// public override bool OnKeyDown (KeyEvent keyEvent) { if (keyEvent.IsAlt) { @@ -569,12 +573,7 @@ namespace Terminal.Gui { return false; } - /// - /// Track Alt key-up events. On Windows, when a user releases Alt (without another key), the menu gets focus but doesn't open. - /// We mimic that behavior here. - /// - /// - /// + /// public override bool OnKeyUp (KeyEvent keyEvent) { if (keyEvent.IsAlt) { @@ -615,6 +614,7 @@ namespace Terminal.Gui { return false; } + /// public override void Redraw (Rect region) { Move (0, 0); @@ -645,6 +645,7 @@ namespace Terminal.Gui { PositionCursor (); } + /// public override void PositionCursor () { int pos = 0; @@ -672,8 +673,16 @@ namespace Terminal.Gui { action = item.Action; } + /// + /// Raised as a menu is opened. + /// public event EventHandler OnOpenMenu; + + /// + /// Raised when a menu is closing. + /// public event EventHandler OnCloseMenu; + internal Menu openMenu; Menu openCurrentMenu; internal List openSubMenu; @@ -681,7 +690,12 @@ namespace Terminal.Gui { internal bool isMenuOpening; internal bool isMenuClosing; internal bool isMenuClosed; - public bool MenuOpen; + + /// + /// True of the menu is open; otherwise false. + /// + public bool MenuOpen { get; set; } + View lastFocused; /// @@ -940,7 +954,7 @@ namespace Terminal.Gui { bool openedByHotKey; internal bool FindAndOpenMenuByHotkey (KeyEvent kb) { - int pos = 0; + //int pos = 0; var c = ((uint)kb.Key & (uint)Key.CharMask); for (int i = 0; i < Menus.Length; i++) { // TODO: this code is duplicated, hotkey should be part of the MenuBarItem @@ -969,6 +983,7 @@ namespace Terminal.Gui { } } + /// public override bool ProcessHotKey (KeyEvent kb) { if (kb.Key == Key.F9) { @@ -980,7 +995,7 @@ namespace Terminal.Gui { } // To ncurses simulate a AltMask key pressing Alt+Space because - // it can´t detect an alone special key down was pressed. + // it can�t detect an alone special key down was pressed. if (kb.IsAlt && kb.Key == Key.AltMask && openMenu == null) { OnKeyDown (kb); OnKeyUp (kb); @@ -993,6 +1008,7 @@ namespace Terminal.Gui { return base.ProcessHotKey (kb); } + /// public override bool ProcessKey (KeyEvent kb) { switch (kb.Key) { @@ -1047,6 +1063,7 @@ namespace Terminal.Gui { return true; } + /// public override bool MouseEvent (MouseEvent me) { if (!handled && !HandleGrabView (me, this)) { diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index d9c23ea87..b10e567b2 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -189,6 +189,7 @@ namespace Terminal.Gui { } } + /// public override bool MouseEvent(MouseEvent me) { if (me.Flags != MouseFlags.Button1Pressed && me.Flags != MouseFlags.Button1Clicked && @@ -249,6 +250,10 @@ namespace Terminal.Gui { View contentView; ScrollBarView vertical, horizontal; + /// + /// Constructs a ScrollView + /// + /// public ScrollView (Rect frame) : base (frame) { contentView = new View (frame); @@ -363,7 +368,7 @@ namespace Terminal.Gui { /// /// This event is raised when the contents have scrolled /// - public event Action Scrolled; + //public event Action Scrolled; public override void Redraw(Rect region) { @@ -383,6 +388,7 @@ namespace Terminal.Gui { } } + /// public override void PositionCursor() { if (InternalSubviews.Count == 0) @@ -448,6 +454,7 @@ namespace Terminal.Gui { return true; } + /// public override bool ProcessKey(KeyEvent kb) { if (base.ProcessKey (kb)) diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index fe1573a91..1bb5f2b33 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -85,8 +85,14 @@ namespace Terminal.Gui { public StatusBarStyle Style { get; set; } = StatusBarStyle.Default; #endif + /// + /// The parent view of the StatusBar. + /// public View Parent { get; set; } + /// + /// The items that compose the StatusBar + /// public StatusItem [] Items { get; set; } /// @@ -132,6 +138,7 @@ namespace Terminal.Gui { return result; } + /// public override void Redraw (Rect region) { if (Frame.Y != Driver.Rows - 1) { @@ -161,6 +168,7 @@ namespace Terminal.Gui { } } + /// public override bool ProcessHotKey (KeyEvent kb) { foreach (var item in Items) { diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 8d239e86c..0d6f35a5c 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -266,6 +266,9 @@ namespace Terminal.Gui { bool selecting; //bool used; + /// + /// Raised when the Text of the TextView changes. + /// public event EventHandler TextChanged; #if false @@ -535,6 +538,7 @@ namespace Terminal.Gui { PositionCursor (); } + /// public override bool CanFocus { get => true; set { base.CanFocus = value; } @@ -682,6 +686,7 @@ namespace Terminal.Gui { bool lastWasKill; + /// public override bool ProcessKey (KeyEvent kb) { int restCount; @@ -1139,6 +1144,7 @@ namespace Terminal.Gui { return null; } + /// public override bool MouseEvent (MouseEvent ev) { if (!ev.Flags.HasFlag (MouseFlags.Button1Clicked)) { diff --git a/Terminal.sln b/Terminal.sln index 5bace6618..77680ffd0 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -7,6 +7,8 @@ Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gu EndProject Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Designer", "Designer\Designer.csproj", "{1228D992-C801-49BB-839A-7BD28A3FFF0A}" EndProject +Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "UICatalog", "UICatalog\UICatalog.csproj", "{88979F89-9A42-448F-AE3E-3060145F6375}" +EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution Debug|x86 = Debug|x86 @@ -25,6 +27,14 @@ Global {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Debug|x86.Build.0 = Debug|x86 {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.ActiveCfg = Release|x86 {1228D992-C801-49BB-839A-7BD28A3FFF0A}.Release|x86.Build.0 = Release|x86 + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.ActiveCfg = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|Any CPU.Build.0 = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.ActiveCfg = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Debug|x86.Build.0 = Debug|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.ActiveCfg = Release|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|Any CPU.Build.0 = Release|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.ActiveCfg = Release|Any CPU + {88979F89-9A42-448F-AE3E-3060145F6375}.Release|x86.Build.0 = Release|Any CPU EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 diff --git a/UICatalog/.editorconfig b/UICatalog/.editorconfig new file mode 100644 index 000000000..040e7abd9 --- /dev/null +++ b/UICatalog/.editorconfig @@ -0,0 +1,23 @@ +[*.cs] +indent_style = tab +indent_size = 8 +tab_width = 8 +csharp_new_line_before_open_brace = methods,local_functions +csharp_new_line_before_else = false +csharp_new_line_before_catch = false +csharp_new_line_before_finally = false +end_of_line = crlf + +csharp_indent_case_contents = true +csharp_indent_switch_labels = false +csharp_indent_labels = flush_left +csharp_space_after_keywords_in_control_flow_statements = true +csharp_space_between_method_declaration_parameter_list_parentheses = false +csharp_space_between_method_call_parameter_list_parentheses = false +csharp_preserve_single_line_blocks = true +dotnet_style_require_accessibility_modifiers = never +csharp_style_var_when_type_is_apparent = true +csharp_prefer_braces = false +csharp_space_before_open_square_brackets = true +csharp_space_between_method_call_name_and_opening_parenthesis = true +csharp_space_between_method_declaration_name_and_open_parenthesis = true \ No newline at end of file diff --git a/UICatalog/.gitignore b/UICatalog/.gitignore new file mode 100644 index 000000000..4378419e7 --- /dev/null +++ b/UICatalog/.gitignore @@ -0,0 +1,9 @@ +############### +# folder # +############### +/**/DROP/ +/**/TEMP/ +/**/packages/ +/**/bin/ +/**/obj/ +_site diff --git a/UICatalog/Program.cs b/UICatalog/Program.cs new file mode 100644 index 000000000..f81df07df --- /dev/null +++ b/UICatalog/Program.cs @@ -0,0 +1,271 @@ +using NStack; +using System; +using System.Collections.Generic; +using System.Diagnostics; +using System.Globalization; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + /// + /// Main program for the Terminal.gui UI Catalog app. This app provides a chooser that allows + /// for a calalog of UI demos, examples, and tests. + /// + class Program { + private static Toplevel _top; + private static MenuBar _menu; + private static int _nameColumnWidth; + private static Window _leftPane; + private static List _categories; + private static ListView _categoryListView; + private static Window _rightPane; + private static List _scenarios; + private static ListView _scenarioListView; + private static StatusBar _statusBar; + + private static Scenario _runningScenario = null; + + static void Main (string [] args) + { + if (Debugger.IsAttached) + CultureInfo.DefaultThreadCurrentUICulture = CultureInfo.GetCultureInfo ("en-US"); + + _scenarios = Scenario.GetDerivedClassesCollection ().ToList (); + + if (args.Length > 0) { + var item = _scenarios.FindIndex (t => Scenario.ScenarioMetadata.GetName (t).Equals (args [0], StringComparison.OrdinalIgnoreCase)); + _runningScenario = (Scenario)Activator.CreateInstance (_scenarios [item]); + Application.Init (); + _runningScenario.Init (Application.Top); + _runningScenario.Setup (); + _runningScenario.Run (); + _runningScenario = null; + return; + } + + Scenario scenario = GetScenarioToRun (); + while (scenario != null) { + Application.Init (); + scenario.Init (Application.Top); + scenario.Setup (); + scenario.Run (); + scenario = GetScenarioToRun (); + } + } + + /// + /// Create all controls. This gets called once and the controls remain with their state between Sceanrio runs. + /// + private static void Setup () + { + _menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_File", new MenuItem [] { + new MenuItem ("_Quit", "", () => Application.RequestStop() ) + }), + new MenuBarItem ("_About...", "About this app", () => MessageBox.Query (0, 6, "About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok")), + }); + + _leftPane = new Window ("Categories") { + X = 0, + Y = 1, // for menu + Width = 25, + Height = Dim.Fill (), + CanFocus = false, + }; + + + _categories = Scenario.GetAllCategories (); + _categoryListView = new ListView (_categories) { + X = 1, + Y = 0, + Width = Dim.Fill (0), + Height = Dim.Fill (2), + AllowsMarking = false, + CanFocus = true, + }; + _categoryListView.OpenSelectedItem += (o, a) => { + _top.SetFocus (_rightPane); + }; + _categoryListView.SelectedChanged += CategoryListView_SelectedChanged; + _leftPane.Add (_categoryListView); + + _rightPane = new Window ("Scenarios") { + X = 25, + Y = 1, // for menu + Width = Dim.Fill (), + Height = Dim.Fill (), + CanFocus = false, + + }; + + _nameColumnWidth = Scenario.ScenarioMetadata.GetName (_scenarios.OrderByDescending (t => Scenario.ScenarioMetadata.GetName (t).Length).FirstOrDefault ()).Length; + + _scenarioListView = new ListView () { + X = 0, + Y = 0, + Width = Dim.Fill (0), + Height = Dim.Fill (0), + AllowsMarking = false, + CanFocus = true, + }; + + //_scenarioListView.OnKeyPress += (KeyEvent ke) => { + // if (_top.MostFocused == _scenarioListView && ke.Key == Key.Enter) { + // _scenarioListView_OpenSelectedItem (null, null); + // } + //}; + + _scenarioListView.OpenSelectedItem += _scenarioListView_OpenSelectedItem; + _rightPane.Add (_scenarioListView); + + _categoryListView.SelectedItem = 0; + CategoryListView_SelectedChanged (); + + _statusBar = new StatusBar (new StatusItem [] { + //new StatusItem(Key.F1, "~F1~ Help", () => Help()), + new StatusItem(Key.ControlQ, "~CTRL-Q~ Quit", () => { + if (_runningScenario is null){ + // This causes GetScenarioToRun to return null + _runningScenario = null; + Application.RequestStop(); + } else { + _runningScenario.RequestStop(); + } + }), + }); + } + + /// + /// This shows the selection UI. Each time it is run, it calls Application.Init to reset everything. + /// + /// + private static Scenario GetScenarioToRun () + { + Application.Init (); + + if (_menu == null) { + Setup (); + } + + _top = Application.Top; + _top.KeyUp += KeyUpHandler; + _top.Add (_menu); + _top.Add (_leftPane); + _top.Add (_rightPane); + _top.Add (_statusBar); + + // HACK: There is no other way to SetFocus before Application.Run. See Issue #445 +#if false + if (_runningScenario != null) + Application.Iteration += Application_Iteration; +#else + _top.Ready += (o, a) => { + if (_runningScenario != null) { + _top.SetFocus (_rightPane); + _runningScenario = null; + } + }; +#endif + + Application.Run (_top); + return _runningScenario; + } + +#if false + private static void Application_Iteration (object sender, EventArgs e) + { + Application.Iteration -= Application_Iteration; + _top.SetFocus (_rightPane); + } +#endif + private static void _scenarioListView_OpenSelectedItem (object sender, EventArgs e) + { + if (_runningScenario is null) { + var source = _scenarioListView.Source as ScenarioListDataSource; + _runningScenario = (Scenario)Activator.CreateInstance (source.Scenarios [_scenarioListView.SelectedItem]); + Application.RequestStop (); + } + } + + internal class ScenarioListDataSource : IListDataSource { + public List Scenarios { get; set; } + + public bool IsMarked (int item) => false;// Scenarios [item].IsMarked; + + public int Count => Scenarios.Count; + + public ScenarioListDataSource (List itemList) => Scenarios = itemList; + + public void Render (ListView container, ConsoleDriver driver, bool selected, int item, int col, int line, int width) + { + container.Move (col, line); + // Equivalent to an interpolated string like $"{Scenarios[item].Name, -widtestname}"; if such a thing were possible + var s = String.Format (String.Format ("{{0,{0}}}", -_nameColumnWidth), Scenario.ScenarioMetadata.GetName (Scenarios [item])); + RenderUstr (driver, $"{s} {Scenario.ScenarioMetadata.GetDescription (Scenarios [item])}", col, line, width); + } + + public void SetMark (int item, bool value) + { + } + + // A slightly adapted method from: https://github.com/migueldeicaza/gui.cs/blob/fc1faba7452ccbdf49028ac49f0c9f0f42bbae91/Terminal.Gui/Views/ListView.cs#L433-L461 + private void RenderUstr (ConsoleDriver driver, ustring ustr, int col, int line, int width) + { + int used = 0; + int index = 0; + while (index < ustr.Length) { + (var rune, var size) = Utf8.DecodeRune (ustr, index, index - ustr.Length); + var count = Rune.ColumnWidth (rune); + if (used + count >= width) break; + driver.AddRune (rune); + used += count; + index += size; + } + + while (used < width) { + driver.AddRune (' '); + used++; + } + } + } + + /// + /// When Scenarios are running we need to override the behavior of the Menu + /// and Statusbar to enable Scenarios that use those (or related key input) + /// to not be impacted. Same as for tabs. + /// + /// + private static void KeyUpHandler (object sender, View.KeyEventEventArgs a) + { + if (_runningScenario != null) { + //switch (ke.Key) { + //case Key.Esc: + // //_runningScenario.RequestStop (); + // break; + //case Key.Enter: + // break; + //} + } else if (a.KeyEvent.Key == Key.Tab || a.KeyEvent.Key == Key.BackTab) { + // BUGBUG: Work around Issue #434 by implementing our own TAB navigation + if (_top.MostFocused == _categoryListView) + _top.SetFocus (_rightPane); + else + _top.SetFocus (_leftPane); + } + } + + private static void CategoryListView_SelectedChanged () + { + var item = _categories [_categoryListView.SelectedItem]; + List newlist; + if (item.Equals ("All")) { + newlist = _scenarios; + + } else { + newlist = _scenarios.Where (t => Scenario.ScenarioCategory.GetCategories (t).Contains (item)).ToList (); + } + _scenarioListView.Source = new ScenarioListDataSource (newlist); + _scenarioListView.SelectedItem = 0; + } + } +} diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json new file mode 100644 index 000000000..68c82f53f --- /dev/null +++ b/UICatalog/Properties/launchSettings.json @@ -0,0 +1,7 @@ +{ + "profiles": { + "UICatalog": { + "commandName": "Project" + } + } +} \ No newline at end of file diff --git a/UICatalog/README.md b/UICatalog/README.md new file mode 100644 index 000000000..6f6b019f8 --- /dev/null +++ b/UICatalog/README.md @@ -0,0 +1,122 @@ +# Terminal.Gui UI Catalog + +UI Catalog is a comprehensive sample library for Terminal.Gui. It attempts to satisfy the following goals: + +1. Be an easy to use showcase for Terminal.Gui concepts and features. +2. Provide sample code that illustrates how to properly implement said concepts & features. +3. Make it easy for contributors to add additional samples in a structured way. + +![screenshot](screenshot.png) + +## Motivation + +The original `demo.cs` sample app for Terminal.Gui is neither good to showcase, nor does it explain different concepts. In addition, because it is built on a single source file, it has proven to cause friction when multiple contributors are simultaneously working on different aspects of Terminal.Gui. See [Issue #368](https://github.com/migueldeicaza/Terminal.Gui/issues/368) for more background. + +## How To Use + +`Program.cs` is the main app and provides a UI for selecting and running **Scenarios**. Each **Scenario* is implemented as a class derived from `Scenario` and `Program.cs` uses reflection to dynamically build the UI. + +**Scenarios** are tagged with categories using the `[ScenarioCategory]` attribute. The left pane of the main screen lists the categories. Clicking on a category shows all the scenarios in that category. + +**Scenarios** can be run either from the **UICatalog.exe** app UI or by being specified on the command line: + +``` +UICatalog.exe +``` + +e.g. + +``` +UICatalog.exe Buttons +``` + +When a **Scenario** is run, it runs as though it were a standalone `Terminal.Gui` app. However, scaffolding is provided (in the `Scenario` base class) that (optionally) takes care of `Terminal.Gui` initialization. + +## Contributing by Adding Scenarios + +To add a new **Scenario** simply: + +1. Create a new `.cs` file in the `Scenarios` directory that derives from `Scenario`. +2. Add a `[ScenarioMetaData]` attribute to the class specifying the scenario's name and description. +3. Add one or more `[ScenarioCategory]` attributes to the class specifying which categories the sceanrio belongs to. If you don't specify a category the sceanrio will show up in "All". +4. Implement the `Setup` override which will be called when a user selects the scenario to run. +5. Optionally, implement the `Init` and/or `Run` overrides to provide a custom implementation. + +The sample below is provided in the `Scenarios` directory as a generic sample that can be copied and re-named: + +```csharp +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Generic", Description: "Generic sample - A template for creating new Scenarios")] + [ScenarioCategory ("Controls")] + class MyScenario : Scenario { + public override void Setup () + { + // Put your scenario code here, e.g. + Win.Add (new Button ("Press me!") { + X = Pos.Center (), + Y = Pos.Center (), + Clicked = () => MessageBox.Query (20, 7, "Hi", "Neat?", "Yes", "No") + }); + } + } +} +``` + +`Scenario` provides a `Toplevel` and `Window` the provides a canvas for the Scenario to operate. The default `Window` shows the Scenario name and supports exiting the Scenario through the `Esc` key. + +![screenshot](generic_screenshot.png) + +To build a more advanced scenario, where control of the `Toplevel` and `Window` is needed (e.g. for scenarios using `MenuBar` or `StatusBar`), simply set the `Top` and `Window` properties as appropriate, as seen in the `UnicodeInMenu` scenario: + +```csharp +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Unicode In Menu", Description: "Unicode menus per PR #204")] + [ScenarioCategory ("Text")] + [ScenarioCategory ("Controls")] + class UnicodeInMenu : Scenario { + public override void Setup () + { + Top = new Toplevel (new Rect (0, 0, Application.Driver.Cols, Application.Driver.Rows)); + var menu = new MenuBar (new MenuBarItem [] { + new MenuBarItem ("_Файл", new MenuItem [] { + new MenuItem ("_Создать", "Creates new file", null), + new MenuItem ("_Открыть", "", null), + new MenuItem ("Со_хранить", "", null), + new MenuItem ("_Выход", "", () => Application.RequestStop() ) + }), + new MenuBarItem ("_Edit", new MenuItem [] { + new MenuItem ("_Copy", "", null), + new MenuItem ("C_ut", "", null), + new MenuItem ("_Paste", "", null) + }) + }); + Top.Add (menu); + + Win = new Window ($"Scenario: {GetName ()}") { + X = 0, + Y = 1, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + Top.Add (Win); + } + } +} +``` + +For complete control, the `Init` and `Run` overrides can be implemented. The `base.Init` assigns `Application.Top` to `Top` and creates `Win`. The `base.Run` simply calls `Application.Run(Top)`. + +## Contribution Guidelines + +- Provide a terse, descriptive name for `Scenarios`. Keep them short; the `ListView` that displays them dynamically sizes the column width and long names will make it hard for people to use. +- Provide a clear description. +- Comment `Scenario` code to describe to others why it's a useful `Scenario`. +- Annotate `Scenarios` with `[ScenarioCategory]` attributes. Try to minimize the number of new categories created. +- Use the `Bug Rero` Category for `Scnarios` that reproduce bugs. + - Include the Github Issue # in the Description. + - Once the bug has been fixed in `master` submit another PR to remove the `Scenario` (or modify it to provide a good regression test). +- Tag bugs or suggestions for `UI Catalog` in the main `Terminal.Gui` Github Issues with "UICatalog: ". \ No newline at end of file diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs new file mode 100644 index 000000000..e491a4c8e --- /dev/null +++ b/UICatalog/Scenario.cs @@ -0,0 +1,181 @@ +using NStack; +using System; +using System.Collections.Generic; +using System.Linq; +using Terminal.Gui; + +namespace UICatalog { + /// + /// Base class for each demo/scenario. To define a new sceanrio simply + /// + /// 1) declare a class derived from Scenario, + /// 2) Set Name and Description as appropriate using [ScenarioMetadata] attribute + /// 3) Set one or more categories with the [ScenarioCategory] attribute + /// 4) Implement Setup. + /// 5) Optionally, implement Run. + /// + /// The Main program uses reflection to find all sceanarios and adds them to the + /// ListViews. Press ENTER to run the selected sceanrio. Press CTRL-Q to exit it. + /// + public class Scenario { + /// + /// The Top level for the Scenario. This should be set to `Application.Top` in most cases. + /// + public Toplevel Top { get; set; } + + /// + /// + public Window Win { get; set; } + + /// + /// Helper that provides the default Window implementation with a frame and + /// label showing the name of the Scenario and logic to exit back to + /// the Scenario picker UI. + /// Override Init to provide any `Toplevel` behavior needed. + /// + /// + public virtual void Init(Toplevel top) + { + Top = top; + Win = new Window ($"CTRL-Q to Close - Scenario: {GetName ()}") { + X = 0, + Y = 0, + Width = Dim.Fill (), + Height = Dim.Fill () + }; + Top.Add (Win); + } + + [System.AttributeUsage (System.AttributeTargets.Class)] + public class ScenarioMetadata : System.Attribute { + /// + /// Scenario Name + /// + public string Name { get; set; } + + /// + /// Scenario Description + /// + public string Description { get; set; } + + public ScenarioMetadata (string Name, string Description) + { + this.Name = Name; + this.Description = Description; + } + + /// + /// Static helper function to get the Scenario Name given a Type + /// + /// + /// + public static string GetName (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Name; + + /// + /// Static helper function to get the Scenario Description given a Type + /// + /// + /// + public static string GetDescription (Type t) => ((ScenarioMetadata)System.Attribute.GetCustomAttributes (t) [0]).Description; + } + + /// + /// Helper to get the Scenario Name + /// + /// + public string GetName () => ScenarioMetadata.GetName (this.GetType ()); + + /// + /// Helper to get the Scenario Descripiton + /// + /// + public string GetDescription () => ScenarioMetadata.GetDescription (this.GetType ()); + + [System.AttributeUsage (System.AttributeTargets.Class, AllowMultiple = true)] + public class ScenarioCategory : System.Attribute { + /// + /// Category Name + /// + public string Name { get; set; } + + public ScenarioCategory (string Name) => this.Name = Name; + + /// + /// Static helper function to get the Scenario Name given a Type + /// + /// + /// + public static string GetName (Type t) => ((ScenarioCategory)System.Attribute.GetCustomAttributes (t) [0]).Name; + + /// + /// Static helper function to get the Scenario Categories given a Type + /// + /// + /// + public static List GetCategories (Type t) => System.Attribute.GetCustomAttributes (t) + .ToList () + .Where (a => a is ScenarioCategory) + .Select (a => ((ScenarioCategory)a).Name) + .ToList (); + } + + /// + /// Helper function to get the Categories of a Scenario + /// + /// + public List GetCategories () => ScenarioCategory.GetCategories (this.GetType ()); + + public override string ToString () => $"{GetName (),-30}{GetDescription ()}"; + + /// + /// Override this to implement the Scenario setup logic (create controls, etc...). + /// + public virtual void Setup () + { + } + + /// + /// Runs the scenario. Override to start the scearnio using a Top level different than `Top`. + /// + public virtual void Run () + { + Application.Run (Top); + } + + /// + /// Stops the scenario. Override to implement shutdown behavior for the Scenario. + /// + public virtual void RequestStop () + { + Application.RequestStop (); + } + + /// + /// Returns a list of all Categories set by all of the scenarios defined in the project. + /// + internal static List GetAllCategories () + { + List categories = new List () { "All" }; + foreach (Type type in typeof (Scenario).Assembly.GetTypes () + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { + List attrs = System.Attribute.GetCustomAttributes (type).ToList (); + categories = categories.Union (attrs.Where (a => a is ScenarioCategory).Select (a => ((ScenarioCategory)a).Name)).ToList (); + } + return categories; + } + + /// + /// Returns an instance of each Scenario defined in the project. + /// https://stackoverflow.com/questions/5411694/get-all-inherited-classes-of-an-abstract-class + /// + internal static List GetDerivedClassesCollection () + { + List objects = new List (); + foreach (Type type in typeof (Scenario).Assembly.GetTypes () + .Where (myType => myType.IsClass && !myType.IsAbstract && myType.IsSubclassOf (typeof (Scenario)))) { + objects.Add (type); + } + return objects; + } + } +} diff --git a/UICatalog/Scenarios/Buttons.cs b/UICatalog/Scenarios/Buttons.cs new file mode 100644 index 000000000..64b92bfce --- /dev/null +++ b/UICatalog/Scenarios/Buttons.cs @@ -0,0 +1,105 @@ +using Terminal.Gui; + +namespace UICatalog { + [ScenarioMetadata (Name: "Buttons", Description: "Demonstrates all sorts of Buttons")] + [ScenarioCategory ("Controls")] + [ScenarioCategory ("Layout")] + class Buttons : Scenario { + public override void Setup () + { + // Add a label & text field so we can demo IsDefault + var editLabel = new Label ("TextField (to demo IsDefault):") { + X = 0, + Y = 0, + }; + Win.Add (editLabel); + var edit = new TextField ("") { + X = Pos.Right (editLabel) + 1, + Y = Pos.Top (editLabel), + Width = Dim.Fill (2), + }; + Win.Add (edit); + + // This is the default button (IsDefault = true); if user presses ENTER in the TextField + // the scenario will quit + var defaultButton = new Button ("Quit") { + X = Pos.Center (), + // BUGBUG: Throws an exception + //Y= Pos.Bottom(Win), + Y = 20, + IsDefault = true, + Clicked = () => Application.RequestStop (), + }; + Win.Add (defaultButton); + + var y = 2; + var button = new Button (10, y, "Base Color") { + ColorScheme = Colors.Base, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }; + Win.Add (button); + + y += 2; + Win.Add (new Button (10, y, "Error Color") { + ColorScheme = Colors.Error, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "Dialog Color") { + ColorScheme = Colors.Dialog, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "Menu Color") { + ColorScheme = Colors.Menu, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "TopLevel Color") { + ColorScheme = Colors.TopLevel, + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + Win.Add (new Button (10, y, "A super long button that will probably expose a bug in clipping or wrapping of text. Will it?") { + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + // Note the 'N' in 'Newline' will be the hotkey + Win.Add (new Button (10, y, "a Newline\nin the button") { + Clicked = () => MessageBox.Query (30, 7, "Message", "Question?", "Yes", "No") + }); + + y += 2; + // BUGBUG: Buttons don't support specifying hotkeys with _?!? + Win.Add (button = new Button (10, y, "Te_xt Changer") { + }); + button.Clicked = () => button.Text += $"{y++}"; + + Win.Add (new Button ("Lets see if this will move as \"Text Changer\" grows") { + X = Pos.Right(button) + 10, + Y = y, + }); + + y += 2; + Win.Add (new Button (10, y, "Delete") { + ColorScheme = Colors.Error, + Clicked = () => Win.Remove (button) + }); + + y += 2; + Win.Add (new Button (10, y, "Change Default") { + Clicked = () => { + defaultButton.IsDefault = !defaultButton.IsDefault; + button.IsDefault = !button.IsDefault; + }, + }); + + + } + } +} diff --git a/UICatalog/Scenarios/DimAndPosLayout.cs b/UICatalog/Scenarios/DimAndPosLayout.cs new file mode 100644 index 000000000..f1f543a57 --- /dev/null +++ b/UICatalog/Scenarios/DimAndPosLayout.cs @@ -0,0 +1,82 @@ +using System; +using System.Collections.Generic; +using System.Linq; +using System.Text; +using Terminal.Gui; + +namespace UICatalog { + /// + /// This Scenario demonstrates how to use Termina.gui's Dim and Pos Layout System. + /// [x] - Using Dim.Fill to fill a window + /// [x] - Using Dim.Fill and Dim.Pos to automatically align controls based on an initial control + /// [ ] - ... + /// + [ScenarioMetadata (Name: "DimAndPosLayout", Description: "Demonstrates using the Dim and Pos Layout System")] + [ScenarioCategory ("Layout")] + class DimAndPosLayout : Scenario { + + public override void Setup () + { + Top.LayoutStyle = LayoutStyle.Computed; + // Demonstrate using Dim to create a ruler that always measures the top-level window's width + // BUGBUG: Dim.Fill returns too big a value sometimes. + //const string rule = "|123456789"; + //var labelRuler = new Label ("ruler") { + // X = 0, + // Y = 0, + // Width = Dim.Fill (1), // BUGBUG: I don't think this should be needed; DimFill() should respect container's frame. X does. + // ColorScheme = Colors.Error + //}; + + //Application.OnResized += () => { + // labelRuler.Text = rule.Repeat ((int)Math.Ceiling((double)(labelRuler.Bounds.Width) / (double)rule.Length))[0..(labelRuler.Bounds.Width)]; + //}; + + //win.Add (labelRuler); + + // Demonstrate using Dim to create a window that fills the parent with a margin + int margin = 20; + var subWin = new Window ($"Sub Windoww with {margin} character margin") { + X = margin, + Y = 2, + Width = Dim.Fill (margin), + Height = Dim.Fill () + }; + Win.Add (subWin); + + int i = 1; + string txt = "Hello world, how are you doing today"; + var labelList = new List