diff --git a/.github/workflows/dotnetcore.yml b/.github/workflows/dotnetcore.yml deleted file mode 100644 index fe44e5fb5..000000000 --- a/.github/workflows/dotnetcore.yml +++ /dev/null @@ -1,26 +0,0 @@ -name: .NET Core - -on: - push: - branches: [ master ] - pull_request: - branches: [ master ] - -jobs: - build: - - runs-on: ubuntu-latest - -# Commented out until we resolve how to build the project with just netstandard2.1 -# 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/Example/demo.cs b/Example/demo.cs index 98dd80af7..53974b3c0 100644 --- a/Example/demo.cs +++ b/Example/demo.cs @@ -92,8 +92,8 @@ static class Demo { Width = Dim.Fill (), Height = Dim.Fill () }; - container.OnKeyUp += (KeyEvent ke) => { - if (ke.Key == Key.Esc) + container.KeyUp += (sender, e) => { + if (e.KeyEvent.Key == Key.Esc) container.Running = false; }; @@ -436,11 +436,11 @@ static class Demo { #endregion - #region OnKeyDown / OnKeyPress / OnKeyUp Demo + #region KeyDown / KeyPress / KeyUp Demo private static void OnKeyDownPressUpDemo () { var container = new Dialog ( - "OnKeyDown & OnKeyPress & OnKeyUp demo", 80, 20, + "KeyDown & KeyPress & KeyUp demo", 80, 20, new Button ("Close") { Clicked = () => { Application.RequestStop (); } }) { Width = Dim.Fill (), Height = Dim.Fill (), @@ -492,9 +492,9 @@ static class Demo { } - container.OnKeyDown += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Down"); - container.OnKeyPress += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Press"); - container.OnKeyUp += (KeyEvent keyEvent) => KeyDownPressUp (keyEvent, "Up"); + container.KeyDown += (o, e) => KeyDownPressUp (e.KeyEvent, "Down"); + container.KeyPress += (o, e) => KeyDownPressUp (e.KeyEvent, "Press"); + container.KeyUp += (o, e) => KeyDownPressUp (e.KeyEvent, "Up"); Application.Run (container); } #endregion @@ -628,7 +628,7 @@ static class Demo { var bottom2 = new Label ("This should go on the bottom of another top-level!"); top.Add (bottom2); - Application.OnLoad = () => { + Application.Loaded += (sender, e) => { bottom.X = win.X; bottom.Y = Pos.Bottom (win) - Pos.Top (win) - margin; bottom2.X = Pos.Left (win); diff --git a/README.md b/README.md index 303368499..2b42b3659 100644 --- a/README.md +++ b/README.md @@ -2,59 +2,84 @@ [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui) [![Downloads](https://img.shields.io/nuget/dt/Terminal.Gui)](https://www.nuget.org/packages/Terminal.Gui) [![License](https://img.shields.io/github/license/migueldeicaza/gui.cs.svg)](LICENSE) +[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mono/mono?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - The Mono Channel room -[![Gitter](https://badges.gitter.im/Join%20Chat.svg)](https://gitter.im/mono/mono?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge) - This is the Mono Channel room +# Terminal.Gui - Terminal UI toolkit for .NET -# Gui.cs - Terminal UI toolkit for .NET +A simple UI toolkit for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix. -This is a simple UI toolkit for .NET, .NET Core and Mono and works on -both Windows and Linux/Unix. +![Sample app](https://raw.githubusercontent.com/migueldeicaza/gui.cs/master/docfx/sample.gif) -![Sample app](https://raw.githubusercontent.com/migueldeicaza/gui.cs/master/docfx/sample.png) +## Controls & Features -A presentation of this was part of the [Retro.NET](https://channel9.msdn.com/Events/dotnetConf/2018/S313) talk at .NET Conf 2018 [Slides](https://tirania.org/Retro.pdf) - -The toolkit contains various controls for building text user interfaces: +The *Terminal.Gui* toolkit contains various controls for building text user interfaces: * [Buttons](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Button.html) -* [Labels](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Label.html) -* [Text entry](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TextField.html) -* [Text view](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TextView.html) -* [Time editing field](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TimeField.html) -* [Radio buttons](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.RadioGroup.html) * [Checkboxes](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.CheckBox.html) * [Dialog boxes](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Dialog.html) - * [Message boxes](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.MessageBox.html) -* [Windows](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Window.html) -* [Menus](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.MenuBar.html) -* [ListViews](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ListView.html) * [Frames](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.FrameView.html) +* [Hex viewer/editor](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.HexView.html) +* [Labels](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Label.html) +* [ListViews](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ListView.html) +* [Menus](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.MenuBar.html) +* [Message boxes](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.MessageBox.html) * [ProgressBars](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ProgressBar.html) -* [Scroll views](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ScrollView.html) and [Scrollbars](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ScrollBarView.html) -* Hexadecimal viewer/editor (HexView) -* Terminal Emulator - a complete Xterm/Vt100 terminal emulator that you can embed is now part of [XtermSharp](https://github.com/migueldeicaza/XtermSharp/blob/master/GuiCsHost/TerminalView.cs) - you just need to pull the `TerminalView` linked here into your project. +* [Time editing field](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TimeField.html) +* [Text entry](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TextField.html) +* [Text view](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.TextView.html) +* [Scroll views](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ScrollView.html) +* [Scrollbars](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.ScrollBarView.html) +* [Status bars]() +* [Radio buttons](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.RadioGroup.html) +* [Windows](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Window.html) -All visible UI elements are subclasses of the -[View](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.View.html), -and these in turn can contain an arbitrary number of subviews. +In addition, a complete Xterm/Vt100 terminal emulator that you can embed is now part of [XtermSharp](https://github.com/migueldeicaza/XtermSharp/blob/master/GuiCsHost/TerminalView.cs) - you just need to pull the `TerminalView` linked here into your project. -It comes with a -[mainloop](https://migueldeicaza.github.io/gui.cs/api/Mono.Terminal/Mono.Terminal.MainLoop.html) -to process events, process idle handlers, timers and monitoring file +### Features + +* **Cross Platform** - Terminal drivers for Curses, [Windows Console](https://github.com/migueldeicaza/gui.cs/issues/27), and the .NET Console mean **Terminal.Gui** works well on both color and monochrome terminals and has mouse support on terminal emulators that support it. +* **Keyboard and Mouse Input** - Both keyboard and mouse input are supported, including limited support for drag & drop. +* **[Flexible Layout](https://migueldeicaza.github.io/gui.cs/articles/overview.html#layout)** - **Terminal.Gui** supports both *Absolute layout* and an innovative UI layout system referred to as *Computed Layout*. *Computed Layout* makes it easy to layout controls relative to each other and enables dynamic console GUIs. +* **Clipboard support** - Cut, Copy, and Paste of text provided through the [`Clipboard`](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.Clipboard.html) class. +* **[Arbitrary Views](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui/Terminal.Gui.View.html)** - All visible UI elements are subclasses of the `View` class, and these in turn can contain an arbitrary number of sub-views. +* **Advanced App Features** - The [Mainloop](https://migueldeicaza.github.io/gui.cs/api/Mono.Terminal/Mono.Terminal.MainLoop.html) supports processing events, idle handlers, timers, and monitoring file descriptors. -It is designed to work on Curses and the [Windows Console](https://github.com/migueldeicaza/gui.cs/issues/27), -works well on both color and monochrome terminals and has mouse support on -terminal emulators that support it. +### Keyboard Input Handling -# Documentation +The input handling of **Terminal.Gui** is similar in some ways to Emacs and the Midnight Commander, so you can expect some of the special key combinations to be active. -* [API documentation](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui.html) for details. +The key `ESC` can act as an Alt modifier (or Meta in Emacs parlance), to allow input on terminals that do not have an alt key. So to produce the sequence `Alt-F`, you can press either `Alt-F`, or `ESC` followed by the key `F`. -* [Overview](https://migueldeicaza.github.io/gui.cs/articles/overview.html) contains the conceptual - documentation and a walkthrough of the core concepts of `gui.cs` +To enter the key `ESC`, you can either press `ESC` and wait 100 milliseconds, or you can press `ESC` twice. -# Sample Usage +`ESC-0`, and `ESC-1` through `ESC-9` have a special meaning, they map to `F10`, and `F1` to `F9` respectively. + +**Terminal.Gui** respects common Mac and Windows keyboard idoms as well. For example, clipboard operations use the familiar `Control/Command-C, X, V` model. + +`CTRL-Q` is used for exiting views (and apps). + +### Driver model + +Currently **Terminal.Gui** has support for [ncurses](https://github.com/migueldeicaza/gui.cs/blob/master/Terminal.Gui/Drivers/CursesDriver.cs), [`System.Console`](https://github.com/migueldeicaza/gui.cs/blob/master/Terminal.Gui/Drivers/NetDriver.cs), and a full [Win32 Console](https://github.com/migueldeicaza/gui.cs/blob/master/Terminal.Gui/Drivers/WindowsDriver.cs) front-end. + +`ncurses` is used on Mac/Linux/Unix with color support based on what your library is compiled with; the Windows driver supports full color and mouse, and an easy-to-debug `System.Console` can be used on Windows and Unix, but lacks mouse support. + +You can force the use of `System.Console` on Unix as well; see `Core.cs`. + +## Showcase & Examples + +* **UI Catalog** - The [UI Catalog project](https://github.com/migueldeicaza/gui.cs/tree/master/UICatalog) provides an easy to use and extend sample illustrating the capabilities of **Terminal.Gui**. Run `dotnet run` in the `UICatalog` directory to run the UI Catalog. +* **Example (aka `demo.cs`)** - Run `dotnet run` in the `Example` directory to run the simple demo. +* **Standalone Example** - A trivial .NET core sample application can be found in the `StandaloneExample` directory. Run `dotnet run` in directory to test. + +## Documentation + +* [API documentation](https://migueldeicaza.github.io/gui.cs/api/Terminal.Gui.html) + +* [Overview](https://migueldeicaza.github.io/gui.cs/articles/overview.html) contains the conceptual documentation and a walkthrough of the core concepts of **Terminal.Gui**. + +### Sample Usage ```csharp using Terminal.Gui; @@ -125,8 +150,7 @@ class Demo { } ``` -Alternatively, you can encapsulate the app behavior in a new `Window`-derived class, -say `App.cs` containing the code above, and simplify your `Main` method to: +Alternatively, you can encapsulate the app behavior in a new `Window`-derived class, say `App.cs` containing the code above, and simplify your `Main` method to: ```csharp using Terminal.Gui; @@ -139,78 +163,26 @@ class Demo { } ``` -The example above shows how to add views, two styles are used, a very -nice layout system that I have no name for, but that [is -documented](https://migueldeicaza.github.io/gui.cs/articles/overview.html#layout), -and the absolute positioning. +The example above shows how to add views using both styles of layout supported by **Terminal.Gui**: **Absolute layout** and **[Computed layout](https://migueldeicaza.github.io/gui.cs/articles/overview.html#layout)**. -# Installing it +## Installing -If you want to try Gui.cs, use NuGet to install the `Terminal.Gui` NuGet package: +Use NuGet to install the `Terminal.Gui` NuGet package: https://www.nuget.org/packages/Terminal.Gui -https://www.nuget.org/packages/Terminal.Gui +## Running and Building -# Running and Building +* *`Terminal.Gui`* - Build and run using the .NET SDK command line tools (`doetnet build` in the root directory) or with Visual Studio 2019. -You can find a trivial .NET core sample application in the -"StandaloneExample" directory. You can execute it by running -`dotnet run` in that directory. +## Contributing -That sample relies on the distributed NuGet package, if you want to -to use the code on GitHub, you can open the Example program which -references the library built out of this tree. +See [Issues](https://github.com/migueldeicaza/gui.cs/issues) for a list of open bugs and enhancements. -# Input Handling +## History -The input handling of gui.cs is similar in some ways to Emacs and the -Midnight Commander, so you can expect some of the special key -combinations to be active. +This is an updated version of [gui.cs](http://tirania.org/blog/archive/2007/Apr-16.html) that Miguel wrote for [mono-curses](https://github.com/mono/mono-curses) in 2007. -The key `ESC` can act as an Alt modifier (or Meta in Emacs parlance), to -allow input on terminals that do not have an alt key. So to produce -the sequence `Alt-F`, you can press either `Alt-F`, or `ESC` followed by the key `F`. +The original **gui.cs** was a UI toolkit in a single file and tied to curses. This version tries to be console-agnostic and instead of having a container/widget model, only uses Views (which can contain subviews) and changes the rendering model to rely on damage regions instead of burdening each view with the details. -To enter the key `ESC`, you can either press `ESC` and wait 100 -milliseconds, or you can press `ESC` twice. +A presentation of this was part of the [Retro.NET](https://channel9.msdn.com/Events/dotnetConf/2018/S313) talk at .NET Conf 2018 [Slides](https://tirania.org/Retro.pdf) -`ESC-0`, and `ESC-1` through `ESC-9` have a special meaning, they map to -`F10`, and `F1` to `F9` respectively. - -# Driver model - -Currently gui.cs has support for ncurses, `System.Console` and a full -Win32 console front-end. - -ncurses is used on Unix with color support based on what your library -is compiled with; The windows driver supports full color and mouse, and -an easy-to-debug `System.Console` can be used on Windows and Unix, but -lacks mouse support. - -You can force the use of `System.Console` on Unix as -well, see `Core.cs`. - -# Tasks - -There are some tasks in the github issues, and some others are being -tracked in the TODO.md file. - -# History - -This is an updated version of -[gui.cs](http://tirania.org/blog/archive/2007/Apr-16.html) that -I wrote for [mono-curses](https://github.com/mono/mono-curses) in 2007. - -The original gui.cs was a UI toolkit in a single file and tied to -curses. This version tries to be console-agnostic and instead of -having a container/widget model, only uses Views (which can contain -subviews) and changes the rendering model to rely on damage regions -instead of burderning each view with the details. - -# Releases - -Recently, I setup VSTS to do the releases, for now, this requires a -branch to be pushed with the name release/XXX, do this after the NuGet -package version has been updated on the -Terminal.Gui/Terminal.Gui.csproj, and push. - -Then once the package is built, VSTS will request an approval. +Release history can be found in the [Terminal.Gui.csproj](https://github.com/migueldeicaza/gui.cs/blob/master/Terminal.Gui/Terminal.Gui.csproj) file. diff --git a/StandaloneExample/README.md b/StandaloneExample/README.md index c959ff9f8..d04ad1153 100644 --- a/StandaloneExample/README.md +++ b/StandaloneExample/README.md @@ -1,5 +1,4 @@ -This is just a simple standalone sample that shows how to consume -the gui.cs from a NuGet package and .NET Core project. +This is just a simple standalone sample that shows how to consume Terinal.Gui from a NuGet package and .NET Core project. Simply run: @@ -7,6 +6,4 @@ Simply run: $ dotnet run ``` -To launch the application. - -Or use Visual Studio, open the solution and run. +To launch the application. Or use Visual Studio, open the solution and run. diff --git a/TODO.md b/TODO.md deleted file mode 100644 index 166936357..000000000 --- a/TODO.md +++ /dev/null @@ -1,48 +0,0 @@ - -# Things missing - -## Color System - -Topics to debate. - -Given that we need pairs of foreground/background to be set when -operating on a view, should we surface the values independently, or -should we surface the attribute? - -Currently views hardcode the colors to Colors.Base.SOmething for -example, perhaps these should be set with styles instead, or even -inheriting them. - -The reason why the Colors definition is useful is because it comes with -defaults that work for both color and black and white and other limited -terminals. Setting foreground/background independently tends to break -the black and white scenarios. - -## Color and Dialogs - -Replaces `Colors.Base.Normal` with `Attributes.Normal`, and perhaps attributes -points to the container. - -## Views - -Wanted: -- HotLabels (should be labelsw ith a hotkey that take a focus view as an argument) -- Shell/Process? -- Submenus in menus. -- Make windows draggable -- View + Attribute for SolidFills? - -Should Views support Padding/Margin/Border? Would make it simpler for Forms backend and perhaps -adopt the Forms CSS as-is - -## Layout manager - -Unclear what to do about that right now. Perhaps use Flex? - -Will at least need the protocol for sizing - -# Merge Responder into View - -For now it is split, in case we want to introduce formal view -controllers. But the design becomes very ugly. - diff --git a/Terminal.Gui/Core.cs b/Terminal.Gui/Core.cs index 4bed3fbf6..2bf0d78f7 100644 --- a/Terminal.Gui/Core.cs +++ b/Terminal.Gui/Core.cs @@ -126,7 +126,7 @@ namespace Terminal.Gui { /// /// Contains the details about the key that produced the event. /// true if the event was handled - public virtual bool KeyDown (KeyEvent keyEvent) + public virtual bool OnKeyDown (KeyEvent keyEvent) { return false; } @@ -136,7 +136,7 @@ namespace Terminal.Gui { /// /// Contains the details about the key that produced the event. /// true if the event was handled - public virtual bool KeyUp (KeyEvent keyEvent) + public virtual bool OnKeyUp (KeyEvent keyEvent) { return false; } @@ -607,7 +607,7 @@ namespace Terminal.Gui { return; while (subviews.Count > 0) { - Remove (subviews[0]); + Remove (subviews [0]); } } @@ -705,9 +705,9 @@ namespace Terminal.Gui { { PerformActionForSubview (subview, x => { var idx = subviews.IndexOf (x); - if (idx+1 < subviews.Count) { + if (idx + 1 < subviews.Count) { subviews.Remove (x); - subviews.Insert (idx+1, x); + subviews.Insert (idx + 1, x); } }); } @@ -910,7 +910,7 @@ namespace Terminal.Gui { OnEnter (); else OnLeave (); - SetNeedsDisplay (); + SetNeedsDisplay (); base.HasFocus = value; // Remove focus down the chain of subviews if focus is removed @@ -1062,18 +1062,33 @@ namespace Terminal.Gui { focused.EnsureFocus (); // Send focus upwards - SuperView?.SetFocus(this); + SuperView?.SetFocus (this); + } + + /// + /// Specifies the event arguments for + /// + public class KeyEventEventArgs : EventArgs { + /// + /// Constructs. + /// + /// + public KeyEventEventArgs(KeyEvent ke) => KeyEvent = ke; + /// + /// The for the event. + /// + public KeyEvent KeyEvent { get; set; } } /// /// Invoked when a character key is pressed and occurs after the key up event. /// - public Action OnKeyPress; + public event EventHandler KeyPress; /// public override bool ProcessKey (KeyEvent keyEvent) { - OnKeyPress?.Invoke (keyEvent); + KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); if (Focused?.ProcessKey (keyEvent) == true) return true; @@ -1083,6 +1098,7 @@ namespace Terminal.Gui { /// public override bool ProcessHotKey (KeyEvent keyEvent) { + KeyPress?.Invoke (this, new KeyEventEventArgs (keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1094,6 +1110,7 @@ namespace Terminal.Gui { /// public override bool ProcessColdKey (KeyEvent keyEvent) { + KeyPress?.Invoke (this, new KeyEventEventArgs(keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) @@ -1105,16 +1122,16 @@ namespace Terminal.Gui { /// /// Invoked when a key is pressed /// - public Action OnKeyDown; + public event EventHandler KeyDown; - /// - public override bool KeyDown (KeyEvent keyEvent) + /// Contains the details about the key that produced the event. + public override bool OnKeyDown (KeyEvent keyEvent) { - OnKeyDown?.Invoke (keyEvent); + KeyDown?.Invoke (this, new KeyEventEventArgs (keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) - if (view.KeyDown (keyEvent)) + if (view.OnKeyDown (keyEvent)) return true; return false; @@ -1123,16 +1140,16 @@ namespace Terminal.Gui { /// /// Invoked when a key is released /// - public Action OnKeyUp; + public event EventHandler KeyUp; - /// - public override bool KeyUp (KeyEvent keyEvent) + /// Contains the details about the key that produced the event. + public override bool OnKeyUp (KeyEvent keyEvent) { - OnKeyUp?.Invoke (keyEvent); + KeyUp?.Invoke (this, new KeyEventEventArgs (keyEvent)); if (subviews == null || subviews.Count == 0) return false; foreach (var view in subviews) - if (view.KeyUp (keyEvent)) + if (view.OnKeyUp (keyEvent)) return true; return false; @@ -1174,7 +1191,7 @@ namespace Terminal.Gui { public void FocusLast () { if (subviews == null) { - SuperView?.SetFocus(this); + SuperView?.SetFocus (this); return; } @@ -1221,7 +1238,7 @@ namespace Terminal.Gui { w.FocusLast (); SetFocus (w); - return true; + return true; } } if (focused != null) { @@ -1538,12 +1555,12 @@ namespace Terminal.Gui { /// /// Check id current toplevel has menu bar /// - public bool HasMenuBar { get; set; } + public MenuBar MenuBar { get; set; } /// /// Check id current toplevel has status bar /// - public bool HasStatusBar { get; set; } + public StatusBar StatusBar { get; set; } /// public override bool ProcessKey (KeyEvent keyEvent) @@ -1602,9 +1619,9 @@ namespace Terminal.Gui { { if (this == Application.Top) { if (view is MenuBar) - HasMenuBar = true; + MenuBar = view as MenuBar; if (view is StatusBar) - HasStatusBar = true; + StatusBar = view as StatusBar; } base.Add (view); } @@ -1614,9 +1631,9 @@ namespace Terminal.Gui { { if (this == Application.Top) { if (view is MenuBar) - HasMenuBar = true; + MenuBar = null; if (view is StatusBar) - HasStatusBar = true; + StatusBar = null; } base.Remove (view); } @@ -1625,8 +1642,8 @@ namespace Terminal.Gui { public override void RemoveAll () { if (this == Application.Top) { - HasMenuBar = false; - HasStatusBar = false; + MenuBar = null; + StatusBar = null; } base.RemoveAll (); } @@ -1634,21 +1651,21 @@ namespace Terminal.Gui { internal void EnsureVisibleBounds (Toplevel top, int x, int y, out int nx, out int ny) { nx = Math.Max (x, 0); - nx = nx + top.Frame.Width > Driver.Cols ? Math.Max(Driver.Cols - top.Frame.Width, 0) : nx; + nx = nx + top.Frame.Width > Driver.Cols ? Math.Max (Driver.Cols - top.Frame.Width, 0) : nx; bool m, s; - if (SuperView == null) - m = Application.Top.HasMenuBar; + if (SuperView == null || SuperView.GetType() != typeof(Toplevel)) + m = Application.Top.MenuBar != null; else - m = ((Toplevel)SuperView).HasMenuBar; + m = ((Toplevel)SuperView).MenuBar != null; int l = m ? 1 : 0; ny = Math.Max (y, l); - if (SuperView == null) - s = Application.Top.HasStatusBar; + if (SuperView == null || SuperView.GetType() != typeof(Toplevel)) + s = Application.Top.StatusBar != null; else - s = ((Toplevel)SuperView).HasStatusBar; + s = ((Toplevel)SuperView).StatusBar != null; l = s ? Driver.Rows - 1 : Driver.Rows; ny = Math.Min (ny, l); - ny = ny + top.Frame.Height > l ? Math.Max(l - top.Frame.Height, m ? 1 : 0) : ny; + ny = ny + top.Frame.Height > l ? Math.Max (l - top.Frame.Height, m ? 1 : 0) : ny; } internal void PositionToplevels () @@ -1667,9 +1684,15 @@ namespace Terminal.Gui { top.X = nx; top.Y = ny; } - if (HasStatusBar && ny + top.Frame.Height > Driver.Rows - 1) { - if (top.Height is Dim.DimFill) - top.Height = Dim.Fill () - 1; + if (StatusBar != null) { + if (ny + top.Frame.Height > Driver.Rows - 1) { + if (top.Height is Dim.DimFill) + top.Height = Dim.Fill () - 1; + } + if (StatusBar.Frame.Y != Driver.Rows - 1) { + StatusBar.Y = Driver.Rows - 1; + SetNeedsDisplay (); + } } } } @@ -1681,7 +1704,7 @@ namespace Terminal.Gui { { Application.CurrentView = this; - if (this == Application.Top) { + if (this == Application.Top || this == Application.Current) { if (!NeedDisplay.IsEmpty) { Driver.SetAttribute (Colors.TopLevel.Normal); Clear (region); @@ -1796,7 +1819,7 @@ namespace Terminal.Gui { this.Title = title; int wb = 1 + padding; this.padding = padding; - contentView = new ContentView () { + contentView = new ContentView () { X = wb, Y = wb, Width = Dim.Fill (wb), @@ -2008,7 +2031,7 @@ namespace Terminal.Gui { /// /// See also /// - static public event EventHandler Iteration; + public static event EventHandler Iteration; /// /// Returns a rectangle that is centered in the screen for the provided size. @@ -2079,7 +2102,7 @@ namespace Terminal.Gui { if (UseSystemConsole) { mainLoopDriver = new Mono.Terminal.NetMainLoop (); Driver = new NetDriver (); - } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows){ + } else if (p == PlatformID.Win32NT || p == PlatformID.Win32S || p == PlatformID.Win32Windows) { var windowsDriver = new WindowsDriver (); mainLoopDriver = windowsDriver; Driver = windowsDriver; @@ -2137,7 +2160,7 @@ namespace Terminal.Gui { static void ProcessKeyEvent (KeyEvent ke) { - var chain = toplevels.ToList(); + var chain = toplevels.ToList (); foreach (var topLevel in chain) { if (topLevel.ProcessHotKey (ke)) return; @@ -2165,7 +2188,7 @@ namespace Terminal.Gui { { var chain = toplevels.ToList (); foreach (var topLevel in chain) { - if (topLevel.KeyDown (ke)) + if (topLevel.OnKeyDown (ke)) return; if (topLevel.Modal) break; @@ -2177,7 +2200,7 @@ namespace Terminal.Gui { { var chain = toplevels.ToList (); foreach (var topLevel in chain) { - if (topLevel.KeyUp (ke)) + if (topLevel.OnKeyUp (ke)) return; if (topLevel.Modal) break; @@ -2194,7 +2217,7 @@ namespace Terminal.Gui { return null; } - if (start.InternalSubviews != null){ + if (start.InternalSubviews != null) { int count = start.InternalSubviews.Count; if (count > 0) { var rx = x - startFrame.X; @@ -2210,8 +2233,8 @@ namespace Terminal.Gui { } } } - resx = x-startFrame.X; - resy = y-startFrame.Y; + resx = x - startFrame.X; + resy = y - startFrame.Y; return start; } @@ -2242,7 +2265,7 @@ namespace Terminal.Gui { /// /// Merely a debugging aid to see the raw mouse events /// - static public Action RootMouseEvent; + public static Action RootMouseEvent; internal static View wantContinuousButtonPressedView; static View lastMouseOwnerView; @@ -2267,8 +2290,12 @@ namespace Terminal.Gui { OfY = me.Y - newxy.Y, View = view }; - mouseGrabView.MouseEvent (nme); - return; + if (OutsideFrame (new Point (nme.X, nme.Y), mouseGrabView.Frame)) + lastMouseOwnerView.OnMouseLeave (me); + if (mouseGrabView != null) { + mouseGrabView.MouseEvent (nme); + return; + } } if (view != null) { @@ -2303,10 +2330,16 @@ namespace Terminal.Gui { } } + static bool OutsideFrame (Point p, Rect r) + { + return p.X < 0 || p.X > r.Width - 1 || p.Y < 0 || p.Y > r.Height - 1; + } + /// - /// Action that is invoked once at beginning. + /// This event is fired once when the application is first loaded. The dimensions of the + /// terminal are provided. /// - static public Action OnLoad; + public static event EventHandler Loaded; /// /// Building block API: Prepares the provided toplevel for execution. @@ -2321,7 +2354,7 @@ namespace Terminal.Gui { /// the method, and then the method upon termination which will /// undo these changes. /// - static public RunState Begin (Toplevel toplevel) + public static RunState Begin (Toplevel toplevel) { if (toplevel == null) throw new ArgumentNullException (nameof (toplevel)); @@ -2330,11 +2363,11 @@ namespace Terminal.Gui { Init (); if (toplevel is ISupportInitializeNotification initializableNotification && !initializableNotification.IsInitialized) { - initializableNotification.BeginInit(); - initializableNotification.EndInit(); + initializableNotification.BeginInit (); + initializableNotification.EndInit (); } else if (toplevel is ISupportInitialize initializable) { - initializable.BeginInit(); - initializable.EndInit(); + initializable.BeginInit (); + initializable.EndInit (); } toplevels.Push (toplevel); Current = toplevel; @@ -2342,7 +2375,7 @@ namespace Terminal.Gui { if (toplevel.LayoutStyle == LayoutStyle.Computed) toplevel.RelativeLayout (new Rect (0, 0, Driver.Cols, Driver.Rows)); toplevel.LayoutSubviews (); - OnLoad?.Invoke (); + Loaded?.Invoke (null, new ResizedEventArgs () { Rows = Driver.Rows, Cols = Driver.Cols } ); toplevel.WillPresent (); Redraw (toplevel); toplevel.PositionCursor (); @@ -2355,7 +2388,7 @@ namespace Terminal.Gui { /// Building block API: completes the execution of a Toplevel that was started with Begin. /// /// The runstate returned by the method. - static public void End (RunState runState) + public static void End (RunState runState) { if (runState == null) throw new ArgumentNullException (nameof (runState)); @@ -2409,9 +2442,8 @@ namespace Terminal.Gui { toplevels.Pop (); if (toplevels.Count == 0) Shutdown (); - else - { - Current = toplevels.Peek(); + else { + Current = toplevels.Peek (); Refresh (); } } @@ -2480,7 +2512,7 @@ namespace Terminal.Gui { /// public static void Run () where T : Toplevel, new() { - Init (() => new T()); + Init (() => new T ()); Run (Top); } @@ -2525,14 +2557,28 @@ namespace Terminal.Gui { } /// - /// Invoked when the terminal was resized. + /// Event arguments for the event. /// - static public Action OnResized; + public class ResizedEventArgs : EventArgs { + /// + /// The number of rows in the resized terminal. + /// + public int Rows { get; set; } + /// + /// The number of columns in the resized terminal. + /// + public int Cols { get; set; } + } + + /// + /// Invoked when the terminal was resized. The new size of the terminal is provided. + /// + public static event EventHandler Resized; static void TerminalResized () { - OnResized?.Invoke (); var full = new Rect (0, 0, Driver.Cols, Driver.Rows); + Resized?.Invoke (null, new ResizedEventArgs () { Cols = full.Width, Rows = full.Height }); Driver.Clip = full; foreach (var t in toplevels) { t.PositionToplevels (); diff --git a/Terminal.Gui/Drivers/CursesDriver.cs b/Terminal.Gui/Drivers/CursesDriver.cs index 95a658108..2e8e7059a 100644 --- a/Terminal.Gui/Drivers/CursesDriver.cs +++ b/Terminal.Gui/Drivers/CursesDriver.cs @@ -7,6 +7,7 @@ using System; using System.Collections.Generic; using System.Runtime.InteropServices; +using System.Threading.Tasks; using Mono.Terminal; using NStack; using Unix.Terminal; @@ -183,12 +184,185 @@ namespace Terminal.Gui { } } - static MouseEvent ToDriverMouse (Curses.MouseEvent cev) + Curses.Event? LastMouseButtonPressed = null; + bool IsButtonPressed = false; + bool cancelButtonClicked = false; + Point point; + + MouseEvent ToDriverMouse (Curses.MouseEvent cev) + { + MouseFlags mouseFlag = MouseFlags.AllEvents; + + if (LastMouseButtonPressed != null && cev.ButtonState != Curses.Event.ReportMousePosition) { + LastMouseButtonPressed = null; + IsButtonPressed = false; + } + + + if ((cev.ButtonState == Curses.Event.Button1Clicked || cev.ButtonState == Curses.Event.Button2Clicked || + cev.ButtonState == Curses.Event.Button3Clicked) && + LastMouseButtonPressed == null) { + + IsButtonPressed = false; + mouseFlag = ProcessButtonClickedEvent (cev, mouseFlag); + + } else if (((cev.ButtonState == Curses.Event.Button1Pressed || cev.ButtonState == Curses.Event.Button2Pressed || + cev.ButtonState == Curses.Event.Button3Pressed) && LastMouseButtonPressed == null) || + IsButtonPressed && cev.ButtonState == Curses.Event.ReportMousePosition) { + + mouseFlag = (MouseFlags)cev.ButtonState; + if (cev.ButtonState != Curses.Event.ReportMousePosition) + LastMouseButtonPressed = cev.ButtonState; + IsButtonPressed = true; + + if (cev.ButtonState == Curses.Event.ReportMousePosition) { + mouseFlag = (MouseFlags)LastMouseButtonPressed | MouseFlags.ReportMousePosition; + point = new Point (); + cancelButtonClicked = true; + } else { + point = new Point () { + X = cev.X, + Y = cev.Y + }; + } + + if ((mouseFlag & MouseFlags.ReportMousePosition) == 0) { + Task.Run (async () => { + while (IsButtonPressed && LastMouseButtonPressed != null) { + await Task.Delay (200); + var me = new MouseEvent () { + X = cev.X, + Y = cev.Y, + Flags = mouseFlag + }; + + var view = Application.wantContinuousButtonPressedView; + if (view == null) + break; + if (IsButtonPressed && LastMouseButtonPressed != null && (mouseFlag & MouseFlags.ReportMousePosition) == 0) { + mouseHandler (me); + mainLoop.Driver.Wakeup (); + } + } + }); + } + + + } else if ((cev.ButtonState == Curses.Event.Button1Released || cev.ButtonState == Curses.Event.Button2Released || + cev.ButtonState == Curses.Event.Button3Released)) { + + mouseFlag = ProcessButtonReleasedEvent (cev, mouseFlag); + IsButtonPressed = false; + + } else if (cev.ButtonState == Curses.Event.Button4Pressed) { + + mouseFlag = MouseFlags.WheeledUp; + + } else if (cev.ButtonState == Curses.Event.ReportMousePosition && cev.X == point.X && cev.Y == point.Y) { + + mouseFlag = MouseFlags.WheeledDown; + + } + else if (cev.ButtonState == Curses.Event.ReportMousePosition) { + + mouseFlag = MouseFlags.ReportMousePosition; + } else { + mouseFlag = (MouseFlags)cev.ButtonState; + } + + point = new Point () { + X = cev.X, + Y = cev.Y + }; + + + + if (cev.ID != 0) + mouseFlag = MouseFlags.WheeledDown; + + return new MouseEvent () { + X = cev.X, + Y = cev.Y, + //Flags = (MouseFlags)cev.ButtonState + Flags = mouseFlag + }; + } + + private MouseFlags ProcessButtonClickedEvent (Curses.MouseEvent cev, MouseFlags mf) + { + LastMouseButtonPressed = cev.ButtonState; + mf = GetButtonState (cev, true); + mouseHandler (ProcessButtonState (cev, mf)); + if (LastMouseButtonPressed != null && LastMouseButtonPressed == cev.ButtonState) { + mf = GetButtonState (cev, false); + mouseHandler (ProcessButtonState (cev, mf)); + if (LastMouseButtonPressed != null && LastMouseButtonPressed == cev.ButtonState) { + mf = (MouseFlags)cev.ButtonState; + } + } + LastMouseButtonPressed = null; + return mf; + } + + private MouseFlags ProcessButtonReleasedEvent (Curses.MouseEvent cev, MouseFlags mf) + { + mf = (MouseFlags)cev.ButtonState; + mouseHandler (ProcessButtonState (cev, mf)); + if (!cancelButtonClicked && LastMouseButtonPressed == null) + mf = GetButtonState (cev); + else + cancelButtonClicked = false; + return mf; + } + + MouseFlags GetButtonState (Curses.MouseEvent cev, bool pressed = false) + { + MouseFlags mf = default; + switch (cev.ButtonState) { + case Curses.Event.Button1Clicked: + if (pressed) + mf = MouseFlags.Button1Pressed; + else + mf = MouseFlags.Button1Released; + break; + + case Curses.Event.Button2Clicked: + if (pressed) + mf = MouseFlags.Button2Pressed; + else + mf = MouseFlags.Button2Released; + break; + + case Curses.Event.Button3Clicked: + if (pressed) + mf = MouseFlags.Button3Pressed; + else + mf = MouseFlags.Button3Released; + break; + + case Curses.Event.Button1Released: + mf = MouseFlags.Button1Clicked; + break; + + case Curses.Event.Button2Released: + mf = MouseFlags.Button2Clicked; + break; + + case Curses.Event.Button3Released: + mf = MouseFlags.Button3Clicked; + break; + + + } + return mf; + } + + MouseEvent ProcessButtonState (Curses.MouseEvent cev, MouseFlags mf) { return new MouseEvent () { X = cev.X, Y = cev.Y, - Flags = (MouseFlags)cev.ButtonState + Flags = mf }; } @@ -252,10 +426,15 @@ namespace Terminal.Gui { keyUpHandler (new KeyEvent ((Key)wch)); } + Action mouseHandler; + MainLoop mainLoop; + public override void PrepareToRun (MainLoop mainLoop, Action keyHandler, Action keyDownHandler, Action keyUpHandler, Action mouseHandler) { // Note: Curses doesn't support keydown/up events and thus any passed keyDown/UpHandlers will never be called - Curses.timeout (-1); + Curses.timeout (0); + this.mouseHandler = mouseHandler; + this.mainLoop = mainLoop; (mainLoop.Driver as Mono.Terminal.UnixMainLoop).AddWatch (0, Mono.Terminal.UnixMainLoop.Condition.PollIn, x => { ProcessInput (keyHandler, keyUpHandler, mouseHandler); @@ -425,21 +604,21 @@ namespace Terminal.Gui { Console.Out.Flush (); } - int lastMouseInterval; - bool mouseGrabbed; + //int lastMouseInterval; + //bool mouseGrabbed; public override void UncookMouse () { - if (mouseGrabbed) - return; - lastMouseInterval = Curses.mouseinterval (0); - mouseGrabbed = true; + //if (mouseGrabbed) + // return; + //lastMouseInterval = Curses.mouseinterval (0); + //mouseGrabbed = true; } public override void CookMouse () { - mouseGrabbed = false; - Curses.mouseinterval (lastMouseInterval); + //mouseGrabbed = false; + //Curses.mouseinterval (lastMouseInterval); } } diff --git a/Terminal.Gui/Drivers/WindowsDriver.cs b/Terminal.Gui/Drivers/WindowsDriver.cs index 343182dc0..0576071e3 100644 --- a/Terminal.Gui/Drivers/WindowsDriver.cs +++ b/Terminal.Gui/Drivers/WindowsDriver.cs @@ -673,10 +673,10 @@ namespace Terminal.Gui { keyUpHandler (key); } else { if (inputEvent.KeyEvent.bKeyDown) { + // Key Down - Fire KeyDown Event and KeyStroke (ProcessKey) Event + keyHandler (new KeyEvent (map)); keyDownHandler (new KeyEvent (map)); } else { - // Key Up - Fire KeyDown Event and KeyStroke (ProcessKey) Event - keyHandler (new KeyEvent (map)); keyUpHandler (new KeyEvent (map)); } } diff --git a/Terminal.Gui/MonoCurses/constants.cs b/Terminal.Gui/MonoCurses/constants.cs index df19c30e2..8eed47967 100644 --- a/Terminal.Gui/MonoCurses/constants.cs +++ b/Terminal.Gui/MonoCurses/constants.cs @@ -2,6 +2,8 @@ * This file is autogenerated by the attrib.c program, do not edit */ +#define XTERM1006 + using System; namespace Unix.Terminal { @@ -77,6 +79,15 @@ namespace Unix.Terminal { ReportMousePosition = unchecked((int)0x8000000), AllEvents = unchecked((int)0x7ffffff), } +#if XTERM1006 + public const int LeftRightUpNPagePPage= unchecked((int)0x8); + public const int DownEnd = unchecked((int)0x6); + public const int Home = unchecked((int)0x7); +#else + public const int LeftRightUpNPagePPage= unchecked((int)0x0); + public const int DownEnd = unchecked((int)0x0); + public const int Home = unchecked((int)0x0); +#endif public const int ERR = unchecked((int)0xffffffff); public const int KeyBackspace = unchecked((int)0x107); public const int KeyUp = unchecked((int)0x103); @@ -110,30 +121,30 @@ namespace Unix.Terminal { public const int ShiftKeyPPage = unchecked((int)0x18e); public const int ShiftKeyHome = unchecked((int)0x187); public const int ShiftKeyEnd = unchecked((int)0x182); - public const int AltKeyUp = unchecked((int)0x234); - public const int AltKeyDown = unchecked((int)0x20b); - public const int AltKeyLeft = unchecked((int)0x21f); - public const int AltKeyRight = unchecked((int)0x22e); - public const int AltKeyNPage = unchecked((int)0x224); - public const int AltKeyPPage = unchecked((int)0x229); - public const int AltKeyHome = unchecked((int)0x215); - public const int AltKeyEnd = unchecked((int)0x210); - public const int CtrlKeyUp = unchecked((int)0x236); - public const int CtrlKeyDown = unchecked((int)0x20d); - public const int CtrlKeyLeft = unchecked((int)0x221); - public const int CtrlKeyRight = unchecked((int)0x230); - public const int CtrlKeyNPage = unchecked((int)0x226); - public const int CtrlKeyPPage = unchecked((int)0x22b); - public const int CtrlKeyHome = unchecked((int)0x217); - public const int CtrlKeyEnd = unchecked((int)0x212); - public const int ShiftCtrlKeyUp = unchecked((int)0x237); - public const int ShiftCtrlKeyDown = unchecked((int)0x20e); - public const int ShiftCtrlKeyLeft = unchecked((int)0x222); - public const int ShiftCtrlKeyRight = unchecked((int)0x231); - public const int ShiftCtrlKeyNPage = unchecked((int)0x227); - public const int ShiftCtrlKeyPPage = unchecked((int)0x22c); - public const int ShiftCtrlKeyHome = unchecked((int)0x218); - public const int ShiftCtrlKeyEnd = unchecked((int)0x213); + public const int AltKeyUp = unchecked((int)0x234 + LeftRightUpNPagePPage); + public const int AltKeyDown = unchecked((int)0x20b + DownEnd); + public const int AltKeyLeft = unchecked((int)0x21f + LeftRightUpNPagePPage); + public const int AltKeyRight = unchecked((int)0x22e + LeftRightUpNPagePPage); + public const int AltKeyNPage = unchecked((int)0x224 + LeftRightUpNPagePPage); + public const int AltKeyPPage = unchecked((int)0x229 + LeftRightUpNPagePPage); + public const int AltKeyHome = unchecked((int)0x215 + Home); + public const int AltKeyEnd = unchecked((int)0x210 + DownEnd); + public const int CtrlKeyUp = unchecked((int)0x236 + LeftRightUpNPagePPage); + public const int CtrlKeyDown = unchecked((int)0x20d + DownEnd); + public const int CtrlKeyLeft = unchecked((int)0x221 + LeftRightUpNPagePPage); + public const int CtrlKeyRight = unchecked((int)0x230 + LeftRightUpNPagePPage); + public const int CtrlKeyNPage = unchecked((int)0x226 + LeftRightUpNPagePPage); + public const int CtrlKeyPPage = unchecked((int)0x22b + LeftRightUpNPagePPage); + public const int CtrlKeyHome = unchecked((int)0x217 + Home); + public const int CtrlKeyEnd = unchecked((int)0x212 + DownEnd); + public const int ShiftCtrlKeyUp = unchecked((int)0x237 + LeftRightUpNPagePPage); + public const int ShiftCtrlKeyDown = unchecked((int)0x20e + DownEnd); + public const int ShiftCtrlKeyLeft = unchecked((int)0x222 + LeftRightUpNPagePPage); + public const int ShiftCtrlKeyRight = unchecked((int)0x231 + LeftRightUpNPagePPage); + public const int ShiftCtrlKeyNPage = unchecked((int)0x227 + LeftRightUpNPagePPage); + public const int ShiftCtrlKeyPPage = unchecked((int)0x22c + LeftRightUpNPagePPage); + public const int ShiftCtrlKeyHome = unchecked((int)0x218 + Home); + public const int ShiftCtrlKeyEnd = unchecked((int)0x213 + DownEnd); public const int LC_ALL = 6; static public int ColorPair(int n){ diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs index 309c408e0..7262fa478 100644 --- a/Terminal.Gui/Views/ListView.cs +++ b/Terminal.Gui/Views/ListView.cs @@ -63,6 +63,12 @@ namespace Terminal.Gui { /// Item index. /// If set to true value. void SetMark (int item, bool value); + + /// + /// Return the source as IList. + /// + /// + IList ToList (); } /// @@ -257,7 +263,7 @@ namespace Terminal.Gui { /// Redraws the ListView /// /// Region. - public override void Redraw(Rect region) + public override void Redraw (Rect region) { var current = ColorScheme.Focus; Driver.SetAttribute (current); @@ -279,12 +285,12 @@ namespace Terminal.Gui { Move (0, row); if (source == null || item >= source.Count) { for (int c = 0; c < f.Width; c++) - Driver.AddRune(' '); + Driver.AddRune (' '); } else { if (allowsMarking) { Driver.AddStr (source.IsMarked (item) ? (AllowsMultipleSelection ? "[x] " : "(o)") : (AllowsMultipleSelection ? "[ ] " : "( )")); } - Source.Render(this, Driver, isSelected, item, col, row, f.Width-col); + Source.Render (this, Driver, isSelected, item, col, row, f.Width - col); } } } @@ -292,12 +298,12 @@ namespace Terminal.Gui { /// /// This event is raised when the cursor selection has changed. /// - public event Action SelectedChanged; + public event EventHandler SelectedChanged; /// /// This event is raised on Enter key or Double Click to open the selected item. /// - public event EventHandler OpenSelectedItem; + public event EventHandler OpenSelectedItem; /// /// Handles cursor movement for this view, passes all other events. @@ -312,27 +318,27 @@ namespace Terminal.Gui { switch (kb.Key) { case Key.CursorUp: case Key.ControlP: - return MoveUp(); + return MoveUp (); case Key.CursorDown: case Key.ControlN: - return MoveDown(); + return MoveDown (); case Key.ControlV: case Key.PageDown: - return MovePageDown(); + return MovePageDown (); case Key.PageUp: - return MovePageUp(); + return MovePageUp (); case Key.Space: - if (MarkUnmarkRow()) + if (MarkUnmarkRow ()) return true; else break; case Key.Enter: - OpenSelectedItem?.Invoke (this, new EventArgs ()); + OnOpenSelectedItem (); break; } @@ -340,7 +346,7 @@ namespace Terminal.Gui { } /// - /// + /// Prevents marking if it's not allowed mark and if it's not allows multiple selection. /// /// public virtual bool AllowsAll () @@ -359,13 +365,14 @@ namespace Terminal.Gui { } /// - /// + /// Marks an unmarked row. /// /// - public virtual bool MarkUnmarkRow(){ + public virtual bool MarkUnmarkRow () + { if (AllowsAll ()) { - Source.SetMark(SelectedItem, !Source.IsMarked(SelectedItem)); - SetNeedsDisplay(); + Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem)); + SetNeedsDisplay (); return true; } @@ -373,84 +380,114 @@ namespace Terminal.Gui { } /// - /// + /// Moves to the next page. /// /// - public virtual bool MovePageUp(){ + public virtual bool MovePageUp () + { int n = (selected - Frame.Height); if (n < 0) n = 0; - if (n != selected){ + if (n != selected) { selected = n; top = selected; - if (SelectedChanged != null) - SelectedChanged(); - SetNeedsDisplay(); + OnSelectedChanged (); + SetNeedsDisplay (); } return true; } /// - /// + /// Moves to the previous page. /// /// - public virtual bool MovePageDown(){ + public virtual bool MovePageDown () + { var n = (selected + Frame.Height); if (n > source.Count) n = source.Count - 1; - if (n != selected){ + if (n != selected) { selected = n; if (source.Count >= Frame.Height) top = selected; else top = 0; - if (SelectedChanged != null) - SelectedChanged(); - SetNeedsDisplay(); + OnSelectedChanged (); + SetNeedsDisplay (); } return true; } /// - /// + /// Moves to the next row. /// /// - public virtual bool MoveDown(){ - if (selected + 1 < source.Count){ + public virtual bool MoveDown () + { + if (selected + 1 < source.Count) { selected++; if (selected >= top + Frame.Height) top++; - if (SelectedChanged != null) - SelectedChanged(); - SetNeedsDisplay(); + OnSelectedChanged (); + SetNeedsDisplay (); } return true; } /// - /// + /// Moves to the previous row. /// /// - public virtual bool MoveUp(){ - if (selected > 0){ + public virtual bool MoveUp () + { + if (selected > 0) { selected--; if (selected < top) top = selected; - if (SelectedChanged != null) - SelectedChanged(); - SetNeedsDisplay(); + OnSelectedChanged (); + SetNeedsDisplay (); } return true; } + int lastSelectedItem = -1; + + /// + /// Invokes the SelectedChanged event if it is defined. + /// + /// + public virtual bool OnSelectedChanged () + { + if (selected != lastSelectedItem) { + var value = source.ToList () [selected]; + SelectedChanged?.Invoke (this, new ListViewItemEventArgs (selected, value)); + lastSelectedItem = selected; + return true; + } + + return false; + } + + /// + /// Invokes the OnOpenSelectedItem event if it is defined. + /// + /// + public virtual bool OnOpenSelectedItem () + { + var value = source.ToList () [selected]; + OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (selected, value)); + + return true; + } + /// /// Positions the cursor in this view /// - public override void PositionCursor() + public override void PositionCursor () { if (allowsMarking) Move (1, selected - top); @@ -461,7 +498,8 @@ namespace Terminal.Gui { /// public override bool MouseEvent(MouseEvent me) { - if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)) + if (!me.Flags.HasFlag (MouseFlags.Button1Clicked) && !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked) && + me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp) return false; if (!HasFocus) @@ -470,6 +508,14 @@ namespace Terminal.Gui { if (source == null) return false; + if (me.Flags == MouseFlags.WheeledDown) { + MoveDown (); + return true; + } else if (me.Flags == MouseFlags.WheeledUp) { + MoveUp (); + return true; + } + if (me.Y + top >= source.Count) return true; @@ -479,10 +525,10 @@ namespace Terminal.Gui { SetNeedsDisplay (); return true; } - SelectedChanged?.Invoke (); + OnSelectedChanged (); SetNeedsDisplay (); if (me.Flags == MouseFlags.Button1DoubleClicked) - OpenSelectedItem?.Invoke (this, new EventArgs ()); + OnOpenSelectedItem (); return true; } } @@ -497,7 +543,7 @@ namespace Terminal.Gui { int count; /// - /// constructor + /// Constructor based on a source. /// /// public ListWrapper (IList source) @@ -508,7 +554,7 @@ namespace Terminal.Gui { } /// - /// Count of items. + /// Returns the amount of items in the source. /// public int Count => src.Count; @@ -519,7 +565,7 @@ namespace Terminal.Gui { for (int i = 0; i < byteLen;) { (var rune, var size) = Utf8.DecodeRune (ustr, i, i - byteLen); var count = Rune.ColumnWidth (rune); - if (used+count >= width) + if (used + count >= width) break; driver.AddRune (rune); used += count; @@ -531,15 +577,15 @@ namespace Terminal.Gui { } /// - /// Renders an item in the the list. + /// Method that render to the appropriate type based on the type of the item passed to it. /// - /// - /// - /// - /// - /// - /// - /// + /// The ListView. + /// The driver used by the caller. + /// Informs if it's marked or not. + /// The item. + /// The col where to move. + /// The line where to move. + /// The item width. public void Render (ListView container, ConsoleDriver driver, bool marked, int item, int col, int line, int width) { container.Move (col, line); @@ -553,10 +599,10 @@ namespace Terminal.Gui { } /// - /// Returns true of the item is marked. false if not. + /// Returns true if the item is marked, false otherwise. /// - /// - /// + /// The item. + /// trueIf is marked.falseotherwise. public bool IsMarked (int item) { if (item >= 0 && item < count) @@ -565,14 +611,48 @@ namespace Terminal.Gui { } /// - /// Sets the marked state of an item. + /// Sets the item as marked or unmarked based on the value is true or false, respectively. /// - /// - /// + /// The item + /// Marks the item.Unmarked the item.The value. public void SetMark (int item, bool value) { if (item >= 0 && item < count) marks [item] = value; } + + /// + /// Returns the source as IList. + /// + /// + public IList ToList () + { + return src; + } + } + + /// + /// Gets the item and value to use in an event handler. + /// + public class ListViewItemEventArgs : EventArgs { + /// + /// The item. + /// + public int Item { get; } + /// + /// The item value. + /// + public object Value { get; } + + /// + /// Constructor to sets the item and value passed from. + /// + /// The item. + /// The item value + public ListViewItemEventArgs (int item, object value) + { + Item = item; + Value = value; + } } } diff --git a/Terminal.Gui/Views/Menu.cs b/Terminal.Gui/Views/Menu.cs index 56eecf66c..d6438a8a4 100644 --- a/Terminal.Gui/Views/Menu.cs +++ b/Terminal.Gui/Views/Menu.cs @@ -347,7 +347,7 @@ namespace Terminal.Gui { }); } - public override bool KeyDown (KeyEvent keyEvent) + public override bool OnKeyDown (KeyEvent keyEvent) { if (keyEvent.IsAlt) { host.CloseAllMenus (); @@ -360,9 +360,9 @@ 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) { - KeyDown (keyEvent); + OnKeyDown (keyEvent); return true; } @@ -563,8 +563,8 @@ namespace Terminal.Gui { bool openedByAltKey; - /// - public override bool KeyDown (KeyEvent keyEvent) + /// + public override bool OnKeyDown (KeyEvent keyEvent) { if (keyEvent.IsAlt) { openedByAltKey = true; @@ -574,13 +574,8 @@ 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 KeyUp (KeyEvent keyEvent) + /// + public override bool OnKeyUp (KeyEvent keyEvent) { if (keyEvent.IsAlt) { // User pressed Alt - this may be a precursor to a menu accelerator (e.g. Alt-F) @@ -1001,10 +996,10 @@ 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) { - KeyDown (kb); - KeyUp (kb); + OnKeyDown (kb); + OnKeyUp (kb); return true; } else if (kb.IsAlt) { if (FindAndOpenMenuByHotkey (kb)) return true; diff --git a/Terminal.Gui/Views/ScrollView.cs b/Terminal.Gui/Views/ScrollView.cs index b10e567b2..e67f43eed 100644 --- a/Terminal.Gui/Views/ScrollView.cs +++ b/Terminal.Gui/Views/ScrollView.cs @@ -14,6 +14,8 @@ // - Perhaps allow an option to not display the scrollbar arrow indicators? using System; +using System.Reflection; + namespace Terminal.Gui { /// /// ScrollBarViews are views that display a 1-character scrollbar, either horizontal or vertical @@ -73,7 +75,7 @@ namespace Terminal.Gui { /// Frame for the scrollbar. /// The size that this scrollbar represents. /// The position within this scrollbar. - /// If set to true this is a vertical scrollbar, otherwize, the scrollbar is horizontal. + /// If set to true this is a vertical scrollbar, otherwise, the scrollbar is horizontal. public ScrollBarView (Rect rect, int size, int position, bool isVertical) : base (rect) { vertical = isVertical; @@ -314,9 +316,35 @@ namespace Terminal.Gui { /// The view to add to the scrollview. public override void Add (View view) { + if (!IsOverridden (view)) { + view.MouseEnter += View_MouseEnter; + view.MouseLeave += View_MouseLeave; + vertical.MouseEnter += View_MouseEnter; + vertical.MouseLeave += View_MouseLeave; + horizontal.MouseEnter += View_MouseEnter; + horizontal.MouseLeave += View_MouseLeave; + } contentView.Add (view); } + void View_MouseLeave (object sender, MouseEvent e) + { + Application.UngrabMouse (); + } + + void View_MouseEnter (object sender, MouseEvent e) + { + Application.GrabMouse (this); + } + + bool IsOverridden (View view) + { + Type t = view.GetType (); + MethodInfo m = t.GetMethod ("MouseEvent"); + + return m.DeclaringType == t && m.GetBaseDefinition ().DeclaringType == typeof (Responder); + } + /// /// Gets or sets the visibility for the horizontal scroll indicator. /// @@ -463,7 +491,7 @@ namespace Terminal.Gui { switch (kb.Key) { case Key.CursorUp: return ScrollUp (1); - case (Key) 'v' | Key.AltMask: + case (Key)'v' | Key.AltMask: case Key.PageUp: return ScrollUp (Bounds.Height); @@ -489,5 +517,18 @@ namespace Terminal.Gui { } return false; } + + public override bool MouseEvent (MouseEvent me) + { + if (me.Flags != MouseFlags.WheeledDown && me.Flags != MouseFlags.WheeledUp) + return false; + + if (me.Flags == MouseFlags.WheeledDown) + ScrollDown (1); + else if (me.Flags == MouseFlags.WheeledUp) + ScrollUp (1); + + return true; + } } } diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs index 1bb5f2b33..789781815 100644 --- a/Terminal.Gui/Views/StatusBar.cs +++ b/Terminal.Gui/Views/StatusBar.cs @@ -107,8 +107,12 @@ namespace Terminal.Gui { Items = items; CanFocus = false; ColorScheme = Colors.Menu; + X = 0; + Y = Driver.Rows - 1; + Width = Dim.Fill (); + Height = 1; - Application.OnLoad += () => { + Application.Loaded += (sender, e) => { X = 0; Height = 1; #if SNAP_TO_TOP @@ -120,7 +124,7 @@ namespace Terminal.Gui { case StatusBarStyle.SnapToBottom: #endif if (Parent == null) { - Y = Application.Driver.Rows - 1; // TODO: using internals of Application + Y = e.Rows - 1; } else { Y = Pos.Bottom (Parent); } @@ -141,11 +145,11 @@ namespace Terminal.Gui { /// public override void Redraw (Rect region) { - if (Frame.Y != Driver.Rows - 1) { - Frame = new Rect (Frame.X, Driver.Rows - 1, Frame.Width, Frame.Height); - Y = Driver.Rows - 1; - SetNeedsDisplay (); - } + //if (Frame.Y != Driver.Rows - 1) { + // Frame = new Rect (Frame.X, Driver.Rows - 1, Frame.Width, Frame.Height); + // Y = Driver.Rows - 1; + // SetNeedsDisplay (); + //} Move (0, 0); Driver.SetAttribute (ColorScheme.Normal); diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs index 781866ef5..71a53ccd7 100644 --- a/Terminal.Gui/Views/TextField.cs +++ b/Terminal.Gui/Views/TextField.cs @@ -28,6 +28,11 @@ namespace Terminal.Gui { /// public bool Used { get => used; set { used = value; } } + /// + /// If set to true its not allow any changes in the text. + /// + public bool ReadOnly { get; set; } = false; + /// /// Changed event, raised when the text has clicked. /// @@ -95,7 +100,7 @@ namespace Terminal.Gui { set { base.Frame = value; var w = base.Frame.Width; - //first = point > w ? point - w : 0; + first = point > w ? point - w : 0; Adjust (); } } @@ -115,6 +120,9 @@ namespace Terminal.Gui { } set { + if (ReadOnly) + return; + var oldText = ustring.Make (text); text = TextModel.ToRunes (value); if (!Secret && !isFromHistory) { @@ -184,13 +192,16 @@ namespace Terminal.Gui { int col = 0; int width = Frame.Width; var tcount = text.Count; + var roc = new Attribute (Color.DarkGray, Color.Gray); for (int idx = 0; idx < tcount; idx++){ var rune = text [idx]; if (idx < p) continue; var cols = Rune.ColumnWidth (rune); - if (col == point && HasFocus && !Used && SelectedLength == 0) + if (col == point && HasFocus && !Used && SelectedLength == 0 && !ReadOnly) Driver.SetAttribute (Colors.Menu.HotFocus); + else if (ReadOnly) + Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? color.Focus : roc); else Driver.SetAttribute (idx >= start && length > 0 && idx < start + length ? color.Focus : ColorScheme.Focus); if (col + cols <= width) @@ -261,6 +272,9 @@ namespace Terminal.Gui { switch (kb.Key) { case Key.DeleteChar: case Key.ControlD: + if (ReadOnly) + return true; + if (SelectedLength == 0) { if (text.Count == 0 || text.Count == point) return true; @@ -275,6 +289,9 @@ namespace Terminal.Gui { case Key.Delete: case Key.Backspace: + if (ReadOnly) + return true; + if (SelectedLength == 0) { if (point == 0) return true; @@ -373,6 +390,9 @@ namespace Terminal.Gui { break; case Key.ControlK: // kill-to-end + if (ReadOnly) + return true; + ClearAllSelection (); if (point >= text.Count) return true; @@ -383,6 +403,9 @@ namespace Terminal.Gui { // Undo case Key.ControlZ: + if (ReadOnly) + return true; + if (historyText != null && historyText.Count > 0) { isFromHistory = true; if (idxhistoryText > 0) @@ -396,6 +419,9 @@ namespace Terminal.Gui { //Redo case Key.ControlY: // Control-y, yank + if (ReadOnly) + return true; + if (historyText != null && historyText.Count > 0) { isFromHistory = true; if (idxhistoryText < historyText.Count - 1) { @@ -455,6 +481,9 @@ namespace Terminal.Gui { break; case Key.ControlX: + if (ReadOnly) + return true; + Cut (); break; @@ -472,6 +501,9 @@ namespace Terminal.Gui { if (kb.Key < Key.Space || kb.Key > Key.CharMask) return false; + if (ReadOnly) + return true; + if (SelectedLength != 0) { DeleteSelectedText (); oldCursorPos = point; @@ -639,7 +671,7 @@ namespace Terminal.Gui { point = text.Count; if (point < first) point = 0; - return x; + return point; } void PrepareSelection (int x, int direction = 0) @@ -682,8 +714,11 @@ namespace Terminal.Gui { /// /// Copy the selected text to the clipboard. /// - public void Copy () + public virtual void Copy () { + if (Secret) + return; + if (SelectedLength != 0) { Clipboard.Contents = SelectedText; } @@ -692,7 +727,7 @@ namespace Terminal.Gui { /// /// Cut the selected text to the clipboard. /// - public void Cut () + public virtual void Cut () { if (SelectedLength != 0) { Clipboard.Contents = SelectedText; @@ -715,8 +750,11 @@ namespace Terminal.Gui { /// /// Paste the selected text from the clipboard. /// - public void Paste () + public virtual void Paste () { + if (ReadOnly) + return; + string actualText = Text.ToString (); int start = SelectedStart == -1 ? CursorPosition : SelectedStart; ustring cbTxt = Clipboard.Contents?.ToString () ?? ""; diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs index 0d6f35a5c..738a6f35f 100644 --- a/Terminal.Gui/Views/TextView.cs +++ b/Terminal.Gui/Views/TextView.cs @@ -39,6 +39,7 @@ namespace Terminal.Gui { if (file == null) throw new ArgumentNullException (nameof (file)); try { + FilePath = file; var stream = File.OpenRead (file); } catch { return false; @@ -47,6 +48,19 @@ namespace Terminal.Gui { return true; } + public bool CloseFile () + { + if (FilePath == null) + throw new ArgumentNullException (nameof (FilePath)); + try { + FilePath = null; + lines = new List> (); + } catch { + return false; + } + return true; + } + // Turns the ustring into runes, this does not split the // contents on a newline if it is present. internal static List ToRunes (ustring str) @@ -120,6 +134,8 @@ namespace Terminal.Gui { return sb.ToString (); } + public string FilePath { get; set; } + /// /// The number of text lines in the model /// @@ -351,6 +367,18 @@ namespace Terminal.Gui { SetNeedsDisplay (); } + /// + /// Closes the contents of the stream into the TextView. + /// + /// true, if stream was closed, false otherwise. + public bool CloseFile() + { + ResetPosition (); + var res = model.CloseFile (); + SetNeedsDisplay (); + return res; + } + /// /// The current cursor row. /// diff --git a/Terminal.sln b/Terminal.sln index 77680ffd0..3b1fc187e 100644 --- a/Terminal.sln +++ b/Terminal.sln @@ -1,13 +1,14 @@ - Microsoft Visual Studio Solution File, Format Version 12.00 -# Visual Studio 2012 +# Visual Studio Version 16 +VisualStudioVersion = 16.0.30114.128 +MinimumVisualStudioVersion = 10.0.40219.1 Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Example", "Example\Example.csproj", "{B0A602CD-E176-449D-8663-64238D54F857}" EndProject -Project("{FAE04EC0-301F-11D3-BF4B-00C04F79EFBC}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "Terminal.Gui", "Terminal.Gui\Terminal.Gui.csproj", "{00F366F8-DEE4-482C-B9FD-6DB0200B79E5}" 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}" +Project("{9A19103F-16F7-4668-BE54-9A1E7A4F7556}") = "UICatalog", "UICatalog\UICatalog.csproj", "{88979F89-9A42-448F-AE3E-3060145F6375}" EndProject Global GlobalSection(SolutionConfigurationPlatforms) = preSolution @@ -27,15 +28,17 @@ 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(SolutionProperties) = preSolution + HideSolutionNode = FALSE + EndGlobalSection + GlobalSection(ExtensibilityGlobals) = postSolution + SolutionGuid = {9F8F8A4D-7B8D-4C2A-AC5E-CD7117F74C03} + EndGlobalSection GlobalSection(MonoDevelopProperties) = preSolution Policies = $0 $0.TextStylePolicy = $1 diff --git a/UICatalog/Program.cs b/UICatalog/Program.cs index 14a7956db..a571f99c2 100644 --- a/UICatalog/Program.cs +++ b/UICatalog/Program.cs @@ -1,5 +1,6 @@ using NStack; using System; +using System.Collections; using System.Collections.Generic; using System.Diagnostics; using System.Globalization; @@ -62,7 +63,7 @@ namespace UICatalog { 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")), + new MenuBarItem ("_About...", "About this app", () => MessageBox.Query (0, 10, "About UI Catalog", "UI Catalog is a comprehensive sample library for Terminal.Gui", "Ok")), }); _leftPane = new Window ("Categories") { @@ -119,7 +120,7 @@ namespace UICatalog { _rightPane.Add (_scenarioListView); _categoryListView.SelectedItem = 0; - CategoryListView_SelectedChanged (); + _categoryListView.OnSelectedChanged (); _statusBar = new StatusBar (new StatusItem [] { //new StatusItem(Key.F1, "~F1~ Help", () => Help()), @@ -148,7 +149,9 @@ namespace UICatalog { } _top = Application.Top; - _top.OnKeyUp += KeyUpHandler; + + _top.KeyUp += KeyUpHandler; + _top.Add (_menu); _top.Add (_leftPane); _top.Add (_rightPane); @@ -227,6 +230,12 @@ namespace UICatalog { used++; } } + + public IList ToList () + { + return Scenarios; + } + } /// @@ -235,7 +244,7 @@ namespace UICatalog { /// to not be impacted. Same as for tabs. /// /// - private static void KeyUpHandler (KeyEvent ke) + private static void KeyUpHandler (object sender, View.KeyEventEventArgs a) { if (_runningScenario != null) { //switch (ke.Key) { @@ -244,8 +253,8 @@ namespace UICatalog { // break; //case Key.Enter: // break; - //} - } else if (ke.Key == Key.Tab || ke.Key == Key.BackTab) { + //}< + } 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); @@ -254,7 +263,7 @@ namespace UICatalog { } } - private static void CategoryListView_SelectedChanged () + private static void CategoryListView_SelectedChanged (object sender, ListViewItemEventArgs e) { var item = _categories [_categoryListView.SelectedItem]; List newlist; diff --git a/UICatalog/Properties/launchSettings.json b/UICatalog/Properties/launchSettings.json index 3941ffe72..68c82f53f 100644 --- a/UICatalog/Properties/launchSettings.json +++ b/UICatalog/Properties/launchSettings.json @@ -2,7 +2,6 @@ "profiles": { "UICatalog": { "commandName": "Project" - } } } \ No newline at end of file diff --git a/UICatalog/README.md b/UICatalog/README.md index 6f6b019f8..49eebf94c 100644 --- a/UICatalog/README.md +++ b/UICatalog/README.md @@ -119,4 +119,4 @@ For complete control, the `Init` and `Run` overrides can be implemented. The `ba - 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 +- Tag bugs or suggestions for `UI Catalog` as [`Terminal.Gui` Github Issues](https://github.com/migueldeicaza/gui.cs/issues) with "UICatalog: ". \ No newline at end of file diff --git a/UICatalog/Scenario.cs b/UICatalog/Scenario.cs index e491a4c8e..b43821a4e 100644 --- a/UICatalog/Scenario.cs +++ b/UICatalog/Scenario.cs @@ -17,7 +17,9 @@ namespace UICatalog { /// 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 { + public class Scenario : IDisposable { + private bool _disposedValue; + /// /// The Top level for the Scenario. This should be set to `Application.Top` in most cases. /// @@ -177,5 +179,25 @@ namespace UICatalog { } return objects; } + + protected virtual void Dispose (bool disposing) + { + if (!_disposedValue) { + if (disposing) { + // TODO: dispose managed state (managed objects) + } + + // TODO: free unmanaged resources (unmanaged objects) and override finalizer + // TODO: set large fields to null + _disposedValue = true; + } + } + + public void Dispose () + { + // Do not change this code. Put cleanup code in 'Dispose(bool disposing)' method + Dispose (disposing: true); + GC.SuppressFinalize (this); + } } } diff --git a/UICatalog/Scenarios/ComputedLayout.cs b/UICatalog/Scenarios/ComputedLayout.cs new file mode 100644 index 000000000..d66194f22 --- /dev/null +++ b/UICatalog/Scenarios/ComputedLayout.cs @@ -0,0 +1,155 @@ +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: "Computed Layout", Description: "Demonstrates using the Computed (Dim and Pos) Layout System")] + [ScenarioCategory ("Layout")] + class ComputedLayout : Scenario { + + public override void Setup () + { + //Top.LayoutStyle = LayoutStyle.Computed; + // Demonstrate using Dim to create a horizontal ruler that always measures the parent window's width + // BUGBUG: Dim.Fill returns too big a value sometimes. + const string rule = "|123456789"; + var horizontalRuler = new Label ("") { + 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.Resized += (sender, a) => { + horizontalRuler.Text = rule.Repeat ((int)Math.Ceiling ((double)(horizontalRuler.Bounds.Width) / (double)rule.Length)) [0..(horizontalRuler.Bounds.Width)]; + }; + + Win.Add (horizontalRuler); + + // Demonstrate using Dim to create a vertical ruler that always measures the parent window's height + // TODO: Either build a custom control for this or implement linewrap in Label #352 + //var verticalRuler = new Label ("") { + // X = 0, + // Y = 0, + // Width = 1, + // Height = Dim.Fill (), + // ColorScheme = Colors.Error + //}; + + //Application.OnResized += () => { + // verticalRuler.Text = rule.Repeat ((int)Math.Ceiling ((double)(verticalRuler.Bounds.Height) / (double)rule.Length)) [0..(verticalRuler.Bounds.Height)]; + //}; + + //Win.Add (verticalRuler); + + + // Demonstrate using Dim to create a window that fills the parent with a margin + int margin = 10; + var subWin = new Window ($"Centered Sub Window with {margin} character margin") { + X = Pos.Center(), + Y = 2, + Width = Dim.Fill (margin), + Height = 7 + }; + Win.Add (subWin); + + int i = 1; + string txt = "Resize the terminal to see computed layout in action."; + var labelList = new List