diff --git a/.github/ISSUE_TEMPLATE/bug_report.md b/.github/ISSUE_TEMPLATE/bug_report.md
index dd84ea7824..2eda8e0510 100644
--- a/.github/ISSUE_TEMPLATE/bug_report.md
+++ b/.github/ISSUE_TEMPLATE/bug_report.md
@@ -2,7 +2,7 @@
name: Bug report
about: Create a report to help us improve
title: ''
-labels: ''
+labels: bug
assignees: ''
---
@@ -36,3 +36,6 @@ If applicable, add screenshots to help explain your problem.
**Additional context**
Add any other context about the problem here.
+
+**Set Project & Milestone**
+If you have access, please don't forget to set the right Project and Milestone.
diff --git a/.github/workflows/dotnet-core.yml b/.github/workflows/dotnet-core.yml
index 5779ed9da6..d22eefcfbb 100644
--- a/.github/workflows/dotnet-core.yml
+++ b/.github/workflows/dotnet-core.yml
@@ -90,7 +90,13 @@ jobs:
dotnet-version: 8.x
dotnet-quality: 'ga'
- - name: Build Release
+ - name: Build Release Terminal.Gui
+ run: dotnet build Terminal.Gui/Terminal.Gui.csproj --configuration Release
+
+ - name: Pack Release Terminal.Gui
+ run: dotnet pack Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ./local_packages
+
+ - name: Build Release Solution
run: dotnet build --configuration Release
diff --git a/.github/workflows/publish.yml b/.github/workflows/publish.yml
index d968715b5a..a484cdcfd8 100644
--- a/.github/workflows/publish.yml
+++ b/.github/workflows/publish.yml
@@ -43,11 +43,11 @@ jobs:
- name: Build Release
run: |
dotnet-gitversion /updateprojectfiles
- dotnet build --no-incremental --nologo --force --configuration Release
- dotnet test --configuration Release
+ dotnet build Terminal.Gui/Terminal.Gui.csproj --no-incremental --nologo --force --configuration Release
+ dotnet test Terminal.Gui/Terminal.Gui.csproj --configuration Release
- name: Pack
- run: dotnet pack -c Release --include-symbols -p:Version='${{ steps.gitversion.outputs.SemVer }}'
+ run: dotnet pack Terminal.Gui/Terminal.Gui.csproj -c Release --include-symbols -p:Version='${{ steps.gitversion.outputs.SemVer }}'
# - name: Test to generate Code Coverage Report
# run: |
diff --git a/.gitignore b/.gitignore
index cca1f48017..1a111a2ca5 100644
--- a/.gitignore
+++ b/.gitignore
@@ -61,3 +61,4 @@ demo.*
*.tui/
*.dotCover
+/local_packages/
diff --git a/CommunityToolkitExample/LoginView.cs b/CommunityToolkitExample/LoginView.cs
index b0d891fa94..8d66f29bbf 100644
--- a/CommunityToolkitExample/LoginView.cs
+++ b/CommunityToolkitExample/LoginView.cs
@@ -19,13 +19,13 @@ public LoginView (LoginViewModel viewModel)
{
ViewModel.Password = passwordInput.Text;
};
- loginButton.Accept += (_, _) =>
+ loginButton.Accepting += (_, _) =>
{
if (!ViewModel.CanLogin) { return; }
ViewModel.LoginCommand.Execute (null);
};
- clearButton.Accept += (_, _) =>
+ clearButton.Accepting += (_, _) =>
{
ViewModel.ClearCommand.Execute (null);
};
diff --git a/Example/Example.cs b/Example/Example.cs
index 13c7b9138b..39126f4d92 100644
--- a/Example/Example.cs
+++ b/Example/Example.cs
@@ -63,7 +63,7 @@ public ExampleWindow ()
};
// When login button is clicked display a message popup
- btnLogin.Accept += (s, e) =>
+ btnLogin.Accepting += (s, e) =>
{
if (userNameText.Text == "admin" && passwordText.Text == "password")
{
diff --git a/NativeAot/NativeAot.csproj b/NativeAot/NativeAot.csproj
index 20721a952c..4f5238a9d1 100644
--- a/NativeAot/NativeAot.csproj
+++ b/NativeAot/NativeAot.csproj
@@ -15,7 +15,7 @@
-
+
diff --git a/NativeAot/PackTerminalGui.ps1 b/NativeAot/PackTerminalGui.ps1
new file mode 100644
index 0000000000..ea6bff0540
--- /dev/null
+++ b/NativeAot/PackTerminalGui.ps1
@@ -0,0 +1,9 @@
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore NativeAot with the new package
+dotnet restore ./NativeAot.csproj --source ./local_packages
+
+# Step 3: Build NativeAot
+dotnet build ./NativeAot.csproj --configuration Release
diff --git a/NativeAot/PackTerminalGui.sh b/NativeAot/PackTerminalGui.sh
new file mode 100644
index 0000000000..ab0cc7e171
--- /dev/null
+++ b/NativeAot/PackTerminalGui.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore NativeAot with the new package
+dotnet restore ./NativeAot.csproj --source ./local_packages
+
+# Step 3: Build NativeAot
+dotnet build ./NativeAot.csproj --configuration Release
diff --git a/NativeAot/Program.cs b/NativeAot/Program.cs
index 4aae3c0997..bb0f38bc0d 100644
--- a/NativeAot/Program.cs
+++ b/NativeAot/Program.cs
@@ -93,7 +93,7 @@ public ExampleWindow ()
};
// When login button is clicked display a message popup
- btnLogin.Accept += (s, e) =>
+ btnLogin.Accepting += (s, e) =>
{
if (userNameText.Text == "admin" && passwordText.Text == "password")
{
diff --git a/NativeAot/Properties/launchSettings.json b/NativeAot/Properties/launchSettings.json
index a66f387c46..a7872f0012 100644
--- a/NativeAot/Properties/launchSettings.json
+++ b/NativeAot/Properties/launchSettings.json
@@ -3,7 +3,7 @@
"NativeAot": {
"commandName": "Project"
},
- "WSL : UICatalog": {
+ "WSL : NativeAot": {
"commandName": "Executable",
"executablePath": "wsl",
"commandLineArgs": "dotnet NativeAot.dll",
diff --git a/README.md b/README.md
index 5b793db2fe..f35c7c6666 100644
--- a/README.md
+++ b/README.md
@@ -8,7 +8,7 @@
* The current, stable, release of Terminal.Gui v1 is [![Version](https://img.shields.io/nuget/v/Terminal.Gui.svg)](https://www.nuget.org/packages/Terminal.Gui).
* The current `prealpha` release of Terminal.Gui v2 can be found on [Nuget](https://www.nuget.org/packages/Terminal.Gui).
-* Developers starting new TUI projects are encouraged to target `v2`. The API is signifcantly changed, and significantly improved. There will be breaking changes in the API before Beta, but the core API is stable.
+* Developers starting new TUI projects are encouraged to target `v2`. The API is significantly changed, and significantly improved. There will be breaking changes in the API before Beta, but the core API is stable.
* `v1` is in maintenance mode and we will only accept PRs for issues impacting existing functionality.
**Terminal.Gui**: A toolkit for building rich console apps for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix.
@@ -35,9 +35,9 @@ dotnet run
* [API Documentation](https://gui-cs.github.io/Terminal.GuiV2Docs/api/Terminal.Gui.html)
* [Documentation Home](https://gui-cs.github.io/Terminal.GuiV2Docs)
-The above documentation matches the most recent Nuget release from the `v2_develop` branch. Get the [v1 documentation here](This is the v2 API documentation. For v1 go here: https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.html)
+The above documentation matches the most recent Nuget release from the `v2_develop` branch. Get the [v1 documentation here](https://gui-cs.github.io/Terminal.Gui/api/Terminal.Gui.html).
-See the [`Terminal.Gui/` README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured.
+See the [`Terminal.Gui/`README](https://github.com/gui-cs/Terminal.Gui/tree/master/Terminal.Gui) for an overview of how the library is structured.
## Showcase & Examples
diff --git a/ReactiveExample/LoginView.cs b/ReactiveExample/LoginView.cs
index a69d38f845..130aebb43b 100644
--- a/ReactiveExample/LoginView.cs
+++ b/ReactiveExample/LoginView.cs
@@ -108,7 +108,7 @@ public LoginView (LoginViewModel viewModel)
login
.Events ()
- .Accept
+ .Accepting
.InvokeCommand (ViewModel, x => x.Login)
.DisposeWith (_disposable);
})
@@ -120,7 +120,7 @@ public LoginView (LoginViewModel viewModel)
clear
.Events ()
- .Accept
+ .Accepting
.InvokeCommand (ViewModel, x => x.ClearCommand)
.DisposeWith (_disposable);
})
diff --git a/SelfContained/PackTerminalGui.ps1 b/SelfContained/PackTerminalGui.ps1
new file mode 100644
index 0000000000..6e5cd41928
--- /dev/null
+++ b/SelfContained/PackTerminalGui.ps1
@@ -0,0 +1,9 @@
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore SelfContained with the new package
+dotnet restore ./SelfContained.csproj --source ./local_packages
+
+# Step 3: Build SelfContained
+dotnet build ./SelfContained.csproj --configuration Release
diff --git a/SelfContained/PackTerminalGui.sh b/SelfContained/PackTerminalGui.sh
new file mode 100644
index 0000000000..d2cba3e875
--- /dev/null
+++ b/SelfContained/PackTerminalGui.sh
@@ -0,0 +1,11 @@
+#!/bin/bash
+
+# Step 1: Build and pack Terminal.Gui
+dotnet build ../Terminal.Gui/Terminal.Gui.csproj --configuration Release
+dotnet pack ../Terminal.Gui/Terminal.Gui.csproj --configuration Release --output ../local_packages
+
+# Step 2: Restore SelfContained with the new package
+dotnet restore ./SelfContained.csproj --source ./local_packages
+
+# Step 3: Build SelfContained
+dotnet build ./SelfContained.csproj --configuration Release
diff --git a/SelfContained/Program.cs b/SelfContained/Program.cs
index 67d8f935c9..29b7f5cd9c 100644
--- a/SelfContained/Program.cs
+++ b/SelfContained/Program.cs
@@ -92,7 +92,7 @@ public ExampleWindow ()
};
// When login button is clicked display a message popup
- btnLogin.Accept += (s, e) =>
+ btnLogin.Accepting += (s, e) =>
{
if (userNameText.Text == "admin" && passwordText.Text == "password")
{
diff --git a/SelfContained/SelfContained.csproj b/SelfContained/SelfContained.csproj
index af651e3ca8..46800e09ce 100644
--- a/SelfContained/SelfContained.csproj
+++ b/SelfContained/SelfContained.csproj
@@ -18,7 +18,7 @@
-
+
diff --git a/Terminal.Gui/Application/Application.Driver.cs b/Terminal.Gui/Application/Application.Driver.cs
index f15bd80539..2abeb1337a 100644
--- a/Terminal.Gui/Application/Application.Driver.cs
+++ b/Terminal.Gui/Application/Application.Driver.cs
@@ -10,7 +10,7 @@ public static partial class Application // Driver abstractions
///
/// Gets or sets whether will be forced to output only the 16 colors defined in
- /// . The default is , meaning 24-bit (TrueColor) colors will be output
+ /// . The default is , meaning 24-bit (TrueColor) colors will be output
/// as long as the selected supports TrueColor.
///
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
diff --git a/Terminal.Gui/Application/Application.Initialization.cs b/Terminal.Gui/Application/Application.Initialization.cs
index d9b4529d02..4fb11621d6 100644
--- a/Terminal.Gui/Application/Application.Initialization.cs
+++ b/Terminal.Gui/Application/Application.Initialization.cs
@@ -1,4 +1,5 @@
#nullable enable
+using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;
@@ -71,7 +72,7 @@ internal static void InternalInit (
if (!calledViaRunT)
{
// Reset all class variables (Application is a singleton).
- ResetState ();
+ ResetState (ignoreDisposed: true);
}
Navigation = new ();
@@ -80,6 +81,16 @@ internal static void InternalInit (
if (driver is { })
{
Driver = driver;
+
+ if (driver is FakeDriver)
+ {
+ // We're running unit tests. Disable loading config files other than default
+ if (Locations == ConfigLocations.All)
+ {
+ Locations = ConfigLocations.DefaultOnly;
+ Reset ();
+ }
+ }
}
// Start the process of configuration management.
@@ -88,7 +99,12 @@ internal static void InternalInit (
// valid after a Driver is loaded. In this case we need just
// `Settings` so we can determine which driver to use.
// Don't reset, so we can inherit the theme from the previous run.
+ string previousTheme = Themes?.Theme ?? string.Empty;
Load ();
+ if (Themes is { } && !string.IsNullOrEmpty (previousTheme) && previousTheme != "Default")
+ {
+ ThemeManager.SelectedTheme = previousTheme;
+ }
Apply ();
AddApplicationKeyBindings ();
@@ -198,10 +214,16 @@ internal static void InternalInit (
public static void Shutdown ()
{
// TODO: Throw an exception if Init hasn't been called.
+
+ bool wasInitialized = IsInitialized;
ResetState ();
PrintJsonErrors ();
- bool init = IsInitialized;
- InitializedChanged?.Invoke (null, new (in init));
+
+ if (wasInitialized)
+ {
+ bool init = IsInitialized;
+ InitializedChanged?.Invoke (null, new (in init));
+ }
}
///
diff --git a/Terminal.Gui/Application/Application.Keyboard.cs b/Terminal.Gui/Application/Application.Keyboard.cs
index 259af8fba9..01545deddb 100644
--- a/Terminal.Gui/Application/Application.Keyboard.cs
+++ b/Terminal.Gui/Application/Application.Keyboard.cs
@@ -5,12 +5,10 @@ public static partial class Application // Keyboard handling
{
private static Key _nextTabGroupKey = Key.F6; // Resources/config.json overrrides
private static Key _nextTabKey = Key.Tab; // Resources/config.json overrrides
-
private static Key _prevTabGroupKey = Key.F6.WithShift; // Resources/config.json overrrides
-
private static Key _prevTabKey = Key.Tab.WithShift; // Resources/config.json overrrides
-
private static Key _quitKey = Key.Esc; // Resources/config.json overrrides
+ private static Key _arrangeKey = Key.F5.WithCtrl; // Resources/config.json overrrides
static Application () { AddApplicationKeyBindings (); }
@@ -97,7 +95,7 @@ public static bool OnKeyDown (Key keyEvent)
return true;
}
- if (Current is null)
+ if (Top is null)
{
foreach (Toplevel topLevel in TopLevels.ToList ())
{
@@ -114,7 +112,7 @@ public static bool OnKeyDown (Key keyEvent)
}
else
{
- if (Current.NewKeyDownEvent (keyEvent))
+ if (Top.NewKeyDownEvent (keyEvent))
{
return true;
}
@@ -155,7 +153,7 @@ public static bool OnKeyDown (Key keyEvent)
}
///
- /// INTENRAL method to invoke one of the commands in
+ /// INTENRAL method to invoke one of the commands in
///
///
///
@@ -171,9 +169,10 @@ public static bool OnKeyDown (Key keyEvent)
);
}
- if (CommandImplementations.TryGetValue (command, out Func? implementation))
+ if (CommandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
{
var context = new CommandContext (command, keyEvent, appBinding); // Create the context here
+
return implementation (context);
}
@@ -262,24 +261,31 @@ public static Key QuitKey
}
}
+ /// Gets or sets the key to activate arranging views using the keyboard.
+ [SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
+ public static Key ArrangeKey
+ {
+ get => _arrangeKey;
+ set
+ {
+ if (_arrangeKey != value)
+ {
+ ReplaceKey (_arrangeKey, value);
+ _arrangeKey = value;
+ }
+ }
+ }
+
internal static void AddApplicationKeyBindings ()
{
CommandImplementations = new ();
// Things this view knows how to do
AddCommand (
- Command.QuitToplevel, // TODO: IRunnable: Rename to Command.Quit to make more generic.
+ Command.Quit,
static () =>
{
- if (ApplicationOverlapped.OverlappedTop is { })
- {
- RequestStop (Current!);
- }
- else
- {
- RequestStop ();
- }
-
+ RequestStop ();
return true;
}
);
@@ -295,54 +301,50 @@ internal static void AddApplicationKeyBindings ()
);
AddCommand (
- Command.NextView,
+ Command.NextTabStop,
static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop));
AddCommand (
- Command.PreviousView,
+ Command.PreviousTabStop,
static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop));
AddCommand (
- Command.NextViewOrTop,
- static () =>
- {
- // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491
- if (ApplicationOverlapped.OverlappedTop is { })
- {
- ApplicationOverlapped.OverlappedMoveNext ();
-
- return true;
- }
+ Command.NextTabGroup,
+ static () => Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup));
- return Navigation?.AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabGroup);
- }
- );
+ AddCommand (
+ Command.PreviousTabGroup,
+ static () => Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup));
AddCommand (
- Command.PreviousViewOrTop,
+ Command.Refresh,
static () =>
{
- // TODO: This OverlapppedTop tomfoolery goes away in addressing #2491
- if (ApplicationOverlapped.OverlappedTop is { })
- {
- ApplicationOverlapped.OverlappedMovePrevious ();
-
- return true;
- }
+ Refresh ();
- return Navigation?.AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabGroup);
+ return true;
}
);
AddCommand (
- Command.Refresh,
+ Command.Edit,
static () =>
{
- Refresh ();
+ View? viewToArrange = Navigation?.GetFocused ();
- return true;
- }
- );
+ // Go up the superview hierarchy and find the first that is not ViewArrangement.Fixed
+ while (viewToArrange is { SuperView: { }, Arrangement: ViewArrangement.Fixed })
+ {
+ viewToArrange = viewToArrange.SuperView;
+ }
+
+ if (viewToArrange is { })
+ {
+ return viewToArrange.Border?.EnterArrangeMode (ViewArrangement.Fixed);
+ }
+
+ return false;
+ });
KeyBindings.Clear ();
@@ -352,18 +354,21 @@ internal static void AddApplicationKeyBindings ()
NextTabGroupKey = Key.F6;
PrevTabGroupKey = Key.F6.WithShift;
QuitKey = Key.Esc;
+ ArrangeKey = Key.F5.WithCtrl;
+
+ KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.Quit);
- KeyBindings.Add (QuitKey, KeyBindingScope.Application, Command.QuitToplevel);
+ KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextTabStop);
+ KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextTabStop);
+ KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousTabStop);
+ KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousTabStop);
+ KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextTabStop);
+ KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousTabStop);
- KeyBindings.Add (Key.CursorRight, KeyBindingScope.Application, Command.NextView);
- KeyBindings.Add (Key.CursorDown, KeyBindingScope.Application, Command.NextView);
- KeyBindings.Add (Key.CursorLeft, KeyBindingScope.Application, Command.PreviousView);
- KeyBindings.Add (Key.CursorUp, KeyBindingScope.Application, Command.PreviousView);
- KeyBindings.Add (NextTabKey, KeyBindingScope.Application, Command.NextView);
- KeyBindings.Add (PrevTabKey, KeyBindingScope.Application, Command.PreviousView);
+ KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextTabGroup);
+ KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousTabGroup);
- KeyBindings.Add (NextTabGroupKey, KeyBindingScope.Application, Command.NextViewOrTop);
- KeyBindings.Add (PrevTabGroupKey, KeyBindingScope.Application, Command.PreviousViewOrTop);
+ KeyBindings.Add (ArrangeKey, KeyBindingScope.Application, Command.Edit);
// TODO: Refresh Key should be configurable
KeyBindings.Add (Key.F5, KeyBindingScope.Application, Command.Refresh);
@@ -413,7 +418,7 @@ internal static List GetViewKeyBindings ()
///
/// Commands for Application.
///
- private static Dictionary>? CommandImplementations { get; set; }
+ private static Dictionary? CommandImplementations { get; set; }
private static void ReplaceKey (Key oldKey, Key newKey)
{
diff --git a/Terminal.Gui/Application/Application.Mouse.cs b/Terminal.Gui/Application/Application.Mouse.cs
index 2b16686d3d..ab1cf428dc 100644
--- a/Terminal.Gui/Application/Application.Mouse.cs
+++ b/Terminal.Gui/Application/Application.Mouse.cs
@@ -1,9 +1,17 @@
#nullable enable
+using System.ComponentModel;
+using System.Diagnostics;
+
namespace Terminal.Gui;
public static partial class Application // Mouse handling
{
- #region Mouse handling
+ internal static Point? _lastMousePosition;
+
+ ///
+ /// Gets the most recent position of the mouse.
+ ///
+ public static Point? GetLastMousePosition () { return _lastMousePosition; }
/// Disable or enable the mouse. The mouse is enabled by default.
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
@@ -116,9 +124,6 @@ private static void OnUnGrabbedMouse (View? view)
UnGrabbedMouse?.Invoke (view, new (view));
}
- // Used by OnMouseEvent to track the last view that was clicked on.
- internal static View? MouseEnteredView { get; set; }
-
/// Event fired when a mouse move or click occurs. Coordinates are screen relative.
///
///
@@ -129,27 +134,35 @@ private static void OnUnGrabbedMouse (View? view)
///
public static event EventHandler? MouseEvent;
- /// Called when a mouse event occurs. Raises the event.
+ /// Called when a mouse event is raised by the driver.
/// This method can be used to simulate a mouse event, e.g. in unit tests.
/// The mouse event with coordinates relative to the screen.
internal static void OnMouseEvent (MouseEvent mouseEvent)
{
+ _lastMousePosition = mouseEvent.ScreenPosition;
+
if (IsMouseDisabled)
{
return;
}
- var view = View.FindDeepestView (Current, mouseEvent.Position);
+ // The position of the mouse is the same as the screen position at the application level.
+ //Debug.Assert (mouseEvent.Position == mouseEvent.ScreenPosition);
+ mouseEvent.Position = mouseEvent.ScreenPosition;
+
+ List currentViewsUnderMouse = View.GetViewsUnderMouse (mouseEvent.ScreenPosition);
+
+ View? deepestViewUnderMouse = currentViewsUnderMouse.LastOrDefault ();
- if (view is { })
+ if (deepestViewUnderMouse is { })
{
#if DEBUG_IDISPOSABLE
- if (view.WasDisposed)
+ if (deepestViewUnderMouse.WasDisposed)
{
- throw new ObjectDisposedException (view.GetType ().FullName);
+ throw new ObjectDisposedException (deepestViewUnderMouse.GetType ().FullName);
}
#endif
- mouseEvent.View = view;
+ mouseEvent.View = deepestViewUnderMouse;
}
MouseEvent?.Invoke (null, mouseEvent);
@@ -159,160 +172,198 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
return;
}
- if (MouseGrabView is { })
+ if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
{
-
-#if DEBUG_IDISPOSABLE
- if (MouseGrabView.WasDisposed)
- {
- throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
- }
-#endif
- // If the mouse is grabbed, send the event to the view that grabbed it.
- // The coordinates are relative to the Bounds of the view that grabbed the mouse.
- Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.Position);
-
- var viewRelativeMouseEvent = new MouseEvent
- {
- Position = frameLoc,
- Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = view ?? MouseGrabView
- };
-
- if ((MouseGrabView.Viewport with { Location = Point.Empty }).Contains (viewRelativeMouseEvent.Position) is false)
- {
- // The mouse has moved outside the bounds of the view that grabbed the mouse
- MouseGrabView.NewMouseLeaveEvent (mouseEvent);
- }
-
- //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
- if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
- {
- return;
- }
-
- // ReSharper disable once ConditionIsAlwaysTrueOrFalse
- if (MouseGrabView is null && view is Adornment)
- {
- // The view that grabbed the mouse has been disposed
- return;
- }
+ return;
}
// We can combine this into the switch expression to reduce cognitive complexity even more and likely
// avoid one or two of these checks in the process, as well.
- WantContinuousButtonPressedView = view switch
- {
- { WantContinuousButtonPressed: true } => view,
- _ => null
- };
-
- if (view is not Adornment
- && (view is null || view == ApplicationOverlapped.OverlappedTop)
- && Current is { Modal: false }
- && ApplicationOverlapped.OverlappedTop != null
- && mouseEvent.Flags is not MouseFlags.ReportMousePosition and not 0)
- {
- // This occurs when there are multiple overlapped "tops"
- // E.g. "Mdi" - in the Background Worker Scenario
- View? top = ApplicationOverlapped.FindDeepestTop (Top!, mouseEvent.Position);
- view = View.FindDeepestView (top, mouseEvent.Position);
- if (view is { } && view != ApplicationOverlapped.OverlappedTop && top != Current && top is { })
- {
- ApplicationOverlapped.MoveCurrent ((Toplevel)top);
- }
- }
+ WantContinuousButtonPressedView = deepestViewUnderMouse switch
+ {
+ { WantContinuousButtonPressed: true } => deepestViewUnderMouse,
+ _ => null
+ };
// May be null before the prior condition or the condition may set it as null.
// So, the checking must be outside the prior condition.
- if (view is null)
+ if (deepestViewUnderMouse is null)
{
return;
}
- MouseEvent? me;
+ // Create a view-relative mouse event to send to the view that is under the mouse.
+ MouseEvent? viewMouseEvent;
- if (view is Adornment adornment)
+ if (deepestViewUnderMouse is Adornment adornment)
{
- Point frameLoc = adornment.ScreenToFrame (mouseEvent.Position);
+ Point frameLoc = adornment.ScreenToFrame (mouseEvent.ScreenPosition);
- me = new ()
+ viewMouseEvent = new ()
{
Position = frameLoc,
Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = view
+ ScreenPosition = mouseEvent.ScreenPosition,
+ View = deepestViewUnderMouse
};
}
- else if (view.ViewportToScreen (Rectangle.Empty with { Size = view.Viewport.Size }).Contains (mouseEvent.Position))
+ else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.Position))
{
- Point viewportLocation = view.ScreenToViewport (mouseEvent.Position);
+ Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
- me = new ()
+ viewMouseEvent = new ()
{
Position = viewportLocation,
Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = view
+ ScreenPosition = mouseEvent.ScreenPosition,
+ View = deepestViewUnderMouse
};
}
else
{
- return;
- }
+ // The mouse was outside any View's Viewport.
- if (MouseEnteredView is null)
- {
- MouseEnteredView = view;
- view.NewMouseEnterEvent (me);
- }
- else if (MouseEnteredView != view)
- {
- MouseEnteredView.NewMouseLeaveEvent (me);
- view.NewMouseEnterEvent (me);
- MouseEnteredView = view;
- }
+ // Debug.Fail ("This should never happen. If it does please file an Issue!!");
- if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
- {
return;
}
- WantContinuousButtonPressedView = view.WantContinuousButtonPressed ? view : null;
+ RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);
- //Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
+ WantContinuousButtonPressedView = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null;
- while (view.NewMouseEvent (me) is not true && MouseGrabView is not { })
+ while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { })
{
- if (view is Adornment adornmentView)
+ if (deepestViewUnderMouse is Adornment adornmentView)
{
- view = adornmentView.Parent!.SuperView;
+ deepestViewUnderMouse = adornmentView.Parent!.SuperView;
}
else
{
- view = view.SuperView;
+ deepestViewUnderMouse = deepestViewUnderMouse.SuperView;
}
- if (view is null)
+ if (deepestViewUnderMouse is null)
{
break;
}
- Point boundsPoint = view.ScreenToViewport (mouseEvent.Position);
+ Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);
- me = new ()
+ viewMouseEvent = new ()
{
Position = boundsPoint,
Flags = mouseEvent.Flags,
- ScreenPosition = mouseEvent.Position,
- View = view
+ ScreenPosition = mouseEvent.ScreenPosition,
+ View = deepestViewUnderMouse
};
}
+ }
- ApplicationOverlapped.BringOverlappedTopToFront ();
+ internal static bool HandleMouseGrab (View? deepestViewUnderMouse, MouseEvent mouseEvent)
+ {
+ if (MouseGrabView is { })
+ {
+#if DEBUG_IDISPOSABLE
+ if (MouseGrabView.WasDisposed)
+ {
+ throw new ObjectDisposedException (MouseGrabView.GetType ().FullName);
+ }
+#endif
+
+ // If the mouse is grabbed, send the event to the view that grabbed it.
+ // The coordinates are relative to the Bounds of the view that grabbed the mouse.
+ Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);
+
+ var viewRelativeMouseEvent = new MouseEvent
+ {
+ Position = frameLoc,
+ Flags = mouseEvent.Flags,
+ ScreenPosition = mouseEvent.ScreenPosition,
+ View = deepestViewUnderMouse ?? MouseGrabView
+ };
+
+ //System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
+ if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) is true)
+ {
+ return true;
+ }
+
+ // ReSharper disable once ConditionIsAlwaysTrueOrFalse
+ if (MouseGrabView is null && deepestViewUnderMouse is Adornment)
+ {
+ // The view that grabbed the mouse has been disposed
+ return true;
+ }
+ }
+
+ return false;
}
- #endregion Mouse handling
+ internal static readonly List _cachedViewsUnderMouse = new ();
+
+ // TODO: Refactor MouseEnter/LeaveEvents to not take MouseEvent param.
+ ///
+ /// INTERNAL: Raises the MouseEnter and MouseLeave events for the views that are under the mouse.
+ ///
+ /// The position of the mouse.
+ /// The most recent result from GetViewsUnderMouse().
+ internal static void RaiseMouseEnterLeaveEvents (Point screenPosition, List currentViewsUnderMouse)
+ {
+ // Tell any views that are no longer under the mouse that the mouse has left
+ List viewsToLeave = _cachedViewsUnderMouse.Where (v => v is { } && !currentViewsUnderMouse.Contains (v)).ToList ();
+
+ foreach (View? view in viewsToLeave)
+ {
+ if (view is null)
+ {
+ continue;
+ }
+
+ view.NewMouseLeaveEvent ();
+ _cachedViewsUnderMouse.Remove (view);
+ }
+
+ // Tell any views that are now under the mouse that the mouse has entered and add them to the list
+ foreach (View? view in currentViewsUnderMouse)
+ {
+ if (view is null)
+ {
+ continue;
+ }
+
+ if (_cachedViewsUnderMouse.Contains (view))
+ {
+ continue;
+ }
+
+ _cachedViewsUnderMouse.Add (view);
+ var raise = false;
+
+ if (view is Adornment { Parent: { } } adornmentView)
+ {
+ Point superViewLoc = adornmentView.Parent.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
+ raise = adornmentView.Contains (superViewLoc);
+ }
+ else
+ {
+ Point superViewLoc = view.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
+ raise = view.Contains (superViewLoc);
+ }
+
+ if (!raise)
+ {
+ continue;
+ }
+
+ CancelEventArgs eventArgs = new ();
+ bool? cancelled = view.NewMouseEnterEvent (eventArgs);
+
+ if (cancelled is true || eventArgs.Cancel)
+ {
+ break;
+ }
+ }
+ }
}
diff --git a/Terminal.Gui/Application/Application.Run.cs b/Terminal.Gui/Application/Application.Run.cs
index 5099788fd8..4a1d89541f 100644
--- a/Terminal.Gui/Application/Application.Run.cs
+++ b/Terminal.Gui/Application/Application.Run.cs
@@ -54,11 +54,6 @@ public static RunState Begin (Toplevel toplevel)
}
#endif
- if (toplevel.IsOverlappedContainer && ApplicationOverlapped.OverlappedTop != toplevel && ApplicationOverlapped.OverlappedTop is { })
- {
- throw new InvalidOperationException ("Only one Overlapped Container is allowed.");
- }
-
// Ensure the mouse is ungrabbed.
MouseGrabView = null;
@@ -96,11 +91,6 @@ public static RunState Begin (Toplevel toplevel)
throw new ObjectDisposedException (Top.GetType ().FullName);
}
}
- else if (ApplicationOverlapped.OverlappedTop is { } && toplevel != Top && TopLevels.Contains (Top!))
- {
- // BUGBUG: Don't call OnLeave/OnEnter directly! Set HasFocus to false and let the system handle it.
- //Top!.OnLeave (toplevel);
- }
// BUGBUG: We should not depend on `Id` internally.
// BUGBUG: It is super unclear what this code does anyway.
@@ -135,79 +125,46 @@ public static RunState Begin (Toplevel toplevel)
}
}
- if (Top is null || toplevel.IsOverlappedContainer)
+ if (Top is null)
{
Top = toplevel;
}
- var refreshDriver = true;
-
- if (ApplicationOverlapped.OverlappedTop is null
- || toplevel.IsOverlappedContainer
- || (Current?.Modal == false && toplevel.Modal)
- || (Current?.Modal == false && !toplevel.Modal)
- || (Current?.Modal == true && toplevel.Modal))
+ if ((Top?.Modal == false && toplevel.Modal)
+ || (Top?.Modal == false && !toplevel.Modal)
+ || (Top?.Modal == true && toplevel.Modal))
{
if (toplevel.Visible)
{
- if (Current is { HasFocus: true })
+ if (Top is { HasFocus: true })
{
- Current.HasFocus = false;
+ Top.HasFocus = false;
}
- Current?.OnDeactivate (toplevel);
- Toplevel previousCurrent = Current!;
+ Top?.OnDeactivate (toplevel);
+ Toplevel previousCurrent = Top!;
- Current = toplevel;
- Current.OnActivate (previousCurrent);
-
- ApplicationOverlapped.SetCurrentOverlappedAsTop ();
+ Top = toplevel;
+ Top.OnActivate (previousCurrent);
}
- else
- {
- refreshDriver = false;
- }
- }
- else if ((toplevel != ApplicationOverlapped.OverlappedTop
- && Current?.Modal == true
- && !TopLevels.Peek ().Modal)
- || (toplevel != ApplicationOverlapped.OverlappedTop && Current?.Running == false))
- {
- refreshDriver = false;
- ApplicationOverlapped.MoveCurrent (toplevel);
- }
- else
- {
- refreshDriver = false;
- ApplicationOverlapped.MoveCurrent (Current!);
}
toplevel.SetRelativeLayout (Driver!.Screen.Size);
-
toplevel.LayoutSubviews ();
- toplevel.PositionToplevels ();
- // TODO: Should this use FindDeepestFocusableView instead?
// Try to set initial focus to any TabStop
if (!toplevel.HasFocus)
{
toplevel.SetFocus ();
}
- ApplicationOverlapped.BringOverlappedTopToFront ();
+ toplevel.OnLoaded ();
- if (refreshDriver)
- {
- ApplicationOverlapped.OverlappedTop?.OnChildLoaded (toplevel);
- toplevel.OnLoaded ();
- toplevel.SetNeedsDisplay ();
- toplevel.Draw ();
- Driver.UpdateScreen ();
+ Refresh ();
- if (PositionCursor (toplevel))
- {
- Driver.UpdateCursor ();
- }
+ if (PositionCursor ())
+ {
+ Driver.UpdateCursor ();
}
NotifyNewRunState?.Invoke (toplevel, new (rs));
@@ -216,35 +173,22 @@ public static RunState Begin (Toplevel toplevel)
}
///
- /// Calls on the most focused view in the view starting with .
+ /// Calls on the most focused view.
///
///
- /// Does nothing if is or if the most focused view is not visible or
- /// enabled.
+ /// Does nothing if there is no most focused view.
///
/// If the most focused view is not visible within it's superview, the cursor will be hidden.
///
///
/// if a view positioned the cursor and the position is visible.
- internal static bool PositionCursor (View view)
+ internal static bool PositionCursor ()
{
// Find the most focused view and position the cursor there.
- View? mostFocused = view?.MostFocused;
-
- if (mostFocused is null)
- {
- if (view is { HasFocus: true })
- {
- mostFocused = view;
- }
- else
- {
- return false;
- }
- }
+ View? mostFocused = Navigation?.GetFocused ();
// If the view is not visible or enabled, don't position the cursor
- if (!mostFocused.Visible || !mostFocused.Enabled)
+ if (mostFocused is null || !mostFocused.Visible || !mostFocused.Enabled)
{
Driver!.GetCursorVisibility (out CursorVisibility current);
@@ -510,20 +454,17 @@ public static void Invoke (Action action)
/// Triggers a refresh of the entire display.
public static void Refresh ()
{
- // TODO: Figure out how to remove this call to ClearContents. Refresh should just repaint damaged areas, not clear
- Driver!.ClearContents ();
-
- foreach (Toplevel v in TopLevels.Reverse ())
+ foreach (Toplevel tl in TopLevels.Reverse ())
{
- if (v.Visible)
+ if (tl.LayoutNeeded)
{
- v.SetNeedsDisplay ();
- v.SetSubViewNeedsDisplay ();
- v.Draw ();
+ tl.LayoutSubviews ();
}
+
+ tl.Draw ();
}
- Driver.Refresh ();
+ Driver!.Refresh ();
}
/// This event is raised on each iteration of the main loop.
@@ -586,80 +527,22 @@ public static void RunIteration (ref RunState state, ref bool firstIteration)
MainLoop.RunIteration ();
Iteration?.Invoke (null, new ());
- EnsureModalOrVisibleAlwaysOnTop (state.Toplevel);
-
- // TODO: Overlapped - Move elsewhere
- if (state.Toplevel != Current)
- {
- ApplicationOverlapped.OverlappedTop?.OnDeactivate (state.Toplevel);
- state.Toplevel = Current;
- ApplicationOverlapped.OverlappedTop?.OnActivate (state.Toplevel!);
- Top!.SetSubViewNeedsDisplay ();
- Refresh ();
- }
}
firstIteration = false;
- if (Current == null)
+ if (Top is null)
{
return;
}
- if (state.Toplevel != Top && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
- {
- state.Toplevel!.SetNeedsDisplay (state.Toplevel.Frame);
- Top.Draw ();
+ Refresh ();
- foreach (Toplevel top in TopLevels.Reverse ())
- {
- if (top != Top && top != state.Toplevel)
- {
- top.SetNeedsDisplay ();
- top.SetSubViewNeedsDisplay ();
- top.Draw ();
- }
- }
- }
-
- if (TopLevels.Count == 1
- && state.Toplevel == Top
- && (Driver!.Cols != state.Toplevel!.Frame.Width
- || Driver!.Rows != state.Toplevel.Frame.Height)
- && (state.Toplevel.NeedsDisplay
- || state.Toplevel.SubViewNeedsDisplay
- || state.Toplevel.LayoutNeeded))
- {
- Driver.ClearContents ();
- }
-
- if (state.Toplevel!.NeedsDisplay || state.Toplevel.SubViewNeedsDisplay || state.Toplevel.LayoutNeeded || ApplicationOverlapped.OverlappedChildNeedsDisplay ())
- {
- state.Toplevel.SetNeedsDisplay ();
- state.Toplevel.Draw ();
- Driver!.UpdateScreen ();
-
- //Driver.UpdateCursor ();
- }
-
- if (PositionCursor (state.Toplevel))
+ if (PositionCursor ())
{
Driver!.UpdateCursor ();
}
- // else
- {
- //if (PositionCursor (state.Toplevel))
- //{
- // Driver.Refresh ();
- //}
- //Driver.UpdateCursor ();
- }
-
- if (state.Toplevel != Top && !state.Toplevel.Modal && (Top!.NeedsDisplay || Top.SubViewNeedsDisplay || Top.LayoutNeeded))
- {
- Top.Draw ();
- }
}
/// Stops the provided , causing or the if provided.
@@ -673,112 +556,26 @@ public static void RunIteration (ref RunState state, ref bool firstIteration)
///
public static void RequestStop (Toplevel? top = null)
{
- if (ApplicationOverlapped.OverlappedTop is null || top is null)
+ if (top is null)
{
- top = Current;
+ top = Top;
}
- if (ApplicationOverlapped.OverlappedTop != null
- && top!.IsOverlappedContainer
- && top?.Running == true
- && (Current?.Modal == false || Current is { Modal: true, Running: false }))
+ if (!top!.Running)
{
- ApplicationOverlapped.OverlappedTop.RequestStop ();
+ return;
}
- else if (ApplicationOverlapped.OverlappedTop != null
- && top != Current
- && Current is { Running: true, Modal: true }
- && top!.Modal
- && top.Running)
- {
- var ev = new ToplevelClosingEventArgs (Current);
- Current.OnClosing (ev);
-
- if (ev.Cancel)
- {
- return;
- }
- ev = new (top);
- top.OnClosing (ev);
+ var ev = new ToplevelClosingEventArgs (top);
+ top.OnClosing (ev);
- if (ev.Cancel)
- {
- return;
- }
-
- Current.Running = false;
- OnNotifyStopRunState (Current);
- top.Running = false;
- OnNotifyStopRunState (top);
- }
- else if ((ApplicationOverlapped.OverlappedTop != null
- && top != ApplicationOverlapped.OverlappedTop
- && top != Current
- && Current is { Modal: false, Running: true }
- && !top!.Running)
- || (ApplicationOverlapped.OverlappedTop != null
- && top != ApplicationOverlapped.OverlappedTop
- && top != Current
- && Current is { Modal: false, Running: false }
- && !top!.Running
- && TopLevels.ToArray () [1].Running))
- {
- ApplicationOverlapped.MoveCurrent (top);
- }
- else if (ApplicationOverlapped.OverlappedTop != null
- && Current != top
- && Current?.Running == true
- && !top!.Running
- && Current?.Modal == true
- && top.Modal)
+ if (ev.Cancel)
{
- // The Current and the top are both modal so needed to set the Current.Running to false too.
- Current.Running = false;
- OnNotifyStopRunState (Current);
- }
- else if (ApplicationOverlapped.OverlappedTop != null
- && Current == top
- && ApplicationOverlapped.OverlappedTop?.Running == true
- && Current?.Running == true
- && top!.Running
- && Current?.Modal == true
- && top!.Modal)
- {
- // The OverlappedTop was requested to stop inside a modal Toplevel which is the Current and top,
- // both are the same, so needed to set the Current.Running to false too.
- Current.Running = false;
- OnNotifyStopRunState (Current);
+ return;
}
- else
- {
- Toplevel currentTop;
-
- if (top == Current || (Current?.Modal == true && !top!.Modal))
- {
- currentTop = Current!;
- }
- else
- {
- currentTop = top!;
- }
-
- if (!currentTop.Running)
- {
- return;
- }
-
- var ev = new ToplevelClosingEventArgs (currentTop);
- currentTop.OnClosing (ev);
- if (ev.Cancel)
- {
- return;
- }
-
- currentTop.Running = false;
- OnNotifyStopRunState (currentTop);
- }
+ top.Running = false;
+ OnNotifyStopRunState (top);
}
private static void OnNotifyStopRunState (Toplevel top)
@@ -798,14 +595,7 @@ public static void End (RunState runState)
{
ArgumentNullException.ThrowIfNull (runState);
- if (ApplicationOverlapped.OverlappedTop is { })
- {
- ApplicationOverlapped.OverlappedTop.OnChildUnloaded (runState.Toplevel);
- }
- else
- {
- runState.Toplevel.OnUnloaded ();
- }
+ runState.Toplevel.OnUnloaded ();
// End the RunState.Toplevel
// First, take it off the Toplevel Stack
@@ -824,66 +614,27 @@ public static void End (RunState runState)
// Notify that it is closing
runState.Toplevel?.OnClosed (runState.Toplevel);
- // If there is a OverlappedTop that is not the RunState.Toplevel then RunState.Toplevel
- // is a child of MidTop, and we should notify the OverlappedTop that it is closing
- if (ApplicationOverlapped.OverlappedTop is { } && !runState.Toplevel!.Modal && runState.Toplevel != ApplicationOverlapped.OverlappedTop)
+ if (TopLevels.Count > 0)
{
- ApplicationOverlapped.OverlappedTop.OnChildClosed (runState.Toplevel);
+ Top = TopLevels.Peek ();
+ Top.SetNeedsDisplay ();
}
- // Set Current and Top to the next TopLevel on the stack
- if (TopLevels.Count == 0)
+ if (runState.Toplevel is { HasFocus: true })
{
- if (Current is { HasFocus: true })
- {
- Current.HasFocus = false;
- }
- Current = null;
+ runState.Toplevel.HasFocus = false;
}
- else
- {
- if (TopLevels.Count > 1 && TopLevels.Peek () == ApplicationOverlapped.OverlappedTop && ApplicationOverlapped.OverlappedChildren?.Any (t => t.Visible) != null)
- {
- ApplicationOverlapped.OverlappedMoveNext ();
- }
- Current = TopLevels.Peek ();
-
- if (TopLevels.Count == 1 && Current == ApplicationOverlapped.OverlappedTop)
- {
- ApplicationOverlapped.OverlappedTop.OnAllChildClosed ();
- }
- else
- {
- ApplicationOverlapped.SetCurrentOverlappedAsTop ();
- // BUGBUG: We should not call OnEnter/OnLeave directly; they should only be called by SetHasFocus
- if (runState.Toplevel is { HasFocus: true })
- {
- runState.Toplevel.HasFocus = false;
- }
-
- if (Current is { HasFocus: false })
- {
- Current.SetFocus ();
- }
- }
-
- Refresh ();
- }
-
- // Don't dispose runState.Toplevel. It's up to caller dispose it
- // If it's not the same as the current in the RunIteration,
- // it will be fixed later in the next RunIteration.
- if (ApplicationOverlapped.OverlappedTop is { } && !TopLevels.Contains (ApplicationOverlapped.OverlappedTop))
+ if (Top is { HasFocus: false })
{
- _cachedRunStateToplevel = ApplicationOverlapped.OverlappedTop;
- }
- else
- {
- _cachedRunStateToplevel = runState.Toplevel;
+ Top.SetFocus ();
}
+ _cachedRunStateToplevel = runState.Toplevel;
+
runState.Toplevel = null;
runState.Dispose ();
+
+ Refresh ();
}
}
diff --git a/Terminal.Gui/Application/Application.Screen.cs b/Terminal.Gui/Application/Application.Screen.cs
index a3d56da067..087021d1c5 100644
--- a/Terminal.Gui/Application/Application.Screen.cs
+++ b/Terminal.Gui/Application/Application.Screen.cs
@@ -37,13 +37,7 @@ public static bool OnSizeChanging (SizeChangedEventArgs args)
{
t.SetRelativeLayout (args.Size.Value);
t.LayoutSubviews ();
- t.PositionToplevels ();
t.OnSizeChanging (new (args.Size));
-
- if (PositionCursor (t))
- {
- Driver?.UpdateCursor ();
- }
}
Refresh ();
diff --git a/Terminal.Gui/Application/Application.Toplevel.cs b/Terminal.Gui/Application/Application.Toplevel.cs
index a1844b2d06..3403d1eda7 100644
--- a/Terminal.Gui/Application/Application.Toplevel.cs
+++ b/Terminal.Gui/Application/Application.Toplevel.cs
@@ -8,49 +8,7 @@ public static partial class Application // Toplevel handling
/// Holds the stack of TopLevel views.
internal static Stack TopLevels { get; } = new ();
- /// The object used for the application on startup ()
+ /// The that is currently active.
/// The top.
public static Toplevel? Top { get; internal set; }
-
- // TODO: Determine why this can't just return _topLevels.Peek()?
- ///
- /// The current object. This is updated in enters and leaves to
- /// point to the current
- /// .
- ///
- ///
- /// This will only be distinct from in scenarios where is .
- ///
- /// The current.
- public static Toplevel? Current { get; internal set; }
-
- ///
- /// If is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current.
- ///
- private static void EnsureModalOrVisibleAlwaysOnTop (Toplevel topLevel)
- {
- if (!topLevel.Running
- || (topLevel == Current && topLevel.Visible)
- || ApplicationOverlapped.OverlappedTop == null
- || TopLevels.Peek ().Modal)
- {
- return;
- }
-
- foreach (Toplevel top in TopLevels.Reverse ())
- {
- if (top.Modal && top != Current)
- {
- ApplicationOverlapped.MoveCurrent (top);
-
- return;
- }
- }
-
- if (!topLevel.Visible && topLevel == Current)
- {
- ApplicationOverlapped.OverlappedMoveNext ();
- }
- }
-
}
diff --git a/Terminal.Gui/Application/Application.cs b/Terminal.Gui/Application/Application.cs
index d37ba3513f..2b7476fbcb 100644
--- a/Terminal.Gui/Application/Application.cs
+++ b/Terminal.Gui/Application/Application.cs
@@ -149,7 +149,6 @@ internal static void ResetState (bool ignoreDisposed = false)
}
TopLevels.Clear ();
- Current = null;
#if DEBUG_IDISPOSABLE
// Don't dispose the Top. It's up to caller dispose it
@@ -198,7 +197,8 @@ internal static void ResetState (bool ignoreDisposed = false)
IsInitialized = false;
// Mouse
- MouseEnteredView = null;
+ _lastMousePosition = null;
+ _cachedViewsUnderMouse.Clear ();
WantContinuousButtonPressedView = null;
MouseEvent = null;
GrabbedMouse = null;
@@ -215,8 +215,6 @@ internal static void ResetState (bool ignoreDisposed = false)
AddApplicationKeyBindings ();
- Colors.Reset ();
-
// Reset synchronization context to allow the user to run async/await,
// as the main loop has been ended, the synchronization context from
// gui.cs does no longer process any callbacks. See #1084 for more details:
diff --git a/Terminal.Gui/Application/ApplicationNavigation.cs b/Terminal.Gui/Application/ApplicationNavigation.cs
index 48aacb0b56..2523709d07 100644
--- a/Terminal.Gui/Application/ApplicationNavigation.cs
+++ b/Terminal.Gui/Application/ApplicationNavigation.cs
@@ -1,5 +1,7 @@
#nullable enable
+using System.Diagnostics;
+
namespace Terminal.Gui;
///
@@ -98,6 +100,6 @@ internal void SetFocused (View? value)
///
public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
{
- return Application.Current is { } && Application.Current.AdvanceFocus (direction, behavior);
+ return Application.Top is { } && Application.Top.AdvanceFocus (direction, behavior);
}
}
diff --git a/Terminal.Gui/Application/ApplicationOverlapped.cs b/Terminal.Gui/Application/ApplicationOverlapped.cs
deleted file mode 100644
index 328897580c..0000000000
--- a/Terminal.Gui/Application/ApplicationOverlapped.cs
+++ /dev/null
@@ -1,444 +0,0 @@
-#nullable enable
-using System.Diagnostics;
-using System.Reflection;
-
-namespace Terminal.Gui;
-
-///
-/// Helper class for managing overlapped views in the application.
-///
-public static class ApplicationOverlapped
-{
-
- ///
- /// Gets or sets if is in overlapped mode within a Toplevel container.
- ///
- ///
- ///
- public static bool IsOverlapped (Toplevel? top)
- {
- return ApplicationOverlapped.OverlappedTop is { } && ApplicationOverlapped.OverlappedTop != top && !top!.Modal;
- }
-
- ///
- /// Gets the list of the Overlapped children which are not modal from the
- /// .
- ///
- public static List? OverlappedChildren
- {
- get
- {
- if (OverlappedTop is { })
- {
- List overlappedChildren = new ();
-
- lock (Application.TopLevels)
- {
- foreach (Toplevel top in Application.TopLevels)
- {
- if (top != OverlappedTop && !top.Modal)
- {
- overlappedChildren.Add (top);
- }
- }
- }
-
- return overlappedChildren;
- }
-
- return null;
- }
- }
-
- ///
- /// The object used for the application on startup which
- /// is true.
- ///
- public static Toplevel? OverlappedTop
- {
- get
- {
- if (Application.Top is { IsOverlappedContainer: true })
- {
- return Application.Top;
- }
-
- return null;
- }
- }
-
- /// Brings the superview of the most focused overlapped view is on front.
- public static void BringOverlappedTopToFront ()
- {
- if (OverlappedTop is { })
- {
- return;
- }
-
- View? top = FindTopFromView (Application.Top?.MostFocused);
-
- if (top is Toplevel && Application.Top?.Subviews.Count > 1 && Application.Top.Subviews [^1] != top)
- {
- Application.Top.MoveSubviewToStart (top);
- }
- }
-
- /// Gets the current visible Toplevel overlapped child that matches the arguments pattern.
- /// The type.
- /// The strings to exclude.
- /// The matched view.
- public static Toplevel? GetTopOverlappedChild (Type? type = null, string []? exclude = null)
- {
- if (OverlappedChildren is null || OverlappedTop is null)
- {
- return null;
- }
-
- foreach (Toplevel top in OverlappedChildren)
- {
- if (type is { } && top.GetType () == type && exclude?.Contains (top.Data?.ToString ()) == false)
- {
- return top;
- }
-
- if ((type is { } && top.GetType () != type) || exclude?.Contains (top.Data?.ToString ()) == true)
- {
- continue;
- }
-
- return top;
- }
-
- return null;
- }
-
-
- ///
- /// Sets the focus to the next view in the specified direction within the provided list of views.
- /// If the end of the list is reached, the focus wraps around to the first view in the list.
- /// The method considers the current focused view (`Application.Current`) and attempts to move the focus
- /// to the next view in the specified direction. If the focus cannot be set to the next view, it wraps around
- /// to the first view in the list.
- ///
- ///
- ///
- internal static void SetFocusToNextViewWithWrap (IEnumerable? viewsInTabIndexes, NavigationDirection direction)
- {
- if (viewsInTabIndexes is null)
- {
- return;
- }
-
- // This code-path only executes in obtuse IsOverlappedContainer scenarios.
- Debug.Assert (Application.Current!.IsOverlappedContainer);
-
- bool foundCurrentView = false;
- bool focusSet = false;
- IEnumerable indexes = viewsInTabIndexes as View [] ?? viewsInTabIndexes.ToArray ();
- int viewCount = indexes.Count ();
- int currentIndex = 0;
-
- foreach (View view in indexes)
- {
- if (view == Application.Current)
- {
- foundCurrentView = true;
- }
- else if (foundCurrentView && !focusSet)
- {
- // One of the views is Current, but view is not. Attempt to Advance...
- Application.Current!.SuperView?.AdvanceFocus (direction, null);
- // QUESTION: AdvanceFocus returns false AND sets Focused to null if no view was found to advance to. Should't we only set focusProcessed if it returned true?
- focusSet = true;
-
- if (Application.Current.SuperView?.Focused != Application.Current)
- {
- return;
- }
-
- // Either AdvanceFocus didn't set focus or the view it set focus to is not current...
- // continue...
- }
-
- currentIndex++;
-
- if (foundCurrentView && !focusSet && currentIndex == viewCount)
- {
- // One of the views is Current AND AdvanceFocus didn't set focus AND we are at the last view in the list...
- // This means we should wrap around to the first view in the list.
- indexes.First ().SetFocus ();
- }
- }
- }
-
- ///
- /// Move to the next Overlapped child from the and set it as the if
- /// it is not already.
- ///
- ///
- ///
- public static bool MoveToOverlappedChild (Toplevel? top)
- {
- ArgumentNullException.ThrowIfNull (top);
-
- if (top.Visible && OverlappedTop is { } && Application.Current?.Modal == false)
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MoveTo (top, 0, new ToplevelEqualityComparer ());
- Application.Current = top;
- }
-
- return true;
- }
-
- return false;
- }
-
- /// Move to the next Overlapped child from the .
- public static void OverlappedMoveNext ()
- {
- if (OverlappedTop is { } && !Application.Current!.Modal)
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MoveNext ();
- var isOverlapped = false;
-
- while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
- {
- if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
- {
- isOverlapped = true;
- }
- else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
- {
- MoveCurrent (Application.Top!);
-
- break;
- }
-
- Application.TopLevels.MoveNext ();
- }
-
- Application.Current = Application.TopLevels.Peek ();
- }
- }
- }
-
- /// Move to the previous Overlapped child from the .
- public static void OverlappedMovePrevious ()
- {
- if (OverlappedTop is { } && !Application.Current!.Modal)
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MovePrevious ();
- var isOverlapped = false;
-
- while (Application.TopLevels.Peek () == OverlappedTop || !Application.TopLevels.Peek ().Visible)
- {
- if (!isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
- {
- isOverlapped = true;
- }
- else if (isOverlapped && Application.TopLevels.Peek () == OverlappedTop)
- {
- MoveCurrent (Application.Top!);
-
- break;
- }
-
- Application.TopLevels.MovePrevious ();
- }
-
- Application.Current = Application.TopLevels.Peek ();
- }
- }
- }
-
- internal static bool OverlappedChildNeedsDisplay ()
- {
- if (OverlappedTop is null)
- {
- return false;
- }
-
- lock (Application.TopLevels)
- {
- foreach (Toplevel top in Application.TopLevels)
- {
- if (top != Application.Current && top.Visible && (top.NeedsDisplay || top.SubViewNeedsDisplay || top.LayoutNeeded))
- {
- OverlappedTop.SetSubViewNeedsDisplay ();
-
- return true;
- }
- }
- }
-
- return false;
- }
-
- internal static bool SetCurrentOverlappedAsTop ()
- {
- if (OverlappedTop is null && Application.Current != Application.Top && Application.Current?.SuperView is null && Application.Current?.Modal == false)
- {
- Application.Top = Application.Current;
-
- return true;
- }
-
- return false;
- }
-
- ///
- /// Finds the first Toplevel in the stack that is Visible and who's Frame contains the .
- ///
- ///
- ///
- ///
- internal static Toplevel? FindDeepestTop (Toplevel start, in Point location)
- {
- if (!start.Frame.Contains (location))
- {
- return null;
- }
-
- lock (Application.TopLevels)
- {
- if (Application.TopLevels is not { Count: > 0 })
- {
- return start;
- }
-
- int rx = location.X - start.Frame.X;
- int ry = location.Y - start.Frame.Y;
-
- foreach (Toplevel t in Application.TopLevels)
- {
- if (t == Application.Current)
- {
- continue;
- }
-
- if (t != start && t.Visible && t.Frame.Contains (rx, ry))
- {
- start = t;
-
- break;
- }
- }
- }
-
- return start;
- }
-
- ///
- /// Given , returns the first Superview up the chain that is .
- ///
- internal static View? FindTopFromView (View? view)
- {
- if (view is null)
- {
- return null;
- }
-
- View top = view.SuperView is { } && view.SuperView != Application.Top
- ? view.SuperView
- : view;
-
- while (top?.SuperView is { } && top?.SuperView != Application.Top)
- {
- top = top!.SuperView;
- }
-
- return top;
- }
-
- ///
- /// If the is not the then is moved to the top of
- /// the Toplevel stack and made Current.
- ///
- ///
- ///
- internal static bool MoveCurrent (Toplevel top)
- {
- // The Current is modal and the top is not modal Toplevel then
- // the Current must be moved above the first not modal Toplevel.
- if (OverlappedTop is { }
- && top != OverlappedTop
- && top != Application.Current
- && Application.Current?.Modal == true
- && !Application.TopLevels.Peek ().Modal)
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
- }
-
- var index = 0;
- Toplevel [] savedToplevels = Application.TopLevels.ToArray ();
-
- foreach (Toplevel t in savedToplevels)
- {
- if (!t!.Modal && t != Application.Current && t != top && t != savedToplevels [index])
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MoveTo (top, index, new ToplevelEqualityComparer ());
- }
- }
-
- index++;
- }
-
- return false;
- }
-
- // The Current and the top are both not running Toplevel then
- // the top must be moved above the first not running Toplevel.
- if (OverlappedTop is { }
- && top != OverlappedTop
- && top != Application.Current
- && Application.Current?.Running == false
- && top?.Running == false)
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MoveTo (Application.Current, 0, new ToplevelEqualityComparer ());
- }
-
- var index = 0;
-
- foreach (Toplevel t in Application.TopLevels.ToArray ())
- {
- if (!t.Running && t != Application.Current && index > 0)
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MoveTo (top, index - 1, new ToplevelEqualityComparer ());
- }
- }
-
- index++;
- }
-
- return false;
- }
-
- if ((OverlappedTop is { } && top?.Modal == true && Application.TopLevels.Peek () != top)
- || (OverlappedTop is { } && Application.Current != OverlappedTop && Application.Current?.Modal == false && top == OverlappedTop)
- || (OverlappedTop is { } && Application.Current?.Modal == false && top != Application.Current)
- || (OverlappedTop is { } && Application.Current?.Modal == true && top == OverlappedTop))
- {
- lock (Application.TopLevels)
- {
- Application.TopLevels.MoveTo (top!, 0, new ToplevelEqualityComparer ());
- Application.Current = top;
- }
- }
-
- return true;
- }
-}
diff --git a/Terminal.Gui/Configuration/AppScope.cs b/Terminal.Gui/Configuration/AppScope.cs
index 089e1fe2f0..9c522deccf 100644
--- a/Terminal.Gui/Configuration/AppScope.cs
+++ b/Terminal.Gui/Configuration/AppScope.cs
@@ -16,7 +16,7 @@ namespace Terminal.Gui;
/// public static bool? MyProperty { get; set; } = true;
/// }
///
-/// THe resultant Json will look like this:
+/// The resultant Json will look like this:
///
/// "AppSettings": {
/// "MyAppSettings.MyProperty": true,
diff --git a/Terminal.Gui/Configuration/ColorJsonConverter.cs b/Terminal.Gui/Configuration/ColorJsonConverter.cs
index 91f77040cc..6da4490d91 100644
--- a/Terminal.Gui/Configuration/ColorJsonConverter.cs
+++ b/Terminal.Gui/Configuration/ColorJsonConverter.cs
@@ -1,9 +1,19 @@
using System.Text.Json;
using System.Text.Json.Serialization;
+using ColorHelper;
namespace Terminal.Gui;
-/// Json converter for the class.
+///
+/// Json converter for the class.
+///
+/// Serialization outputs a string with the color name if the color matches a name in
+/// or the "#RRGGBB" hexadecimal representation (e.g. "#FF0000" for red).
+///
+///
+/// Deserialization formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
+/// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", or any W3C color name.
+///
internal class ColorJsonConverter : JsonConverter
{
private static ColorJsonConverter _instance;
@@ -15,7 +25,7 @@ public static ColorJsonConverter Instance
{
if (_instance is null)
{
- _instance = new ColorJsonConverter ();
+ _instance = new ();
}
return _instance;
@@ -31,10 +41,10 @@ public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonS
ReadOnlySpan colorString = reader.GetString ();
// Check if the color string is a color name
- if (Enum.TryParse (colorString, true, out ColorName color))
+ if (ColorStrings.TryParseW3CColorName (colorString.ToString (), out Color color1))
{
// Return the parsed color
- return new Color (in color);
+ return new (color1);
}
if (Color.TryParse (colorString, null, out Color parsedColor))
@@ -48,5 +58,8 @@ public override Color Read (ref Utf8JsonReader reader, Type typeToConvert, JsonS
throw new JsonException ($"Unexpected token when parsing Color: {reader.TokenType}");
}
- public override void Write (Utf8JsonWriter writer, Color value, JsonSerializerOptions options) { writer.WriteStringValue (value.ToString ()); }
+ public override void Write (Utf8JsonWriter writer, Color value, JsonSerializerOptions options)
+ {
+ writer.WriteStringValue (value.ToString ());
+ }
}
diff --git a/Terminal.Gui/Configuration/ConfigurationManager.cs b/Terminal.Gui/Configuration/ConfigurationManager.cs
index 25826339a2..cd5befa16a 100644
--- a/Terminal.Gui/Configuration/ConfigurationManager.cs
+++ b/Terminal.Gui/Configuration/ConfigurationManager.cs
@@ -24,7 +24,7 @@ namespace Terminal.Gui;
///
///
/// Settings are defined in JSON format, according to this schema:
-/// https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json
+/// https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json
///
///
/// Settings that will apply to all applications (global settings) reside in files named config.json.
@@ -197,10 +197,19 @@ public static void Apply ()
try
{
- settings = Settings?.Apply () ?? false;
-
- themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme)
- && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
+ if (string.IsNullOrEmpty (ThemeManager.SelectedTheme))
+ {
+ // First start. Apply settings first. This ensures if a config sets Theme to something other than "Default", it gets used
+ settings = Settings?.Apply () ?? false;
+ themes = !string.IsNullOrEmpty (ThemeManager.SelectedTheme)
+ && (ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false);
+ }
+ else
+ {
+ // Subsequently. Apply Themes first using whatever the SelectedTheme is
+ themes = ThemeManager.Themes? [ThemeManager.SelectedTheme]?.Apply () ?? false;
+ settings = Settings?.Apply () ?? false;
+ }
appSettings = AppSettings?.Apply () ?? false;
}
catch (JsonException e)
diff --git a/Terminal.Gui/Configuration/RuneJsonConverter.cs b/Terminal.Gui/Configuration/RuneJsonConverter.cs
index 4b364f2523..33cbce6688 100644
--- a/Terminal.Gui/Configuration/RuneJsonConverter.cs
+++ b/Terminal.Gui/Configuration/RuneJsonConverter.cs
@@ -6,9 +6,17 @@
namespace Terminal.Gui;
///
-/// Json converter for . Supports Json converter for . Supports A string as
-/// one of: - unicode char (e.g. "☑") - U+hex format (e.g. "U+2611") - \u format (e.g. "\\u2611") A number - The
-/// unicode code in decimal
+/// Json converter for .
+///
+/// If the Rune is printable, it will be serialized as the glyph; otherwise the \u format (e.g. "\\u2611") is used.
+///
+///
+/// Supports deserializing as one of:
+/// - unicode glyph in a string (e.g. "☑")
+/// - U+hex format in a string (e.g. "U+2611")
+/// - \u format in a string (e.g. "\\u2611")
+/// - A decimal number (e.g. 97 for "a")
+///
///
internal class RuneJsonConverter : JsonConverter
{
@@ -108,7 +116,7 @@ public override Rune Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSe
throw new JsonException ($"Invalid combined Rune ({value})");
}
- return new Rune (combined [0]);
+ return new (combined [0]);
}
case JsonTokenType.Number:
{
@@ -116,7 +124,7 @@ public override Rune Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSe
if (Rune.IsValid (num))
{
- return new Rune (num);
+ return new (num);
}
throw new JsonException ($"Invalid Rune (not a scalar Unicode value): {num}.");
@@ -128,18 +136,17 @@ public override Rune Read (ref Utf8JsonReader reader, Type typeToConvert, JsonSe
public override void Write (Utf8JsonWriter writer, Rune value, JsonSerializerOptions options)
{
- // HACK: Writes a JSON comment in addition to the glyph to ease debugging.
- // Technically, JSON comments are not valid, but we use relaxed decoding
- // (ReadCommentHandling = JsonCommentHandling.Skip)
- //writer.WriteCommentValue ($"(U+{value.Value:X8})");
- //var printable = value.MakePrintable ();
- //if (printable == Rune.ReplacementChar) {
- // writer.WriteStringValue (value.ToString ());
- //} else {
- // //writer.WriteRawValue ($"\"{value}\"");
- //}
-
- writer.WriteNumberValue (value.Value);
+ Rune printable = value.MakePrintable ();
+ if (printable == Rune.ReplacementChar)
+ {
+ // Write as /u string
+ writer.WriteRawValue ($"\"{value}\"");
+ }
+ else
+ {
+ // Write as the actual glyph
+ writer.WriteStringValue (value.ToString ());
+ }
}
}
#pragma warning restore 1591
diff --git a/Terminal.Gui/Configuration/SettingsScope.cs b/Terminal.Gui/Configuration/SettingsScope.cs
index 8ce37bbbcf..2ac4bf4c13 100644
--- a/Terminal.Gui/Configuration/SettingsScope.cs
+++ b/Terminal.Gui/Configuration/SettingsScope.cs
@@ -4,7 +4,6 @@
using System.Reflection;
using System.Text.Json;
using System.Text.Json.Serialization;
-using static Terminal.Gui.SpinnerStyle;
namespace Terminal.Gui;
@@ -15,7 +14,7 @@ namespace Terminal.Gui;
///
///
/// {
-/// "$schema" : "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json",
+/// "$schema" : "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json",
/// "Application.UseSystemConsole" : true,
/// "Theme" : "Default",
/// "Themes": {
@@ -33,7 +32,7 @@ public class SettingsScope : Scope
/// Points to our JSON schema.
[JsonInclude]
[JsonPropertyName ("$schema")]
- public string Schema { get; set; } = "https://gui-cs.github.io/Terminal.Gui/schemas/tui-config-schema.json";
+ public string Schema { get; set; } = "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json";
/// Updates the with the settings in a JSON string.
/// Json document to update the settings with.
@@ -87,12 +86,30 @@ public class SettingsScope : Scope
return this;
}
- FileStream stream = File.OpenRead (realPath);
- SettingsScope? s = Update (stream, filePath);
- stream.Close ();
- stream.Dispose ();
+ int retryCount = 0;
- return s;
+ // Sometimes when the config file is written by an external agent, the change notification comes
+ // before the file is closed. This works around that.
+ while (retryCount < 2)
+ {
+ try
+ {
+ FileStream? stream = File.OpenRead (realPath);
+ SettingsScope? s = Update (stream, filePath);
+ stream.Close ();
+ stream.Dispose ();
+
+ return s;
+ }
+ catch (IOException ioe)
+ {
+ Debug.WriteLine($"Couldn't open {filePath}. Retrying...: {ioe}");
+ Task.Delay (100);
+ retryCount++;
+ }
+ }
+
+ return null;
}
/// Updates the with the settings in a JSON string.
diff --git a/Terminal.Gui/Configuration/SourceGenerationContext.cs b/Terminal.Gui/Configuration/SourceGenerationContext.cs
index 85a438b83c..5d2388bcfe 100644
--- a/Terminal.Gui/Configuration/SourceGenerationContext.cs
+++ b/Terminal.Gui/Configuration/SourceGenerationContext.cs
@@ -15,9 +15,11 @@ namespace Terminal.Gui;
[JsonSerializable (typeof (AlignmentModes))]
[JsonSerializable (typeof (LineStyle))]
[JsonSerializable (typeof (ShadowStyle))]
+[JsonSerializable (typeof (HighlightStyle))]
[JsonSerializable (typeof (bool?))]
-[JsonSerializable (typeof (Dictionary))]
+[JsonSerializable (typeof (Dictionary))]
[JsonSerializable (typeof (Dictionary))]
[JsonSerializable (typeof (Dictionary))]
+[JsonSerializable (typeof (Dictionary))]
internal partial class SourceGenerationContext : JsonSerializerContext
{ }
diff --git a/Terminal.Gui/Configuration/ThemeManager.cs b/Terminal.Gui/Configuration/ThemeManager.cs
index daf2edd9d5..b542c9f2c3 100644
--- a/Terminal.Gui/Configuration/ThemeManager.cs
+++ b/Terminal.Gui/Configuration/ThemeManager.cs
@@ -110,7 +110,7 @@ internal static string SelectedTheme
string oldTheme = _theme;
_theme = value;
- if (oldTheme != _theme && Settings! ["Themes"]?.PropertyValue is Dictionary themes && themes.ContainsKey (_theme))
+ if ((oldTheme != _theme || oldTheme != Settings! ["Theme"].PropertyValue as string) && Settings! ["Themes"]?.PropertyValue is Dictionary themes && themes.ContainsKey (_theme))
{
Settings! ["Theme"].PropertyValue = _theme;
Instance.OnThemeChanged (oldTheme);
diff --git a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
index d69004b4ec..9f2a454654 100644
--- a/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs
@@ -485,6 +485,7 @@ public virtual void Move (int col, int row)
public virtual bool SupportsTrueColor => true;
// TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
+ // BUGBUG: Application.Force16Colors should be bool? so if SupportsTrueColor and Application.Force16Colors == false, this doesn't override
///
/// Gets or sets whether the should use 16 colors instead of the default TrueColors.
/// See to change this setting via .
@@ -591,7 +592,13 @@ public virtual Attribute MakeColor (in Color foreground, in Color background)
/// Called when a mouse event occurs. Fires the event.
///
- public void OnMouseEvent (MouseEvent a) { MouseEvent?.Invoke (this, a); }
+ public void OnMouseEvent (MouseEvent a)
+ {
+ // Ensure ScreenPosition is set
+ a.ScreenPosition = a.Position;
+
+ MouseEvent?.Invoke (this, a);
+ }
/// Simulates a key press.
/// The key character.
diff --git a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
index 378e4b6081..0b11949a94 100644
--- a/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs
@@ -39,7 +39,7 @@ internal set
}
}
- public override bool SupportsTrueColor => false;
+ public override bool SupportsTrueColor => true;
///
public override bool EnsureCursorVisibility () { return false; }
@@ -200,8 +200,12 @@ public override void Suspend ()
if (!RunningUnitTests)
{
Platform.Suspend ();
- Curses.Window.Standard.redrawwin ();
- Curses.refresh ();
+
+ if (Force16Colors)
+ {
+ Curses.Window.Standard.redrawwin ();
+ Curses.refresh ();
+ }
}
StartReportingMouseMoves ();
@@ -214,74 +218,232 @@ public override void UpdateCursor ()
if (!RunningUnitTests && Col >= 0 && Col < Cols && Row >= 0 && Row < Rows)
{
Curses.move (Row, Col);
- Curses.raw ();
- Curses.noecho ();
- Curses.refresh ();
+
+ if (Force16Colors)
+ {
+ Curses.raw ();
+ Curses.noecho ();
+ Curses.refresh ();
+ }
}
}
-
public override void UpdateScreen ()
{
- for (var row = 0; row < Rows; row++)
+ if (Force16Colors)
{
- if (!_dirtyLines [row])
+ for (var row = 0; row < Rows; row++)
{
- continue;
- }
-
- _dirtyLines [row] = false;
-
- for (var col = 0; col < Cols; col++)
- {
- if (Contents [row, col].IsDirty == false)
+ if (!_dirtyLines [row])
{
continue;
}
- if (RunningUnitTests)
+ _dirtyLines [row] = false;
+
+ for (var col = 0; col < Cols; col++)
{
- // In unit tests, we don't want to actually write to the screen.
- continue;
- }
+ if (Contents [row, col].IsDirty == false)
+ {
+ continue;
+ }
- Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
+ if (RunningUnitTests)
+ {
+ // In unit tests, we don't want to actually write to the screen.
+ continue;
+ }
- Rune rune = Contents [row, col].Rune;
+ Curses.attrset (Contents [row, col].Attribute.GetValueOrDefault ().PlatformColor);
- if (rune.IsBmp)
- {
- // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
- if (rune.GetColumns () < 2)
+ Rune rune = Contents [row, col].Rune;
+
+ if (rune.IsBmp)
{
- Curses.mvaddch (row, col, rune.Value);
+ // BUGBUG: CursesDriver doesn't render CharMap correctly for wide chars (and other Unicode) - Curses is doing something funky with glyphs that report GetColums() of 1 yet are rendered wide. E.g. 0x2064 (invisible times) is reported as 1 column but is rendered as 2. WindowsDriver & NetDriver correctly render this as 1 column, overlapping the next cell.
+ if (rune.GetColumns () < 2)
+ {
+ Curses.mvaddch (row, col, rune.Value);
+ }
+ else /*if (col + 1 < Cols)*/
+ {
+ Curses.mvaddwstr (row, col, rune.ToString ());
+ }
}
- else /*if (col + 1 < Cols)*/
+ else
{
Curses.mvaddwstr (row, col, rune.ToString ());
+
+ if (rune.GetColumns () > 1 && col + 1 < Cols)
+ {
+ // TODO: This is a hack to deal with non-BMP and wide characters.
+ //col++;
+ Curses.mvaddch (row, ++col, '*');
+ }
}
}
- else
+ }
+
+ if (!RunningUnitTests)
+ {
+ Curses.move (Row, Col);
+ _window.wrefresh ();
+ }
+ }
+ else
+ {
+ if (RunningUnitTests
+ || Console.WindowHeight < 1
+ || Contents.Length != Rows * Cols
+ || Rows != Console.WindowHeight)
+ {
+ return;
+ }
+
+ var top = 0;
+ var left = 0;
+ int rows = Rows;
+ int cols = Cols;
+ var output = new StringBuilder ();
+ Attribute? redrawAttr = null;
+ int lastCol = -1;
+
+ CursorVisibility? savedVisibility = _currentCursorVisibility;
+ SetCursorVisibility (CursorVisibility.Invisible);
+
+ for (int row = top; row < rows; row++)
+ {
+ if (Console.WindowHeight < 1)
+ {
+ return;
+ }
+
+ if (!_dirtyLines [row])
+ {
+ continue;
+ }
+
+ if (!SetCursorPosition (0, row))
{
- Curses.mvaddwstr (row, col, rune.ToString ());
+ return;
+ }
+
+ _dirtyLines [row] = false;
+ output.Clear ();
- if (rune.GetColumns () > 1 && col + 1 < Cols)
+ for (int col = left; col < cols; col++)
+ {
+ lastCol = -1;
+ var outputWidth = 0;
+
+ for (; col < cols; col++)
{
- // TODO: This is a hack to deal with non-BMP and wide characters.
- //col++;
- Curses.mvaddch (row, ++col, '*');
+ if (!Contents [row, col].IsDirty)
+ {
+ if (output.Length > 0)
+ {
+ WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ }
+ else if (lastCol == -1)
+ {
+ lastCol = col;
+ }
+
+ if (lastCol + 1 < cols)
+ {
+ lastCol++;
+ }
+
+ continue;
+ }
+
+ if (lastCol == -1)
+ {
+ lastCol = col;
+ }
+
+ Attribute attr = Contents [row, col].Attribute.Value;
+
+ // Performance: Only send the escape sequence if the attribute has changed.
+ if (attr != redrawAttr)
+ {
+ redrawAttr = attr;
+
+ output.Append (
+ EscSeqUtils.CSI_SetForegroundColorRGB (
+ attr.Foreground.R,
+ attr.Foreground.G,
+ attr.Foreground.B
+ )
+ );
+
+ output.Append (
+ EscSeqUtils.CSI_SetBackgroundColorRGB (
+ attr.Background.R,
+ attr.Background.G,
+ attr.Background.B
+ )
+ );
+ }
+
+ outputWidth++;
+ Rune rune = Contents [row, col].Rune;
+ output.Append (rune);
+
+ if (Contents [row, col].CombiningMarks.Count > 0)
+ {
+ // AtlasEngine does not support NON-NORMALIZED combining marks in a way
+ // compatible with the driver architecture. Any CMs (except in the first col)
+ // are correctly combined with the base char, but are ALSO treated as 1 column
+ // width codepoints E.g. `echo "[e`u{0301}`u{0301}]"` will output `[é ]`.
+ //
+ // For now, we just ignore the list of CMs.
+ //foreach (var combMark in Contents [row, col].CombiningMarks) {
+ // output.Append (combMark);
+ //}
+ // WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ }
+ else if (rune.IsSurrogatePair () && rune.GetColumns () < 2)
+ {
+ WriteToConsole (output, ref lastCol, row, ref outputWidth);
+ SetCursorPosition (col - 1, row);
+ }
+
+ Contents [row, col].IsDirty = false;
}
}
+
+ if (output.Length > 0)
+ {
+ SetCursorPosition (lastCol, row);
+ Console.Write (output);
+ }
}
- }
- if (!RunningUnitTests)
- {
- Curses.move (Row, Col);
- _window.wrefresh ();
+ SetCursorPosition (0, 0);
+
+ _currentCursorVisibility = savedVisibility;
+
+ void WriteToConsole (StringBuilder output, ref int lastCol, int row, ref int outputWidth)
+ {
+ SetCursorPosition (lastCol, row);
+ Console.Write (output);
+ output.Clear ();
+ lastCol += outputWidth;
+ outputWidth = 0;
+ }
}
}
+ private bool SetCursorPosition (int col, int row)
+ {
+ // + 1 is needed because non-Windows is based on 1 instead of 0 and
+ // Console.CursorTop/CursorLeft isn't reliable.
+ Console.Out.Write (EscSeqUtils.CSI_SetCursorPosition (row + 1, col + 1));
+
+ return true;
+ }
+
internal override void End ()
{
StopReportingMouseMoves ();
@@ -374,7 +536,7 @@ internal override MainLoop Init ()
);
}
- CurrentAttribute = new Attribute (ColorName.White, ColorName.Black);
+ CurrentAttribute = new Attribute (ColorName16.White, ColorName16.Black);
if (Environment.OSVersion.Platform == PlatformID.Win32NT)
{
@@ -405,7 +567,11 @@ internal override MainLoop Init ()
if (!RunningUnitTests)
{
Curses.CheckWinChange ();
- Curses.refresh ();
+
+ if (Force16Colors)
+ {
+ Curses.refresh ();
+ }
}
return new MainLoop (_mainLoopDriver);
@@ -852,15 +1018,16 @@ bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
///
private static Attribute MakeColor (short foreground, short background)
{
- var v = (short)((ushort)foreground | (background << 4));
+ //var v = (short)((ushort)foreground | (background << 4));
+ var v = (short)(((ushort)(foreground & 0xffff) << 16) | (background & 0xffff));
// TODO: for TrueColor - Use InitExtendedPair
Curses.InitColorPair (v, foreground, background);
return new Attribute (
Curses.ColorPair (v),
- CursesColorNumberToColorName (foreground),
- CursesColorNumberToColorName (background)
+ CursesColorNumberToColorName16 (foreground),
+ CursesColorNumberToColorName16 (background)
);
}
@@ -872,11 +1039,11 @@ private static Attribute MakeColor (short foreground, short background)
///
public override Attribute MakeColor (in Color foreground, in Color background)
{
- if (!RunningUnitTests)
+ if (!RunningUnitTests && Force16Colors)
{
return MakeColor (
- ColorNameToCursesColorNumber (foreground.GetClosestNamedColor ()),
- ColorNameToCursesColorNumber (background.GetClosestNamedColor ())
+ ColorNameToCursesColorNumber (foreground.GetClosestNamedColor16 ()),
+ ColorNameToCursesColorNumber (background.GetClosestNamedColor16 ())
);
}
@@ -887,83 +1054,83 @@ public override Attribute MakeColor (in Color foreground, in Color background)
);
}
- private static short ColorNameToCursesColorNumber (ColorName color)
+ private static short ColorNameToCursesColorNumber (ColorName16 color)
{
switch (color)
{
- case ColorName.Black:
+ case ColorName16.Black:
return Curses.COLOR_BLACK;
- case ColorName.Blue:
+ case ColorName16.Blue:
return Curses.COLOR_BLUE;
- case ColorName.Green:
+ case ColorName16.Green:
return Curses.COLOR_GREEN;
- case ColorName.Cyan:
+ case ColorName16.Cyan:
return Curses.COLOR_CYAN;
- case ColorName.Red:
+ case ColorName16.Red:
return Curses.COLOR_RED;
- case ColorName.Magenta:
+ case ColorName16.Magenta:
return Curses.COLOR_MAGENTA;
- case ColorName.Yellow:
+ case ColorName16.Yellow:
return Curses.COLOR_YELLOW;
- case ColorName.Gray:
+ case ColorName16.Gray:
return Curses.COLOR_WHITE;
- case ColorName.DarkGray:
+ case ColorName16.DarkGray:
return Curses.COLOR_GRAY;
- case ColorName.BrightBlue:
+ case ColorName16.BrightBlue:
return Curses.COLOR_BLUE | Curses.COLOR_GRAY;
- case ColorName.BrightGreen:
+ case ColorName16.BrightGreen:
return Curses.COLOR_GREEN | Curses.COLOR_GRAY;
- case ColorName.BrightCyan:
+ case ColorName16.BrightCyan:
return Curses.COLOR_CYAN | Curses.COLOR_GRAY;
- case ColorName.BrightRed:
+ case ColorName16.BrightRed:
return Curses.COLOR_RED | Curses.COLOR_GRAY;
- case ColorName.BrightMagenta:
+ case ColorName16.BrightMagenta:
return Curses.COLOR_MAGENTA | Curses.COLOR_GRAY;
- case ColorName.BrightYellow:
+ case ColorName16.BrightYellow:
return Curses.COLOR_YELLOW | Curses.COLOR_GRAY;
- case ColorName.White:
+ case ColorName16.White:
return Curses.COLOR_WHITE | Curses.COLOR_GRAY;
}
throw new ArgumentException ("Invalid color code");
}
- private static ColorName CursesColorNumberToColorName (short color)
+ private static ColorName16 CursesColorNumberToColorName16 (short color)
{
switch (color)
{
case Curses.COLOR_BLACK:
- return ColorName.Black;
+ return ColorName16.Black;
case Curses.COLOR_BLUE:
- return ColorName.Blue;
+ return ColorName16.Blue;
case Curses.COLOR_GREEN:
- return ColorName.Green;
+ return ColorName16.Green;
case Curses.COLOR_CYAN:
- return ColorName.Cyan;
+ return ColorName16.Cyan;
case Curses.COLOR_RED:
- return ColorName.Red;
+ return ColorName16.Red;
case Curses.COLOR_MAGENTA:
- return ColorName.Magenta;
+ return ColorName16.Magenta;
case Curses.COLOR_YELLOW:
- return ColorName.Yellow;
+ return ColorName16.Yellow;
case Curses.COLOR_WHITE:
- return ColorName.Gray;
+ return ColorName16.Gray;
case Curses.COLOR_GRAY:
- return ColorName.DarkGray;
+ return ColorName16.DarkGray;
case Curses.COLOR_BLUE | Curses.COLOR_GRAY:
- return ColorName.BrightBlue;
+ return ColorName16.BrightBlue;
case Curses.COLOR_GREEN | Curses.COLOR_GRAY:
- return ColorName.BrightGreen;
+ return ColorName16.BrightGreen;
case Curses.COLOR_CYAN | Curses.COLOR_GRAY:
- return ColorName.BrightCyan;
+ return ColorName16.BrightCyan;
case Curses.COLOR_RED | Curses.COLOR_GRAY:
- return ColorName.BrightRed;
+ return ColorName16.BrightRed;
case Curses.COLOR_MAGENTA | Curses.COLOR_GRAY:
- return ColorName.BrightMagenta;
+ return ColorName16.BrightMagenta;
case Curses.COLOR_YELLOW | Curses.COLOR_GRAY:
- return ColorName.BrightYellow;
+ return ColorName16.BrightYellow;
case Curses.COLOR_WHITE | Curses.COLOR_GRAY:
- return ColorName.White;
+ return ColorName16.White;
}
throw new ArgumentException ("Invalid curses color code");
diff --git a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
index 6ec810c8a0..73c12959f4 100644
--- a/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/FakeDriver/FakeDriver.cs
@@ -93,7 +93,7 @@ internal override MainLoop Init ()
FakeConsole.Clear ();
ResizeScreen ();
CurrentAttribute = new Attribute (Color.White, Color.Black);
- ClearContents ();
+ //ClearContents ();
_mainLoopDriver = new FakeMainLoop (this);
_mainLoopDriver.MockKeyPressed = MockKeyPressedHandler;
@@ -165,8 +165,8 @@ public override void UpdateScreen ()
if (attr != redrawAttr)
{
redrawAttr = attr;
- FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor ();
- FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor ();
+ FakeConsole.ForegroundColor = (ConsoleColor)attr.Foreground.GetClosestNamedColor16 ();
+ FakeConsole.BackgroundColor = (ConsoleColor)attr.Background.GetClosestNamedColor16 ();
}
outputWidth++;
diff --git a/Terminal.Gui/ConsoleDrivers/NetDriver.cs b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
index fcd7316e22..1298c1c357 100644
--- a/Terminal.Gui/ConsoleDrivers/NetDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/NetDriver.cs
@@ -961,10 +961,10 @@ public override void UpdateScreen ()
output.Append (
EscSeqUtils.CSI_SetGraphicsRendition (
MapColors (
- (ConsoleColor)attr.Background.GetClosestNamedColor (),
+ (ConsoleColor)attr.Background.GetClosestNamedColor16 (),
false
),
- MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor ())
+ MapColors ((ConsoleColor)attr.Foreground.GetClosestNamedColor16 ())
)
);
}
diff --git a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
index 8fb1d52e8c..2a9bef0810 100644
--- a/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
+++ b/Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
@@ -54,6 +54,8 @@ public WindowsConsole ()
public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord bufferSize, SmallRect window, bool force16Colors)
{
+ //Debug.WriteLine ("WriteToConsole");
+
if (_screenBuffer == nint.Zero)
{
ReadFromConsoleOutput (size, bufferSize, ref window);
@@ -72,7 +74,7 @@ public bool WriteToConsole (Size size, ExtendedCharInfo [] charInfoBuffer, Coord
{
Char = new CharUnion { UnicodeChar = info.Char },
Attributes =
- (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor () | ((int)info.Attribute.Background.GetClosestNamedColor () << 4))
+ (ushort)((int)info.Attribute.Foreground.GetClosestNamedColor16 () | ((int)info.Attribute.Background.GetClosestNamedColor16 () << 4))
};
}
@@ -1811,12 +1813,6 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
int delay = startDelay;
while (_isButtonPressed)
{
- var me = new MouseEvent
- {
- Position = _pointMove,
- Flags = mouseFlag
- };
-
// TODO: This makes ConsoleDriver dependent on Application, which is not ideal. This should be moved to Application.
View view = Application.WantContinuousButtonPressedView;
@@ -1831,6 +1827,12 @@ private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
}
await Task.Delay (delay);
+ var me = new MouseEvent
+ {
+ Position = _pointMove,
+ Flags = mouseFlag
+ };
+
//Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
{
diff --git a/Terminal.Gui/Drawing/Alignment.cs b/Terminal.Gui/Drawing/Alignment.cs
index 169e4c323a..4e2338f55d 100644
--- a/Terminal.Gui/Drawing/Alignment.cs
+++ b/Terminal.Gui/Drawing/Alignment.cs
@@ -10,72 +10,74 @@ public enum Alignment
{
///
/// The items will be aligned to the start (left or top) of the container.
+ ///
+ ///
+ /// If the container is smaller than the total size of the items, the end items will be clipped (their
+ /// locations
+ /// will be greater than the container size).
+ ///
+ ///
+ /// The enumeration provides additional options for aligning items in a container.
+ ///
+ ///
+ ///
+ ///
+ /// |111 2222 33333 |
+ ///
+ ///
///
- ///
- ///
- /// If the container is smaller than the total size of the items, the end items will be clipped (their locations
- /// will be greater than the container size).
- ///
- ///
- /// The enumeration provides additional options for aligning items in a container.
- ///
- ///
- ///
- ///
- /// |111 2222 33333 |
- ///
- ///
Start = 0,
///
/// The items will be aligned to the end (right or bottom) of the container.
+ ///
+ ///
+ /// If the container is smaller than the total size of the items, the start items will be clipped (their
+ /// locations
+ /// will be negative).
+ ///
+ ///
+ /// The enumeration provides additional options for aligning items in a container.
+ ///
+ ///
+ ///
+ ///
+ /// | 111 2222 33333|
+ ///
+ ///
///
- ///
- ///
- /// If the container is smaller than the total size of the items, the start items will be clipped (their locations
- /// will be negative).
- ///
- ///
- /// The enumeration provides additional options for aligning items in a container.
- ///
- ///
- ///
- ///
- /// | 111 2222 33333|
- ///
- ///
End,
///
/// Center in the available space.
+ ///
+ ///
+ /// If centering is not possible, the group will be left-aligned.
+ ///
+ ///
+ /// Extra space will be distributed between the items, biased towards the left.
+ ///
+ ///
+ ///
+ ///
+ /// | 111 2222 33333 |
+ ///
+ ///
///
- ///
- ///
- /// If centering is not possible, the group will be left-aligned.
- ///
- ///
- /// Extra space will be distributed between the items, biased towards the left.
- ///
- ///
- ///
- ///
- /// | 111 2222 33333 |
- ///
- ///
Center,
///
/// The items will fill the available space.
+ ///
+ ///
+ /// Extra space will be distributed between the items, biased towards the end.
+ ///
+ ///
+ ///
+ ///
+ /// |111 2222 33333|
+ ///
+ ///
///
- ///
- ///
- /// Extra space will be distributed between the items, biased towards the end.
- ///
- ///
- ///
- ///
- /// |111 2222 33333|
- ///
- ///
- Fill,
-}
\ No newline at end of file
+ Fill
+}
diff --git a/Terminal.Gui/Drawing/AlignmentModes.cs b/Terminal.Gui/Drawing/AlignmentModes.cs
index 1b6e262c48..d06dbf6be0 100644
--- a/Terminal.Gui/Drawing/AlignmentModes.cs
+++ b/Terminal.Gui/Drawing/AlignmentModes.cs
@@ -17,30 +17,24 @@ public enum AlignmentModes
///
/// The items will be arranged from end (right/bottom) to start (left/top).
///
- ///
- /// Not implemented.
- ///
EndToStart = 1,
///
/// At least one space will be added between items. Useful for justifying text where at least one space is needed.
///
///
- ///
- /// If the total size of the items is greater than the container size, the space between items will be ignored
- /// starting from the end.
- ///
+ /// If the total size of the items is greater than the container size, the space between items will be ignored
+ /// starting from the end.
///
AddSpaceBetweenItems = 2,
///
- /// When aligning via or , the item opposite to the alignment (the first or last item) will be ignored.
+ /// When aligning via or , the item opposite to the alignment
+ /// (the first or last item) will be ignored.
///
///
- ///
- /// If the container is smaller than the total size of the items, the end items will be clipped (their locations
- /// will be greater than the container size).
- ///
+ /// If the container is smaller than the total size of the items, the end items will be clipped (their locations
+ /// will be greater than the container size).
///
///
///
@@ -48,5 +42,5 @@ public enum AlignmentModes
/// End: |111 2222 33333|
///
///
- IgnoreFirstOrLast = 4,
-}
\ No newline at end of file
+ IgnoreFirstOrLast = 4
+}
diff --git a/Terminal.Gui/Drawing/Attribute.cs b/Terminal.Gui/Drawing/Attribute.cs
index 00b64f9a38..32c948d2e4 100644
--- a/Terminal.Gui/Drawing/Attribute.cs
+++ b/Terminal.Gui/Drawing/Attribute.cs
@@ -15,7 +15,7 @@ namespace Terminal.Gui;
{
/// Default empty attribute.
[JsonIgnore]
- public static Attribute Default => new (Color.White, ColorName.Black);
+ public static Attribute Default => new (Color.White, Color.Black);
/// The -specific color value.
[JsonIgnore (Condition = JsonIgnoreCondition.Always)]
@@ -65,28 +65,28 @@ public Attribute (in Color foreground, in Color background)
}
///
- /// Initializes a new instance with a value. Both and
+ /// Initializes a new instance with a value. Both and
/// will be set to the specified color.
///
/// Value.
- internal Attribute (in ColorName colorName) : this (in colorName, in colorName) { }
+ internal Attribute (in ColorName16 colorName) : this (in colorName, in colorName) { }
/// Initializes a new instance of the struct.
/// Foreground
/// Background
- public Attribute (in ColorName foregroundName, in ColorName backgroundName)
+ public Attribute (in ColorName16 foregroundName, in ColorName16 backgroundName)
: this (new Color (in foregroundName), new Color (in backgroundName))
{ }
/// Initializes a new instance of the struct.
/// Foreground
/// Background
- public Attribute (in ColorName foregroundName, in Color background) : this (new Color (in foregroundName), in background) { }
+ public Attribute (in ColorName16 foregroundName, in Color background) : this (new Color (in foregroundName), in background) { }
/// Initializes a new instance of the struct.
/// Foreground
/// Background
- public Attribute (in Color foreground, in ColorName backgroundName) : this (in foreground, new Color (in backgroundName)) { }
+ public Attribute (in Color foreground, in ColorName16 backgroundName) : this (in foreground, new Color (in backgroundName)) { }
///
/// Initializes a new instance of the struct with the same colors for the foreground and
diff --git a/Terminal.Gui/Drawing/Cell.cs b/Terminal.Gui/Drawing/Cell.cs
index 55da051845..16af046a7f 100644
--- a/Terminal.Gui/Drawing/Cell.cs
+++ b/Terminal.Gui/Drawing/Cell.cs
@@ -4,19 +4,18 @@
/// Represents a single row/column in a Terminal.Gui rendering surface (e.g. and
/// ).
///
-public record struct Cell ()
+public record struct Cell (Attribute? Attribute = null, bool IsDirty = false, Rune Rune = default)
{
-
/// The attributes to use when drawing the Glyph.
- public Attribute? Attribute { get; set; } = null;
+ public Attribute? Attribute { get; set; } = Attribute;
///
/// Gets or sets a value indicating whether this has been modified since the
/// last time it was drawn.
///
- public bool IsDirty { get; set; } = false;
+ public bool IsDirty { get; set; } = IsDirty;
- private Rune _rune = default;
+ private Rune _rune = Rune;
/// The character to display. If is , then is ignored.
public Rune Rune
@@ -29,6 +28,8 @@ public Rune Rune
}
}
+ private List _combiningMarks;
+
///
/// The combining marks for that when combined makes this Cell a combining sequence. If
/// empty, then is ignored.
@@ -37,8 +38,153 @@ public Rune Rune
/// Only valid in the rare case where is a combining sequence that could not be normalized to a
/// single Rune.
///
- internal List CombiningMarks { get; } = new ();
+ internal List CombiningMarks
+ {
+ get => _combiningMarks ?? [];
+ private set => _combiningMarks = value ?? [];
+ }
///
public override string ToString () { return $"[{Rune}, {Attribute}]"; }
+
+ /// Converts the string into a .
+ /// The string to convert.
+ /// The to use.
+ ///
+ public static List ToCellList (string str, Attribute? attribute = null)
+ {
+ List cells = new ();
+
+ foreach (Rune rune in str.EnumerateRunes ())
+ {
+ cells.Add (new () { Rune = rune, Attribute = attribute });
+ }
+
+ return cells;
+ }
+
+ ///
+ /// Splits a string into a List that will contain a for each line.
+ ///
+ /// The string content.
+ /// The color scheme.
+ /// A for each line.
+ public static List> StringToLinesOfCells (string content, Attribute? attribute = null)
+ {
+ List cells = content.EnumerateRunes ()
+ .Select (x => new Cell { Rune = x, Attribute = attribute })
+ .ToList ();
+
+ return SplitNewLines (cells);
+ }
+
+ /// Converts a generic collection into a string.
+ /// The enumerable cell to convert.
+ ///
+ public static string ToString (IEnumerable cells)
+ {
+ var str = string.Empty;
+
+ foreach (Cell cell in cells)
+ {
+ str += cell.Rune.ToString ();
+ }
+
+ return str;
+ }
+
+ /// Converts a generic collection into a string.
+ /// The enumerable cell to convert.
+ ///
+ public static string ToString (List> cellsList)
+ {
+ var str = string.Empty;
+
+ for (var i = 0; i < cellsList.Count; i++)
+ {
+ IEnumerable cellList = cellsList [i];
+ str += ToString (cellList);
+
+ if (i + 1 < cellsList.Count)
+ {
+ str += Environment.NewLine;
+ }
+ }
+
+ return str;
+ }
+
+ // Turns the string into cells, this does not split the contents on a newline if it is present.
+
+ internal static List StringToCells (string str, Attribute? attribute = null)
+ {
+ List cells = [];
+
+ foreach (Rune rune in str.ToRunes ())
+ {
+ cells.Add (new () { Rune = rune, Attribute = attribute });
+ }
+
+ return cells;
+ }
+
+ internal static List ToCells (IEnumerable runes, Attribute? attribute = null)
+ {
+ List cells = new ();
+
+ foreach (Rune rune in runes)
+ {
+ cells.Add (new () { Rune = rune, Attribute = attribute });
+ }
+
+ return cells;
+ }
+
+ private static List> SplitNewLines (List cells)
+ {
+ List> lines = [];
+ int start = 0, i = 0;
+ var hasCR = false;
+
+ // ASCII code 13 = Carriage Return.
+ // ASCII code 10 = Line Feed.
+ for (; i < cells.Count; i++)
+ {
+ if (cells [i].Rune.Value == 13)
+ {
+ hasCR = true;
+
+ continue;
+ }
+
+ if (cells [i].Rune.Value == 10)
+ {
+ if (i - start > 0)
+ {
+ lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start));
+ }
+ else
+ {
+ lines.Add (StringToCells (string.Empty));
+ }
+
+ start = i + 1;
+ hasCR = false;
+ }
+ }
+
+ if (i - start >= 0)
+ {
+ lines.Add (cells.GetRange (start, i - start));
+ }
+
+ return lines;
+ }
+
+ ///
+ /// Splits a rune cell list into a List that will contain a for each line.
+ ///
+ /// The cells list.
+ ///
+ public static List> ToCells (List cells) { return SplitNewLines (cells); }
}
diff --git a/Terminal.Gui/Views/RuneCellEventArgs.cs b/Terminal.Gui/Drawing/CellEventArgs.cs
similarity index 57%
rename from Terminal.Gui/Views/RuneCellEventArgs.cs
rename to Terminal.Gui/Drawing/CellEventArgs.cs
index 1283cfe570..f2a8115dc9 100644
--- a/Terminal.Gui/Views/RuneCellEventArgs.cs
+++ b/Terminal.Gui/Drawing/CellEventArgs.cs
@@ -1,27 +1,27 @@
namespace Terminal.Gui;
-/// Args for events that relate to a specific .
-public class RuneCellEventArgs
+/// Args for events that relate to a specific .
+public record struct CellEventArgs
{
- /// Creates a new instance of the class.
+ /// Creates a new instance of the class.
/// The line.
/// The col index.
/// The unwrapped row and col index.
- public RuneCellEventArgs (List line, int col, (int Row, int Col) unwrappedPosition)
+ public CellEventArgs (List line, int col, (int Row, int Col) unwrappedPosition)
{
Line = line;
Col = col;
UnwrappedPosition = unwrappedPosition;
}
- /// The index of the RuneCell in the line.
+ /// The index of the Cell in the line.
public int Col { get; }
- /// The list of runes the RuneCell is part of.
- public List Line { get; }
+ /// The list of runes the Cell is part of.
+ public List Line { get; }
///
- /// The unwrapped row and column index into the text containing the RuneCell. Unwrapped means the text without
+ /// The unwrapped row and column index into the text containing the Cell. Unwrapped means the text without
/// word wrapping or other visual formatting having been applied.
///
public (int Row, int Col) UnwrappedPosition { get; }
diff --git a/Terminal.Gui/Drawing/Color.ColorExtensions.cs b/Terminal.Gui/Drawing/Color.ColorExtensions.cs
index a5d2222cae..fd71da3592 100644
--- a/Terminal.Gui/Drawing/Color.ColorExtensions.cs
+++ b/Terminal.Gui/Drawing/Color.ColorExtensions.cs
@@ -1,72 +1,75 @@
using System.Collections.Frozen;
+using ColorHelper;
namespace Terminal.Gui;
internal static class ColorExtensions
{
- private static FrozenDictionary colorToNameMap;
+ // TODO: This should be refactored to support all W3CColors (`ColorStrings` and this should be merged).
+ // TODO: ColorName and AnsiColorCode are only needed when a driver is in Force16Color mode and we
+ // TODO: should be able to remove these from any non-Driver-specific usages.
+ private static FrozenDictionary colorToNameMap;
static ColorExtensions ()
{
- Dictionary nameToCodeMap = new ()
+ Dictionary nameToCodeMap = new ()
{
- { ColorName.Black, AnsiColorCode.BLACK },
- { ColorName.Blue, AnsiColorCode.BLUE },
- { ColorName.Green, AnsiColorCode.GREEN },
- { ColorName.Cyan, AnsiColorCode.CYAN },
- { ColorName.Red, AnsiColorCode.RED },
- { ColorName.Magenta, AnsiColorCode.MAGENTA },
- { ColorName.Yellow, AnsiColorCode.YELLOW },
- { ColorName.Gray, AnsiColorCode.WHITE },
- { ColorName.DarkGray, AnsiColorCode.BRIGHT_BLACK },
- { ColorName.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
- { ColorName.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
- { ColorName.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
- { ColorName.BrightRed, AnsiColorCode.BRIGHT_RED },
- { ColorName.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
- { ColorName.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
- { ColorName.White, AnsiColorCode.BRIGHT_WHITE }
+ { ColorName16.Black, AnsiColorCode.BLACK },
+ { ColorName16.Blue, AnsiColorCode.BLUE },
+ { ColorName16.Green, AnsiColorCode.GREEN },
+ { ColorName16.Cyan, AnsiColorCode.CYAN },
+ { ColorName16.Red, AnsiColorCode.RED },
+ { ColorName16.Magenta, AnsiColorCode.MAGENTA },
+ { ColorName16.Yellow, AnsiColorCode.YELLOW },
+ { ColorName16.Gray, AnsiColorCode.WHITE },
+ { ColorName16.DarkGray, AnsiColorCode.BRIGHT_BLACK },
+ { ColorName16.BrightBlue, AnsiColorCode.BRIGHT_BLUE },
+ { ColorName16.BrightGreen, AnsiColorCode.BRIGHT_GREEN },
+ { ColorName16.BrightCyan, AnsiColorCode.BRIGHT_CYAN },
+ { ColorName16.BrightRed, AnsiColorCode.BRIGHT_RED },
+ { ColorName16.BrightMagenta, AnsiColorCode.BRIGHT_MAGENTA },
+ { ColorName16.BrightYellow, AnsiColorCode.BRIGHT_YELLOW },
+ { ColorName16.White, AnsiColorCode.BRIGHT_WHITE }
};
- ColorNameToAnsiColorMap = nameToCodeMap.ToFrozenDictionary ();
+ ColorName16ToAnsiColorMap = nameToCodeMap.ToFrozenDictionary ();
- ColorToNameMap = new Dictionary
+ ColorToName16Map = new Dictionary
{
- // using "Windows 10 Console/PowerShell 6" here: https://i.stack.imgur.com/9UVnC.png
- // See also: https://en.wikipedia.org/wiki/ANSI_escape_code
- { new Color (12, 12, 12), ColorName.Black },
- { new Color (0, 55, 218), ColorName.Blue },
- { new Color (19, 161, 14), ColorName.Green },
- { new Color (58, 150, 221), ColorName.Cyan },
- { new Color (197, 15, 31), ColorName.Red },
- { new Color (136, 23, 152), ColorName.Magenta },
- { new Color (128, 64, 32), ColorName.Yellow },
- { new Color (204, 204, 204), ColorName.Gray },
- { new Color (118, 118, 118), ColorName.DarkGray },
- { new Color (59, 120, 255), ColorName.BrightBlue },
- { new Color (22, 198, 12), ColorName.BrightGreen },
- { new Color (97, 214, 214), ColorName.BrightCyan },
- { new Color (231, 72, 86), ColorName.BrightRed },
- { new Color (180, 0, 158), ColorName.BrightMagenta },
- { new Color (249, 241, 165), ColorName.BrightYellow },
- { new Color (242, 242, 242), ColorName.White }
+ // These match the values in Strings.resx, which are the W3C colors.
+ { new Color(0, 0, 0), ColorName16.Black },
+ { new Color(0, 0, 255), ColorName16.Blue },
+ { new Color(0, 128, 0), ColorName16.Green },
+ { new Color(0, 255, 255), ColorName16.Cyan },
+ { new Color(255, 0, 0), ColorName16.Red },
+ { new Color(255, 0, 255), ColorName16.Magenta },
+ { new Color(255, 255, 0), ColorName16.Yellow },
+ { new Color(128, 128, 128), ColorName16.Gray },
+ { new Color(118, 118, 118), ColorName16.DarkGray },
+ { new Color(59, 120, 255), ColorName16.BrightBlue },
+ { new Color(22, 198, 12), ColorName16.BrightGreen },
+ { new Color(97, 214, 214), ColorName16.BrightCyan },
+ { new Color(231, 72, 86), ColorName16.BrightRed },
+ { new Color(180, 0, 158), ColorName16.BrightMagenta },
+ { new Color(249, 241, 165), ColorName16.BrightYellow },
+ { new Color(255, 255, 255), ColorName16.White }
}.ToFrozenDictionary ();
}
/// Defines the 16 legacy color names and their corresponding ANSI color codes.
- internal static FrozenDictionary ColorNameToAnsiColorMap { get; }
+ internal static FrozenDictionary ColorName16ToAnsiColorMap { get; }
- /// Reverse mapping for .
- internal static FrozenDictionary ColorNameToColorMap { get; private set; }
+ /// Reverse mapping for .
+ internal static FrozenDictionary ColorName16ToColorMap { get; private set; }
///
/// Gets or sets a that maps legacy 16-color values to the
- /// corresponding .
+ /// corresponding .
///
///
/// Setter should be called as infrequently as possible, as is
/// expensive to create.
///
- internal static FrozenDictionary ColorToNameMap
+ internal static FrozenDictionary ColorToName16Map
{
get => colorToNameMap;
set
@@ -74,7 +77,7 @@ internal static FrozenDictionary ColorToNameMap
colorToNameMap = value;
//Also be sure to set the reverse mapping
- ColorNameToColorMap = value.ToFrozenDictionary (static kvp => kvp.Value, static kvp => kvp.Key);
+ ColorName16ToColorMap = value.ToFrozenDictionary (static kvp => kvp.Value, static kvp => kvp.Key);
}
}
}
diff --git a/Terminal.Gui/Drawing/Color.ColorName.cs b/Terminal.Gui/Drawing/Color.ColorName.cs
index 71717639e5..45bc0577ce 100644
--- a/Terminal.Gui/Drawing/Color.ColorName.cs
+++ b/Terminal.Gui/Drawing/Color.ColorName.cs
@@ -8,10 +8,10 @@ namespace Terminal.Gui;
/// These colors match the 16 colors defined for ANSI escape sequences for 4-bit (16) colors.
///
/// For terminals that support 24-bit color (TrueColor), the RGB values for each of these colors can be
-/// configured using the property.
+/// configured using the property.
///
///
-public enum ColorName
+public enum ColorName16
{
/// The black color. ANSI escape sequence: \u001b[30m.
Black,
diff --git a/Terminal.Gui/Drawing/Color.Formatting.cs b/Terminal.Gui/Drawing/Color.Formatting.cs
index 406ceff4b1..dd775fe049 100644
--- a/Terminal.Gui/Drawing/Color.Formatting.cs
+++ b/Terminal.Gui/Drawing/Color.Formatting.cs
@@ -97,49 +97,49 @@ public string ToString (
)
{
return (formatString, formatProvider) switch
- {
- // Null or empty string and null formatProvider - Revert to 'g' case behavior
- (null or { Length: 0 }, null) => ToString (),
-
- // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
- (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
-
- // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
- (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) =>
- string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
-
- // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
- (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) =>
- $"#{R:X2}{G:X2}{B:X2}",
-
- // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
- ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
-
- // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
- (['g'], null) => ToString (),
-
- // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
- (['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
-
- // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
- (['d'], null) => $"rgb({R:D},{G:D},{B:D})",
-
- // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
- (['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
-
- // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
- ({ }, _) => string.Format (
- formatProvider ?? CultureInfo.InvariantCulture,
- CompositeFormat.Parse (formatString),
- R,
- G,
- B,
- A
- ),
- _ => throw new InvalidOperationException (
- $"Unable to create string from Color with value {Argb}, using format string {formatString}"
- )
- }
+ {
+ // Null or empty string and null formatProvider - Revert to 'g' case behavior
+ (null or { Length: 0 }, null) => ToString (),
+
+ // Null or empty string and formatProvider is an ICustomColorFormatter - Output according to the given ICustomColorFormatted, with R, G, B, and A as typed arguments
+ (null or { Length: 0 }, ICustomColorFormatter ccf) => ccf.Format (null, R, G, B, A),
+
+ // Null or empty string and formatProvider is otherwise non-null but not the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
+ (null or { Length: 0 }, { }) when !Equals (formatProvider, CultureInfo.InvariantCulture) =>
+ string.Format (formatProvider, formatString ?? string.Empty, R, G, B, A),
+
+ // Null or empty string and formatProvider is the invariant culture - Output according to string.Format with the given IFormatProvider and R, G, B, and A as boxed arguments, with string.Empty as the format string
+ (null or { Length: 0 }, { }) when Equals (formatProvider, CultureInfo.InvariantCulture) =>
+ $"#{R:X2}{G:X2}{B:X2}",
+
+ // Non-null string and non-null formatProvider - let formatProvider handle it and give it R, G, B, and A
+ ({ }, { }) => string.Format (formatProvider, CompositeFormat.Parse (formatString), R, G, B, A),
+
+ // g format string and null formatProvider - Output as 24-bit hex according to invariant culture rules from R, G, and B
+ ( ['g'], null) => ToString (),
+
+ // G format string and null formatProvider - Output as 32-bit hex according to invariant culture rules from Argb
+ ( ['G'], null) => $"#{A:X2}{R:X2}{G:X2}{B:X2}",
+
+ // d format string and null formatProvider - Output as 24-bit decimal rgb(r,g,b) according to invariant culture rules from R, G, and B
+ ( ['d'], null) => $"rgb({R:D},{G:D},{B:D})",
+
+ // D format string and null formatProvider - Output as 32-bit decimal rgba(r,g,b,a) according to invariant culture rules from R, G, B, and A. Non-standard: a is a decimal byte value.
+ ( ['D'], null) => $"rgba({R:D},{G:D},{B:D},{A:D})",
+
+ // All other cases (formatString is not null here) - Delegate to formatProvider, first, and otherwise to invariant culture, and try to format the provided string from the channels
+ ({ }, _) => string.Format (
+ formatProvider ?? CultureInfo.InvariantCulture,
+ CompositeFormat.Parse (formatString),
+ R,
+ G,
+ B,
+ A
+ ),
+ _ => throw new InvalidOperationException (
+ $"Unable to create string from Color with value {Argb}, using format string {formatString}"
+ )
+ }
?? throw new InvalidOperationException (
$"Unable to create string from Color with value {Argb}, using format string {formatString}"
);
@@ -205,7 +205,7 @@ public bool TryFormat (
/// Converts the provided to a new value.
///
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
- /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string values.
+ /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string values.
///
///
/// If specified and not , will be passed to
@@ -246,7 +246,7 @@ public static Color Parse (string? text, IFormatProvider? formatProvider = null)
/// | | | | | | | | | | | | |
///
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#RGBA", "#AARRGGBB", "rgb(r,g,b)",
- /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string values.
+ /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string values.
///
///
/// Optional to provide parsing services for the input text.
@@ -265,95 +265,95 @@ public static Color Parse (string? text, IFormatProvider? formatProvider = null)
public static Color Parse (ReadOnlySpan text, IFormatProvider? formatProvider = null)
{
return text switch
- {
- // Null string or empty span provided
- { IsEmpty: true } when formatProvider is null => throw new ColorParseException (
- in text,
- "The text provided was null or empty.",
- in text
- ),
-
- // A valid ICustomColorFormatter was specified and the text wasn't null or empty
- { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
-
- // Input string is only whitespace
- { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
- in text,
- "The text provided consisted of only whitespace characters.",
- in text
- ),
-
- // Any string too short to possibly be any supported format.
- { Length: > 0 and < 3 } => throw new ColorParseException (
- in text,
- "Text was too short to be any possible supported format.",
- in text
- ),
-
- // The various hexadecimal cases
- ['#', ..] hexString => hexString switch
- {
- // #RGB
- ['#', var rChar, var gChar, var bChar] chars when chars [1..]
- .IsAllAsciiHexDigits () =>
- new Color (
- byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
- byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
- byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
- ),
-
- // #ARGB
- ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
- .IsAllAsciiHexDigits () =>
- new Color (
- byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
- byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
- byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
- byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
- ),
-
- // #RRGGBB
- [
- '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
- var b2Char
- ] chars when chars [1..].IsAllAsciiHexDigits () =>
- new Color (
- byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
- byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
- byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
- ),
-
- // #AARRGGBB
- [
- '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
- var g2Char, var b1Char, var b2Char
- ] chars when chars [1..].IsAllAsciiHexDigits () =>
- new Color (
- byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
- byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
- byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
- byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
- ),
- _ => throw new ColorParseException (
- in hexString,
- $"Color hex string {hexString} was not in a supported format",
- in hexString
- )
- },
-
- // rgb(r,g,b) or rgb(r,g,b,a)
- ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
-
- // rgba(r,g,b,a) or rgba(r,g,b)
- ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
-
- // Attempt to parse as a named color from the ColorName enum
- { } when char.IsLetter (text [0]) && Enum.TryParse (text, true, out ColorName colorName) =>
- new Color (colorName),
-
- // Any other input
- _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
- };
+ {
+ // Null string or empty span provided
+ { IsEmpty: true } when formatProvider is null => throw new ColorParseException (
+ in text,
+ "The text provided was null or empty.",
+ in text
+ ),
+
+ // A valid ICustomColorFormatter was specified and the text wasn't null or empty
+ { IsEmpty: false } when formatProvider is ICustomColorFormatter f => f.Parse (text),
+
+ // Input string is only whitespace
+ { Length: > 0 } when text.IsWhiteSpace () => throw new ColorParseException (
+ in text,
+ "The text provided consisted of only whitespace characters.",
+ in text
+ ),
+
+ // Any string too short to possibly be any supported format.
+ { Length: > 0 and < 3 } => throw new ColorParseException (
+ in text,
+ "Text was too short to be any possible supported format.",
+ in text
+ ),
+
+ // The various hexadecimal cases
+ ['#', ..] hexString => hexString switch
+ {
+ // #RGB
+ ['#', var rChar, var gChar, var bChar] chars when chars [1..]
+ .IsAllAsciiHexDigits () =>
+ new Color (
+ byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
+ byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
+ byte.Parse ([bChar, bChar], NumberStyles.HexNumber)
+ ),
+
+ // #ARGB
+ ['#', var aChar, var rChar, var gChar, var bChar] chars when chars [1..]
+ .IsAllAsciiHexDigits () =>
+ new Color (
+ byte.Parse ([rChar, rChar], NumberStyles.HexNumber),
+ byte.Parse ([gChar, gChar], NumberStyles.HexNumber),
+ byte.Parse ([bChar, bChar], NumberStyles.HexNumber),
+ byte.Parse ([aChar, aChar], NumberStyles.HexNumber)
+ ),
+
+ // #RRGGBB
+ [
+ '#', var r1Char, var r2Char, var g1Char, var g2Char, var b1Char,
+ var b2Char
+ ] chars when chars [1..].IsAllAsciiHexDigits () =>
+ new Color (
+ byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
+ byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
+ byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber)
+ ),
+
+ // #AARRGGBB
+ [
+ '#', var a1Char, var a2Char, var r1Char, var r2Char, var g1Char,
+ var g2Char, var b1Char, var b2Char
+ ] chars when chars [1..].IsAllAsciiHexDigits () =>
+ new Color (
+ byte.Parse ([r1Char, r2Char], NumberStyles.HexNumber),
+ byte.Parse ([g1Char, g2Char], NumberStyles.HexNumber),
+ byte.Parse ([b1Char, b2Char], NumberStyles.HexNumber),
+ byte.Parse ([a1Char, a2Char], NumberStyles.HexNumber)
+ ),
+ _ => throw new ColorParseException (
+ in hexString,
+ $"Color hex string {hexString} was not in a supported format",
+ in hexString
+ )
+ },
+
+ // rgb(r,g,b) or rgb(r,g,b,a)
+ ['r', 'g', 'b', '(', .., ')'] => ParseRgbaFormat (in text, 4),
+
+ // rgba(r,g,b,a) or rgba(r,g,b)
+ ['r', 'g', 'b', 'a', '(', .., ')'] => ParseRgbaFormat (in text, 5),
+
+ // Attempt to parse as a named color from the ColorStrings resources
+ { } when char.IsLetter (text [0]) && ColorStrings.TryParseW3CColorName (text.ToString (), out Color color) =>
+ new Color (color),
+
+ // Any other input
+ _ => throw new ColorParseException (in text, "Text did not match any expected format.", in text, [])
+ };
[Pure]
[SkipLocalsInit]
@@ -372,44 +372,44 @@ static Color ParseRgbaFormat (in ReadOnlySpan originalString, in int start
switch (rangeCount)
{
case 3:
- {
- // rgba(r,g,b)
- ParseRgbValues (
- in valuesSubstring,
- in valueRanges,
- in originalString,
- out ReadOnlySpan rSpan,
- out ReadOnlySpan gSpan,
- out ReadOnlySpan bSpan
- );
-
- return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
- }
+ {
+ // rgba(r,g,b)
+ ParseRgbValues (
+ in valuesSubstring,
+ in valueRanges,
+ in originalString,
+ out ReadOnlySpan rSpan,
+ out ReadOnlySpan gSpan,
+ out ReadOnlySpan bSpan
+ );
+
+ return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan));
+ }
case 4:
- {
- // rgba(r,g,b,a)
- ParseRgbValues (
- in valuesSubstring,
- in valueRanges,
- in originalString,
- out ReadOnlySpan rSpan,
- out ReadOnlySpan gSpan,
- out ReadOnlySpan bSpan
- );
- ReadOnlySpan aSpan = valuesSubstring [valueRanges [3]];
-
- if (!aSpan.IsAllAsciiDigits ())
{
- throw new ColorParseException (
- in originalString,
- "Value was not composed entirely of decimal digits.",
- in aSpan,
- nameof (A)
- );
+ // rgba(r,g,b,a)
+ ParseRgbValues (
+ in valuesSubstring,
+ in valueRanges,
+ in originalString,
+ out ReadOnlySpan rSpan,
+ out ReadOnlySpan gSpan,
+ out ReadOnlySpan bSpan
+ );
+ ReadOnlySpan aSpan = valuesSubstring [valueRanges [3]];
+
+ if (!aSpan.IsAllAsciiDigits ())
+ {
+ throw new ColorParseException (
+ in originalString,
+ "Value was not composed entirely of decimal digits.",
+ in aSpan,
+ nameof (A)
+ );
+ }
+
+ return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
}
-
- return new Color (int.Parse (rSpan), int.Parse (gSpan), int.Parse (bSpan), int.Parse (aSpan));
- }
default:
throw new ColorParseException (
in originalString,
@@ -471,7 +471,7 @@ out ReadOnlySpan bSpan
/// Converts the provided to a new value.
///
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
- /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string
+ /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string
/// values.
///
///
@@ -501,7 +501,7 @@ out result
///
///
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
- /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string
+ /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any W3C color name."/> string
/// values.
///
///
@@ -585,17 +585,20 @@ public static bool TryParse (ReadOnlySpan utf8Text, IFormatProvider? provi
[SkipLocalsInit]
public override string ToString ()
{
- // If Values has an exact match with a named color (in _colorNames), use that.
- return ColorExtensions.ColorToNameMap.TryGetValue (this, out ColorName colorName)
- ? Enum.GetName (typeof (ColorName), colorName) ?? $"#{R:X2}{G:X2}{B:X2}"
- : // Otherwise return as an RGB hex value.
- $"#{R:X2}{G:X2}{B:X2}";
+ string? name = ColorStrings.GetW3CColorName (this);
+
+ if (name is { })
+ {
+ return name;
+ }
+
+ return $"#{R:X2}{G:X2}{B:X2}";
}
/// Converts the provided string to a new instance.
///
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
- /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string values.
+ /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the string values.
///
/// The parsed value.
/// A boolean value indicating whether parsing was successful.
diff --git a/Terminal.Gui/Drawing/Color.Operators.cs b/Terminal.Gui/Drawing/Color.Operators.cs
index 2884e042aa..832c535432 100644
--- a/Terminal.Gui/Drawing/Color.Operators.cs
+++ b/Terminal.Gui/Drawing/Color.Operators.cs
@@ -53,11 +53,11 @@ public readonly partial record struct Color
public static implicit operator Color (uint u) { return new Color (u); }
///
- /// Implicit conversion from to via lookup from
- /// .
+ /// Implicit conversion from to via lookup from
+ /// .
///
[Pure]
- public static implicit operator Color (ColorName colorName) { return ColorExtensions.ColorNameToColorMap [colorName]; }
+ public static implicit operator Color (ColorName16 colorName) { return ColorExtensions.ColorName16ToColorMap [colorName]; }
///
/// Implicit conversion from to , where (,
diff --git a/Terminal.Gui/Drawing/Color.cs b/Terminal.Gui/Drawing/Color.cs
index 5e0ccf1a81..d81254c73a 100644
--- a/Terminal.Gui/Drawing/Color.cs
+++ b/Terminal.Gui/Drawing/Color.cs
@@ -17,7 +17,7 @@ namespace Terminal.Gui;
///
///
///
-///
+///
[JsonConverter (typeof (ColorJsonConverter))]
[StructLayout (LayoutKind.Explicit)]
public readonly partial record struct Color : ISpanParsable, IUtf8SpanParsable, ISpanFormattable,
@@ -109,7 +109,7 @@ public Color (int red = 0, int green = 0, int blue = 0, int alpha = byte.MaxValu
/// Initializes a new instance of the color from a legacy 16-color named value.
/// The 16-color value.
- public Color (in ColorName colorName) { this = ColorExtensions.ColorNameToColorMap [colorName]; }
+ public Color (in ColorName16 colorName) { this = ColorExtensions.ColorName16ToColorMap [colorName]; }
///
/// Initializes a new instance of the color from string. See
@@ -131,26 +131,28 @@ public Color (string colorString)
/// Initializes a new instance of the with all channels set to 0.
public Color () { Argb = 0u; }
+ // TODO: ColorName and AnsiColorCode are only needed when a driver is in Force16Color mode and we
+ // TODO: should be able to remove these from any non-Driver-specific usages.
/// Gets or sets the 3-byte/6-character hexadecimal value for each of the legacy 16-color values.
[SerializableConfigurationProperty (Scope = typeof (SettingsScope), OmitClassName = true)]
- public static Dictionary Colors
+ public static Dictionary Colors16
{
get =>
// Transform _colorToNameMap into a Dictionary
- ColorExtensions.ColorToNameMap.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g"));
+ ColorExtensions.ColorToName16Map.ToDictionary (static kvp => kvp.Value, static kvp => kvp.Key.ToString ("g"));
set
{
// Transform Dictionary into _colorToNameMap
- ColorExtensions.ColorToNameMap = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue);
+ ColorExtensions.ColorToName16Map = value.ToFrozenDictionary (GetColorToNameMapKey, GetColorToNameMapValue);
return;
- static Color GetColorToNameMapKey (KeyValuePair kvp) { return new Color (kvp.Value); }
+ static Color GetColorToNameMapKey (KeyValuePair kvp) { return new Color (kvp.Value); }
- static ColorName GetColorToNameMapValue (KeyValuePair kvp)
+ static ColorName16 GetColorToNameMapValue (KeyValuePair kvp)
{
- return Enum.TryParse (kvp.Key.ToString (), true, out ColorName colorName)
+ return Enum.TryParse (kvp.Key.ToString (), true, out ColorName16 colorName)
? colorName
: throw new ArgumentException ($"Invalid color name: {kvp.Key}");
}
@@ -158,31 +160,31 @@ static ColorName GetColorToNameMapValue (KeyValuePair kvp)
}
///
- /// Gets the using a legacy 16-color value. will
+ /// Gets the using a legacy 16-color value. will
/// return the closest 16 color match to the true color when no exact value is found.
///
///
- /// Get returns the of the closest 24-bit color value. Set sets the RGB
+ /// Get returns the of the closest 24-bit color value. Set sets the RGB
/// value using a hard-coded map.
///
- public AnsiColorCode GetAnsiColorCode () { return ColorExtensions.ColorNameToAnsiColorMap [GetClosestNamedColor ()]; }
+ public AnsiColorCode GetAnsiColorCode () { return ColorExtensions.ColorName16ToAnsiColorMap [GetClosestNamedColor16 ()]; }
///
- /// Gets the using a legacy 16-color value.
+ /// Gets the using a legacy 16-color value.
/// will return the closest 16 color match to the true color when no exact value is found.
///
///
- /// Get returns the of the closest 24-bit color value. Set sets the RGB
+ /// Get returns the of the closest 24-bit color value. Set sets the RGB
/// value using a hard-coded map.
///
- public ColorName GetClosestNamedColor () { return GetClosestNamedColor (this); }
+ public ColorName16 GetClosestNamedColor16 () { return GetClosestNamedColor16 (this); }
///
/// Determines if the closest named to is the provided
/// .
///
///
- /// The to check if this is closer
+ /// The to check if this is closer
/// to than any other configured named color.
///
///
@@ -195,18 +197,18 @@ static ColorName GetColorToNameMapValue (KeyValuePair kvp)
///
[Pure]
[MethodImpl (MethodImplOptions.AggressiveInlining)]
- public bool IsClosestToNamedColor (in ColorName namedColor) { return GetClosestNamedColor () == namedColor; }
+ public bool IsClosestToNamedColor16 (in ColorName16 namedColor) { return GetClosestNamedColor16 () == namedColor; }
///
/// Determines if the closest named to /> is the provided
/// .
///
///
- /// The color to test against the value in
+ /// The color to test against the value in
/// .
///
///
- /// The to check if this is closer
+ /// The to check if this is closer
/// to than any other configured named color.
///
///
@@ -220,20 +222,18 @@ static ColorName GetColorToNameMapValue (KeyValuePair kvp)
///
[Pure]
[MethodImpl (MethodImplOptions.AggressiveInlining)]
- public static bool IsColorClosestToNamedColor (in Color color, in ColorName namedColor) { return color.IsClosestToNamedColor (in namedColor); }
+ public static bool IsColorClosestToNamedColor16 (in Color color, in ColorName16 namedColor) { return color.IsClosestToNamedColor16 (in namedColor); }
/// Gets the "closest" named color to this value.
///
///
/// Distance is defined here as the Euclidean distance between each color interpreted as a .
- ///
- /// The order of the values in the passed Vector3 must be
///
///
[SkipLocalsInit]
- internal static ColorName GetClosestNamedColor (Color inputColor)
+ internal static ColorName16 GetClosestNamedColor16 (Color inputColor)
{
- return ColorExtensions.ColorToNameMap.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value;
+ return ColorExtensions.ColorToName16Map.MinBy (pair => CalculateColorDistance (inputColor, pair.Key)).Value;
}
[SkipLocalsInit]
@@ -246,7 +246,7 @@ internal static ColorName GetClosestNamedColor (Color inputColor)
public Color GetHighlightColor ()
{
// TODO: This is a temporary implementation; just enough to show how it could work.
- var hsl = ColorHelper.ColorConverter.RgbToHsl(new RGB (R, G, B));
+ var hsl = ColorHelper.ColorConverter.RgbToHsl (new RGB (R, G, B));
var amount = .7;
if (hsl.L <= 5)
@@ -284,52 +284,52 @@ public Color GetDarkerColor ()
#region Legacy Color Names
/// The black color.
- public const ColorName Black = ColorName.Black;
+ public const ColorName16 Black = ColorName16.Black;
/// The blue color.
- public const ColorName Blue = ColorName.Blue;
+ public const ColorName16 Blue = ColorName16.Blue;
/// The green color.
- public const ColorName Green = ColorName.Green;
+ public const ColorName16 Green = ColorName16.Green;
/// The cyan color.
- public const ColorName Cyan = ColorName.Cyan;
+ public const ColorName16 Cyan = ColorName16.Cyan;
/// The red color.
- public const ColorName Red = ColorName.Red;
+ public const ColorName16 Red = ColorName16.Red;
/// The magenta color.
- public const ColorName Magenta = ColorName.Magenta;
+ public const ColorName16 Magenta = ColorName16.Magenta;
/// The yellow color.
- public const ColorName Yellow = ColorName.Yellow;
+ public const ColorName16 Yellow = ColorName16.Yellow;
/// The gray color.
- public const ColorName Gray = ColorName.Gray;
+ public const ColorName16 Gray = ColorName16.Gray;
/// The dark gray color.
- public const ColorName DarkGray = ColorName.DarkGray;
+ public const ColorName16 DarkGray = ColorName16.DarkGray;
/// The bright bBlue color.
- public const ColorName BrightBlue = ColorName.BrightBlue;
+ public const ColorName16 BrightBlue = ColorName16.BrightBlue;
/// The bright green color.
- public const ColorName BrightGreen = ColorName.BrightGreen;
+ public const ColorName16 BrightGreen = ColorName16.BrightGreen;
/// The bright cyan color.
- public const ColorName BrightCyan = ColorName.BrightCyan;
+ public const ColorName16 BrightCyan = ColorName16.BrightCyan;
/// The bright red color.
- public const ColorName BrightRed = ColorName.BrightRed;
+ public const ColorName16 BrightRed = ColorName16.BrightRed;
/// The bright magenta color.
- public const ColorName BrightMagenta = ColorName.BrightMagenta;
+ public const ColorName16 BrightMagenta = ColorName16.BrightMagenta;
/// The bright yellow color.
- public const ColorName BrightYellow = ColorName.BrightYellow;
+ public const ColorName16 BrightYellow = ColorName16.BrightYellow;
/// The White color.
- public const ColorName White = ColorName.White;
+ public const ColorName16 White = ColorName16.White;
#endregion
}
\ No newline at end of file
diff --git a/Terminal.Gui/Drawing/ColorScheme.cs b/Terminal.Gui/Drawing/ColorScheme.cs
index 603694eec8..2e83a4c146 100644
--- a/Terminal.Gui/Drawing/ColorScheme.cs
+++ b/Terminal.Gui/Drawing/ColorScheme.cs
@@ -15,12 +15,6 @@ namespace Terminal.Gui;
[JsonConverter (typeof (ColorSchemeJsonConverter))]
public record ColorScheme : IEqualityOperators
{
- private readonly Attribute _disabled;
- private readonly Attribute _focus;
- private readonly Attribute _hotFocus;
- private readonly Attribute _hotNormal;
- private readonly Attribute _normal;
-
/// Creates a new instance set to the default colors (see ).
public ColorScheme () : this (Attribute.Default) { }
@@ -30,22 +24,22 @@ public ColorScheme (ColorScheme? scheme)
{
ArgumentNullException.ThrowIfNull (scheme);
- _normal = scheme.Normal;
- _focus = scheme.Focus;
- _hotNormal = scheme.HotNormal;
- _disabled = scheme.Disabled;
- _hotFocus = scheme.HotFocus;
+ Normal = scheme.Normal;
+ Focus = scheme.Focus;
+ HotNormal = scheme.HotNormal;
+ Disabled = scheme.Disabled;
+ HotFocus = scheme.HotFocus;
}
/// Creates a new instance, initialized with the values from .
/// The attribute to initialize the new instance with.
public ColorScheme (Attribute attribute)
{
- _normal = attribute;
- _focus = attribute;
- _hotNormal = attribute;
- _disabled = attribute;
- _hotFocus = attribute;
+ Normal = attribute;
+ Focus = attribute;
+ HotNormal = attribute;
+ Disabled = attribute;
+ HotFocus = attribute;
}
/// Creates a new instance, initialized with the values provided.
@@ -54,48 +48,45 @@ public ColorScheme (
Attribute focus,
Attribute hotNormal,
Attribute disabled,
- Attribute hotFocus)
+ Attribute hotFocus
+ )
{
- _normal = normal;
- _focus = focus;
- _hotNormal = hotNormal;
- _disabled = disabled;
- _hotFocus = hotFocus;
+ Normal = normal;
+ Focus = focus;
+ HotNormal = hotNormal;
+ Disabled = disabled;
+ HotFocus = hotFocus;
}
/// The default foreground and background color for text when the view is disabled.
- public Attribute Disabled
- {
- get => _disabled;
- init => _disabled = value;
- }
+ public Attribute Disabled { get; init; }
/// The foreground and background color for text when the view has the focus.
- public Attribute Focus
- {
- get => _focus;
- init => _focus = value;
- }
+ public Attribute Focus { get; init; }
/// The foreground and background color for text in a focused view that indicates a .
- public Attribute HotFocus
- {
- get => _hotFocus;
- init => _hotFocus = value;
- }
+ public Attribute HotFocus { get; init; }
/// The foreground and background color for text in a non-focused view that indicates a .
- public Attribute HotNormal
- {
- get => _hotNormal;
- init => _hotNormal = value;
- }
+ public Attribute HotNormal { get; init; }
/// The foreground and background color for text when the view is not focused, hot, or disabled.
- public Attribute Normal
+ public Attribute Normal { get; init; }
+
+ ///
+ /// Gets a new with the same values as this instance, but with the foreground and background
+ /// colors adjusted to be more visible.
+ ///
+ ///
+ public ColorScheme GetHighlightColorScheme ()
{
- get => _normal;
- init => _normal = value;
+ return this with
+ {
+ Normal = new (Normal.Foreground.GetHighlightColor (), Normal.Background),
+ HotNormal = new (HotNormal.Foreground.GetHighlightColor (), HotNormal.Background),
+ Focus = new (Focus.Foreground.GetHighlightColor (), Focus.Background),
+ HotFocus = new (HotFocus.Foreground.GetHighlightColor (), HotFocus.Background)
+ };
}
/// Compares two objects for equality.
@@ -104,20 +95,17 @@ public Attribute Normal
public virtual bool Equals (ColorScheme? other)
{
return other is { }
- && EqualityComparer.Default.Equals (_normal, other._normal)
- && EqualityComparer.Default.Equals (_focus, other._focus)
- && EqualityComparer.Default.Equals (_hotNormal, other._hotNormal)
- && EqualityComparer.Default.Equals (_hotFocus, other._hotFocus)
- && EqualityComparer.Default.Equals (_disabled, other._disabled);
+ && EqualityComparer.Default.Equals (Normal, other.Normal)
+ && EqualityComparer.Default.Equals (Focus, other.Focus)
+ && EqualityComparer.Default.Equals (HotNormal, other.HotNormal)
+ && EqualityComparer.Default.Equals (HotFocus, other.HotFocus)
+ && EqualityComparer.Default.Equals (Disabled, other.Disabled);
}
/// Returns a hashcode for this instance.
/// hashcode for this instance
- public override int GetHashCode ()
- {
- return HashCode.Combine (_normal, _focus, _hotNormal, _hotFocus, _disabled);
- }
+ public override int GetHashCode () { return HashCode.Combine (Normal, Focus, HotNormal, HotFocus, Disabled); }
///
public override string ToString () { return $"Normal: {Normal}; Focus: {Focus}; HotNormal: {HotNormal}; HotFocus: {HotFocus}; Disabled: {Disabled}"; }
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/Drawing/ColorStrings.cs b/Terminal.Gui/Drawing/ColorStrings.cs
index a6b90d8002..79ca9f357e 100644
--- a/Terminal.Gui/Drawing/ColorStrings.cs
+++ b/Terminal.Gui/Drawing/ColorStrings.cs
@@ -1,6 +1,7 @@
#nullable enable
using System.Collections;
using System.Globalization;
+using System.Resources;
using Terminal.Gui.Resources;
namespace Terminal.Gui;
@@ -10,6 +11,8 @@ namespace Terminal.Gui;
///
public static class ColorStrings
{
+ // PERFORMANCE: See https://stackoverflow.com/a/15521524/297526 for why GlobalResources.GetString is fast.
+
///
/// Gets the W3C standard string for .
///
@@ -17,7 +20,6 @@ public static class ColorStrings
/// if there is no standard color name for the specified color.
public static string? GetW3CColorName (Color color)
{
- // Fetch the color name from the resource file
return GlobalResources.GetString ($"#{color.R:X2}{color.G:X2}{color.B:X2}", CultureInfo.CurrentUICulture);
}
@@ -27,18 +29,18 @@ public static class ColorStrings
///
public static IEnumerable GetW3CColorNames ()
{
- foreach (DictionaryEntry entry in GlobalResources.GetResourceSet (
- CultureInfo.CurrentUICulture,
- true,
- true,
- e =>
- {
- string keyName = e.Key.ToString () ?? string.Empty;
+ ResourceSet? resourceSet = GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true);
+ if (resourceSet == null)
+ {
+ yield break;
+ }
- return e.Value is string && keyName.StartsWith ('#');
- })!)
+ foreach (DictionaryEntry entry in resourceSet)
{
- yield return (entry.Value as string)!;
+ if (entry is { Value: string colorName, Key: string key } && key.StartsWith ('#'))
+ {
+ yield return colorName;
+ }
}
}
@@ -50,30 +52,31 @@ public static IEnumerable GetW3CColorNames ()
/// if was parsed successfully.
public static bool TryParseW3CColorName (string name, out Color color)
{
- // Iterate through all resource entries to find the matching color name
foreach (DictionaryEntry entry in GlobalResources.GetResourceSet (CultureInfo.CurrentUICulture, true, true)!)
{
if (entry.Value is string colorName && colorName.Equals (name, StringComparison.OrdinalIgnoreCase))
{
- // Parse the key to extract the color components
- string key = entry.Key.ToString () ?? string.Empty;
+ return TryParseColorKey (entry.Key.ToString (), out color);
+ }
+ }
- if (key.StartsWith ("#") && key.Length == 7)
- {
- if (int.TryParse (key.Substring (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r)
- && int.TryParse (key.Substring (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g)
- && int.TryParse (key.Substring (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b))
- {
- color = new (r, g, b);
+ return TryParseColorKey (name, out color);
- return true;
- }
+ bool TryParseColorKey (string? key, out Color color)
+ {
+ if (key != null && key.StartsWith ('#') && key.Length == 7)
+ {
+ if (int.TryParse (key.AsSpan (1, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int r) &&
+ int.TryParse (key.AsSpan (3, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int g) &&
+ int.TryParse (key.AsSpan (5, 2), NumberStyles.HexNumber, CultureInfo.InvariantCulture, out int b))
+ {
+ color = new Color (r, g, b);
+ return true;
}
}
- }
-
- color = default (Color);
- return false;
+ color = default (Color);
+ return false;
+ }
}
}
diff --git a/Terminal.Gui/Drawing/Glyphs.cs b/Terminal.Gui/Drawing/Glyphs.cs
index f64285f0f9..1268a576db 100644
--- a/Terminal.Gui/Drawing/Glyphs.cs
+++ b/Terminal.Gui/Drawing/Glyphs.cs
@@ -8,11 +8,14 @@
///
///
/// The default glyphs can be changed via the . Within a config.json
-/// file The Json property name is the property name prefixed with "Glyphs.".
+/// file the Json property name is the property name prefixed with "Glyphs.".
///
///
-/// The JSon property can be either a decimal number or a string. The string may be one of: - A unicode char
-/// (e.g. "☑") - A hex value in U+ format (e.g. "U+2611") - A hex value in UTF-16 format (e.g. "\\u2611")
+/// The Json property can be one of:
+/// - unicode glyph in a string (e.g. "☑")
+/// - U+hex format in a string (e.g. "U+2611")
+/// - \u format in a string (e.g. "\\u2611")
+/// - A decimal number (e.g. 97 for "a")
///
///
public class GlyphDefinitions
@@ -106,6 +109,27 @@ public class GlyphDefinitions
/// Identical To (U+226)
public Rune IdenticalTo { get; set; } = (Rune)'≡';
+ /// Move indicator. Default is Lozenge (U+25CA) - ◊.
+ public Rune Move { get; set; } = (Rune)'◊';
+
+ /// Size Horizontally indicator. Default is ┥Left Right Arrow - ↔ U+02194
+ public Rune SizeHorizontal { get; set; } = (Rune)'↔';
+
+ /// Size Vertical indicator. Default Up Down Arrow - ↕ U+02195
+ public Rune SizeVertical { get; set; } = (Rune)'↕';
+
+ /// Size Top Left indicator. North West Arrow - ↖ U+02196
+ public Rune SizeTopLeft { get; set; } = (Rune)'↖';
+
+ /// Size Top Right indicator. North East Arrow - ↗ U+02197
+ public Rune SizeTopRight { get; set; } = (Rune)'↗';
+
+ /// Size Bottom Right indicator. South East Arrow - ↘ U+02198
+ public Rune SizeBottomRight { get; set; } = (Rune)'↘';
+
+ /// Size Bottom Left indicator. South West Arrow - ↙ U+02199
+ public Rune SizeBottomLeft { get; set; } = (Rune)'↙';
+
/// Apple (non-BMP). Because snek. And because it's an example of a non-BMP surrogate pair. See Issue #2610.
public Rune Apple { get; set; } = "🍎".ToRunes () [0]; // nonBMP
@@ -440,9 +464,8 @@ public class GlyphDefinitions
#region ----------------- ShadowStyle -----------------
-
/// Shadow - Vertical Start - Left Half Block - ▌ U+0258c
- public Rune ShadowVerticalStart { get; set; } = (Rune)'▖'; // Half: '\u2596' ▖;
+ public Rune ShadowVerticalStart { get; set; } = (Rune)'▖'; // Half: '\u2596' ▖;
/// Shadow - Vertical - Left Half Block - ▌ U+0258c
public Rune ShadowVertical { get; set; } = (Rune)'▌';
diff --git a/Terminal.Gui/Drawing/LineCanvas.cs b/Terminal.Gui/Drawing/LineCanvas.cs
index 9a7365f264..235d657d98 100644
--- a/Terminal.Gui/Drawing/LineCanvas.cs
+++ b/Terminal.Gui/Drawing/LineCanvas.cs
@@ -138,7 +138,7 @@ public void AddLine (
int length,
Orientation orientation,
LineStyle style,
- Attribute? attribute = default
+ Attribute? attribute = null
)
{
_cachedViewport = Rectangle.Empty;
diff --git a/Terminal.Gui/Drawing/StraightLine.cs b/Terminal.Gui/Drawing/StraightLine.cs
index 2f36995df6..fe2ccdc1d0 100644
--- a/Terminal.Gui/Drawing/StraightLine.cs
+++ b/Terminal.Gui/Drawing/StraightLine.cs
@@ -16,7 +16,7 @@ public StraightLine (
int length,
Orientation orientation,
LineStyle style,
- Attribute? attribute = default
+ Attribute? attribute = null
)
{
Start = start;
diff --git a/Terminal.Gui/FileServices/DefaultFileOperations.cs b/Terminal.Gui/FileServices/DefaultFileOperations.cs
index 85f450fd28..9ba8592840 100644
--- a/Terminal.Gui/FileServices/DefaultFileOperations.cs
+++ b/Terminal.Gui/FileServices/DefaultFileOperations.cs
@@ -135,14 +135,14 @@ private bool Prompt (string title, string defaultText, out string result)
var confirm = false;
var btnOk = new Button { IsDefault = true, Text = Strings.btnOk };
- btnOk.Accept += (s, e) =>
+ btnOk.Accepting += (s, e) =>
{
confirm = true;
Application.RequestStop ();
};
var btnCancel = new Button { Text = Strings.btnCancel };
- btnCancel.Accept += (s, e) =>
+ btnCancel.Accepting += (s, e) =>
{
confirm = false;
Application.RequestStop ();
diff --git a/Terminal.Gui/Input/Command.cs b/Terminal.Gui/Input/Command.cs
index 42dcb16e62..43af4e906c 100644
--- a/Terminal.Gui/Input/Command.cs
+++ b/Terminal.Gui/Input/Command.cs
@@ -3,160 +3,171 @@
namespace Terminal.Gui;
-/// Actions which can be performed by the application or bound to keys in a control.
+///
+/// Actions which can be performed by a . Commands are typically invoked via
+/// and mouse events.
+///
public enum Command
{
- /// Invoked when the HotKey for the View has been pressed.
- HotKey,
+ #region Base View Commands
- /// Accepts the current state (e.g. list selection, button press, toggle, etc).
+ ///
+ /// Accepts the current state of the View (e.g. list selection, button press, checkbox state, etc.).
+ ///
+ /// The default implementation in calls . If the event is not handled,
+ /// the command is invoked on:
+ /// - Any peer-view that is a with set to .
+ /// - The . This enables default Accept behavior.
+ ///
+ ///
Accept,
- /// Selects an item (e.g. a list item or menu item) without necessarily accepting it.
- Select,
-
- /// Moves down one item (cell, line, etc...).
- LineDown,
-
- /// Extends the selection down one (cell, line, etc...).
- LineDownExtend,
+ ///
+ /// Performs a hot key action (e.g. setting focus, accepting, and/or moving focus to the next View).
+ ///
+ /// The default implementation in calls and then
+ /// .
+ ///
+ ///
+ HotKey,
- /// Moves down to the last child node of the branch that holds the current selection.
- LineDownToLastBranch,
+ ///
+ /// Selects the View or an item in the View (e.g. a list item or menu item) without necessarily accepting it.
+ ///
+ /// The default implementation in calls .
+ ///
+ ///
+ Select,
- /// Scrolls down one (cell, line, etc...) (without changing the selection).
- ScrollDown,
+ #endregion
- // --------------------------------------------------------------------
+ #region Movement Commands
/// Moves up one (cell, line, etc...).
- LineUp,
-
- /// Extends the selection up one item (cell, line, etc...).
- LineUpExtend,
-
- /// Moves up to the first child node of the branch that holds the current selection.
- LineUpToFirstBranch,
+ Up,
- /// Scrolls up one item (cell, line, etc...) (without changing the selection).
- ScrollUp,
+ /// Moves down one item (cell, line, etc...).
+ Down,
///
- /// Moves the selection left one by the minimum increment supported by the e.g. single
- /// character, cell, item etc.
+ /// Moves left one (cell, line, etc...).
///
Left,
- /// Scrolls one item (cell, character, etc...) to the left
- ScrollLeft,
-
- ///
- /// Extends the selection left one by the minimum increment supported by the view e.g. single character, cell,
- /// item etc.
- ///
- LeftExtend,
-
///
- /// Moves the selection right one by the minimum increment supported by the view e.g. single character, cell, item
- /// etc.
+ /// Moves right one (cell, line, etc...).
///
Right,
- /// Scrolls one item (cell, character, etc...) to the right.
- ScrollRight,
+ /// Move one page up.
+ PageUp,
- ///
- /// Extends the selection right one by the minimum increment supported by the view e.g. single character, cell,
- /// item etc.
- ///
- RightExtend,
+ /// Move one page down.
+ PageDown,
- /// Moves the caret to the start of the previous word.
- WordLeft,
+ /// Moves to the left page.
+ PageLeft,
- /// Extends the selection to the start of the previous word.
- WordLeftExtend,
+ /// Moves to the right page.
+ PageRight,
- /// Moves the caret to the start of the next word.
- WordRight,
+ /// Moves to the top of page.
+ StartOfPage,
- /// Extends the selection to the start of the next word.
- WordRightExtend,
+ /// Moves to the bottom of page.
+ EndOfPage,
- /// Cuts to the clipboard the characters from the current position to the end of the line.
- CutToEndLine,
+ /// Moves to the start (e.g. the top or home).
+ Start,
- /// Cuts to the clipboard the characters from the current position to the start of the line.
- CutToStartLine,
+ /// Moves to the end (e.g. the bottom).
+ End,
- /// Deletes the characters forwards.
- KillWordForwards,
+ /// Moves left to the start on the current row/line.
+ LeftStart,
- /// Deletes the characters backwards.
- KillWordBackwards,
+ /// Moves right to the end on the current row/line.
+ RightEnd,
+
+ /// Moves to the start of the previous word.
+ WordLeft,
+
+ /// Moves the start of the next word.
+ WordRight,
+
+ #endregion
+
+ #region Movement With Extension Commands
+
+ /// Extends the selection up one item (cell, line, etc...).
+ UpExtend,
+
+ /// Extends the selection down one (cell, line, etc...).
+ DownExtend,
///
- /// Toggles overwrite mode such that newly typed text overwrites the text that is already there (typically
- /// associated with the Insert key).
+ /// Extends the selection left one item (cell, line, etc...)
///
- ToggleOverwrite,
+ LeftExtend,
///
- /// Enables overwrite mode such that newly typed text overwrites the text that is already there (typically
- /// associated with the Insert key).
+ /// Extends the selection right one item (cell, line, etc...)
///
- EnableOverwrite,
+ RightExtend,
- /// Disables overwrite mode ()
- DisableOverwrite,
+ /// Extends the selection to the start of the previous word.
+ WordLeftExtend,
- /// Move one page down.
- PageDown,
+ /// Extends the selection to the start of the next word.
+ WordRightExtend,
/// Move one page down extending the selection to cover revealed objects/characters.
PageDownExtend,
- /// Move one page up.
- PageUp,
-
/// Move one page up extending the selection to cover revealed objects/characters.
PageUpExtend,
- /// Moves to the top/home.
- TopHome,
+ /// Extends the selection to start (e.g. home or top).
+ StartExtend,
- /// Extends the selection to the top/home.
- TopHomeExtend,
+ /// Extends the selection to end (e.g. bottom).
+ EndExtend,
- /// Moves to the bottom/end.
- BottomEnd,
+ /// Extends the selection to the start on the current row/line.
+ LeftStartExtend,
- /// Extends the selection to the bottom/end.
- BottomEndExtend,
+ /// Extends the selection to the right on the current row/line.
+ RightEndExtend,
- /// Open the selected item.
- OpenSelectedItem,
+ /// Toggles the selection.
+ ToggleExtend,
- /// Toggles the Expanded or collapsed state of a list or item (with subitems).
- ToggleExpandCollapse,
+ #endregion
- /// Expands a list or item (with subitems).
- Expand,
+ #region Editing Commands
- /// Recursively Expands all child items and their child items (if any).
- ExpandAll,
+ /// Deletes the characters forwards.
+ KillWordForwards,
- /// Collapses a list or item (with subitems).
- Collapse,
+ /// Deletes the characters backwards.
+ KillWordBackwards,
- /// Recursively collapses a list items of their children (if any).
- CollapseAll,
+ ///
+ /// Toggles overwrite mode such that newly typed text overwrites the text that is already there (typically
+ /// associated with the Insert key).
+ ///
+ ToggleOverwrite,
- /// Cancels an action or any temporary states on the control e.g. expanding a combo list.
- Cancel,
+ // QUESTION: What is the difference between EnableOverwrite and ToggleOverwrite?
- /// Unix emulation.
- UnixEmulation,
+ ///
+ /// Enables overwrite mode such that newly typed text overwrites the text that is already there (typically
+ /// associated with the Insert key).
+ ///
+ EnableOverwrite,
+
+ /// Disables overwrite mode ()
+ DisableOverwrite,
/// Deletes the character on the right.
DeleteCharRight,
@@ -170,41 +181,41 @@ public enum Command
/// Deletes all objects.
DeleteAll,
- /// Moves the cursor to the start of line.
- StartOfLine,
+ /// Inserts a new item.
+ NewLine,
- /// Extends the selection to the start of line.
- StartOfLineExtend,
+ /// Unix emulation.
+ UnixEmulation,
- /// Moves the cursor to the end of line.
- EndOfLine,
+ #endregion
- /// Extends the selection to the end of line.
- EndOfLineExtend,
+ #region Tree Commands
- /// Moves the cursor to the top of page.
- StartOfPage,
+ /// Moves down to the last child node of the branch that holds the current selection.
+ LineDownToLastBranch,
- /// Moves the cursor to the bottom of page.
- EndOfPage,
+ /// Moves up to the first child node of the branch that holds the current selection.
+ LineUpToFirstBranch,
- /// Moves to the left page.
- PageLeft,
+ #endregion
- /// Moves to the right page.
- PageRight,
+ #region Scroll Commands
- /// Moves to the left begin.
- LeftHome,
+ /// Scrolls down one (cell, line, etc...).
+ ScrollDown,
- /// Extends the selection to the left begin.
- LeftHomeExtend,
+ /// Scrolls up one item (cell, line, etc...).
+ ScrollUp,
- /// Moves to the right end.
- RightEnd,
+ /// Scrolls one item (cell, character, etc...) to the left.
+ ScrollLeft,
- /// Extends the selection to the right end.
- RightEndExtend,
+ /// Scrolls one item (cell, character, etc...) to the right.
+ ScrollRight,
+
+ #endregion
+
+ #region Clipboard Commands
/// Undo changes.
Undo,
@@ -221,35 +232,27 @@ public enum Command
/// Pastes the current selection.
Paste,
- /// TODO: IRunnable: Rename to Command.Quit to make more generic.
- /// Quit a .
- QuitToplevel,
-
- /// TODO: Overlapped: Add Command.ShowHide
-
- /// Suspend an application (Only implemented in ).
- Suspend,
+ /// Cuts to the clipboard the characters from the current position to the end of the line.
+ CutToEndLine,
- /// Moves focus to the next view.
- NextView,
+ /// Cuts to the clipboard the characters from the current position to the start of the line.
+ CutToStartLine,
- /// Moves focus to the previous view.
- PreviousView,
+ #endregion
- /// Moves focus to the next view or Toplevel (case of Overlapped).
- NextViewOrTop,
+ #region Navigation Commands
- /// Moves focus to the next previous or Toplevel (case of Overlapped).
- PreviousViewOrTop,
+ /// Moves focus to the next .
+ NextTabStop,
- /// Refresh.
- Refresh,
+ /// Moves focus to the previous .
+ PreviousTabStop,
- /// Toggles the selection.
- ToggleExtend,
+ /// Moves focus to the next .
+ NextTabGroup,
- /// Inserts a new item.
- NewLine,
+ /// Moves focus to the next.
+ PreviousTabGroup,
/// Tabs to the next item.
Tab,
@@ -257,6 +260,40 @@ public enum Command
/// Tabs back to the previous item.
BackTab,
+ #endregion
+
+ #region Action Commands
+
+ /// Toggles something (e.g. the expanded or collapsed state of a list).
+ Toggle,
+
+ /// Expands a list or item (with subitems).
+ Expand,
+
+ /// Recursively Expands all child items and their child items (if any).
+ ExpandAll,
+
+ /// Collapses a list or item (with subitems).
+ Collapse,
+
+ /// Recursively collapses a list items of their children (if any).
+ CollapseAll,
+
+ /// Cancels an action or any temporary states on the control e.g. expanding a combo list.
+ Cancel,
+
+ /// Quit.
+ Quit,
+
+ /// Refresh.
+ Refresh,
+
+ /// Suspend an application (Only implemented in ).
+ Suspend,
+
+ /// Open the selected item or invoke a UI for opening something.
+ Open,
+
/// Saves the current document.
Save,
@@ -267,5 +304,12 @@ public enum Command
New,
/// Shows context about the item (e.g. a context menu).
- ShowContextMenu
-}
+ Context,
+
+ ///
+ /// Invokes a user interface for editing or configuring something.
+ ///
+ Edit,
+
+ #endregion
+}
\ No newline at end of file
diff --git a/Terminal.Gui/Input/CommandContext.cs b/Terminal.Gui/Input/CommandContext.cs
index 5b6c235572..095a3976aa 100644
--- a/Terminal.Gui/Input/CommandContext.cs
+++ b/Terminal.Gui/Input/CommandContext.cs
@@ -11,6 +11,9 @@ namespace Terminal.Gui;
/// use .
///
///
+///
+///
+///
#pragma warning restore CS1574 // XML comment has cref attribute that could not be resolved
public record struct CommandContext
{
@@ -20,11 +23,13 @@ public record struct CommandContext
///
///
///
- public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null)
+ ///
+ public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null, object? data = null)
{
Command = command;
Key = key;
KeyBinding = keyBinding;
+ Data = data;
}
///
@@ -41,4 +46,9 @@ public CommandContext (Command command, Key? key, KeyBinding? keyBinding = null)
/// The KeyBinding that was used to invoke the , if any.
///
public KeyBinding? KeyBinding { get; set; }
+
+ ///
+ /// Arbitrary data.
+ ///
+ public object? Data { get; set; }
}
diff --git a/Terminal.Gui/Input/CommandEventArgs.cs b/Terminal.Gui/Input/CommandEventArgs.cs
new file mode 100644
index 0000000000..f12d21be89
--- /dev/null
+++ b/Terminal.Gui/Input/CommandEventArgs.cs
@@ -0,0 +1,15 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+///
+/// Event arguments for events.
+///
+public class CommandEventArgs : CancelEventArgs
+{
+ ///
+ /// The context for the command.
+ ///
+ public CommandContext Context { get; init; }
+}
diff --git a/Terminal.Gui/Input/GrabMouseEventArgs.cs b/Terminal.Gui/Input/GrabMouseEventArgs.cs
index df13d0b86c..622d33d9a2 100644
--- a/Terminal.Gui/Input/GrabMouseEventArgs.cs
+++ b/Terminal.Gui/Input/GrabMouseEventArgs.cs
@@ -1,6 +1,6 @@
namespace Terminal.Gui;
-/// Args related events.
+/// Args GrabMouse related events.
public class GrabMouseEventArgs : EventArgs
{
/// Creates a new instance of the class.
diff --git a/Terminal.Gui/Input/Key.cs b/Terminal.Gui/Input/Key.cs
index 5c92cc4898..11f8d938e2 100644
--- a/Terminal.Gui/Input/Key.cs
+++ b/Terminal.Gui/Input/Key.cs
@@ -1,6 +1,5 @@
using System.Diagnostics.CodeAnalysis;
using System.Globalization;
-using System.Text.Json.Serialization;
namespace Terminal.Gui;
@@ -80,7 +79,7 @@ public Key () : this (KeyCode.Null) { }
public Key (KeyCode k) { KeyCode = k; }
///
- /// Copy constructor.
+ /// Copy constructor.
///
/// The Key to copy
public Key (Key key)
@@ -155,18 +154,13 @@ public Key (string str)
///
public Rune AsRune => ToRune (KeyCode);
- private bool _handled = false;
-
///
/// Indicates if the current Key event has already been processed and the driver should stop notifying any other
- /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside the
+ /// event subscriber. It's important to set this value to true specially when updating any View's layout from inside
+ /// the
/// subscriber method.
///
- public bool Handled
- {
- get => _handled;
- set => _handled = value;
- }
+ public bool Handled { get; set; }
/// Gets a value indicating whether the Alt key was pressed (real or synthesized)
/// if is alternate; otherwise, .
@@ -339,11 +333,11 @@ public static Rune ToRune (KeyCode key)
switch (baseKey)
{
case >= KeyCode.A and <= KeyCode.Z when !key.HasFlag (KeyCode.ShiftMask):
- return new Rune ((uint)(baseKey + 32));
+ return new ((uint)(baseKey + 32));
case >= KeyCode.A and <= KeyCode.Z when key.HasFlag (KeyCode.ShiftMask):
- return new Rune ((uint)baseKey);
+ return new ((uint)baseKey);
case > KeyCode.Null and < KeyCode.A:
- return new Rune ((uint)baseKey);
+ return new ((uint)baseKey);
}
if (Enum.IsDefined (typeof (KeyCode), baseKey))
@@ -351,7 +345,7 @@ public static Rune ToRune (KeyCode key)
return default (Rune);
}
- return new Rune ((uint)baseKey);
+ return new ((uint)baseKey);
}
#region Operators
@@ -381,17 +375,17 @@ public static Rune ToRune (KeyCode key)
/// Cast to a .
///
- public static implicit operator Key (KeyCode keyCode) { return new Key (keyCode); }
+ public static implicit operator Key (KeyCode keyCode) { return new (keyCode); }
/// Cast to a .
/// See for more information.
///
- public static implicit operator Key (char ch) { return new Key (ch); }
+ public static implicit operator Key (char ch) { return new (ch); }
/// Cast to a .
/// See for more information.
///
- public static implicit operator Key (string str) { return new Key (str); }
+ public static implicit operator Key (string str) { return new (str); }
/// Cast a to a .
/// See for more information.
@@ -399,10 +393,7 @@ public static Rune ToRune (KeyCode key)
public static implicit operator string (Key key) { return key.ToString (); }
///
- public override bool Equals (object obj)
- {
- return obj is Key k && k.KeyCode == KeyCode && k.Handled == Handled;
- }
+ public override bool Equals (object obj) { return obj is Key k && k.KeyCode == KeyCode && k.Handled == Handled; }
bool IEquatable.Equals (Key other) { return Equals (other); }
@@ -568,7 +559,10 @@ private static string TrimEndSeparator (string input, Rune separator)
/// Converts the provided string to a new instance.
///
/// The text to analyze. Formats supported are "Ctrl+X", "Alt+X", "Shift+X", "Ctrl+Alt+X",
- /// "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", and "X".
+ /// "Ctrl+Shift+X", "Alt+Shift+X", "Ctrl+Alt+Shift+X", "X", and "120" (Unicode codepoint).
+ ///
+ /// The separator can be any character, not just (e.g. "Ctrl@Alt@X").
+ ///
///
/// The parsed value.
/// A boolean value indicating whether parsing was successful.
@@ -577,38 +571,88 @@ public static bool TryParse (string text, [NotNullWhen (true)] out Key key)
{
if (string.IsNullOrEmpty (text))
{
- key = Key.Empty;
+ key = Empty;
return true;
}
+ switch (text)
+ {
+ case "Ctrl":
+ key = KeyCode.CtrlMask;
+
+ return true;
+ case "Alt":
+ key = KeyCode.AltMask;
+
+ return true;
+ case "Shift":
+ key = KeyCode.ShiftMask;
+
+ return true;
+ }
+
key = null;
- // Split the string into parts
- string [] parts = text.Split ('+', '-', (char)Separator.Value);
+ Rune separator = Separator;
- if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
+ // Perhaps the separator was written using a different Key.Separator? Does the string
+ // start with "Ctrl", "Alt" or "Shift"? If so, get the char after the modifier string and use that as the separator.
+ if (text.StartsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
+ {
+ separator = (Rune)text [4];
+ }
+ else if (text.StartsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
+ {
+ separator = (Rune)text [3];
+ }
+ else if (text.StartsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
+ {
+ separator = (Rune)text [5];
+ }
+ else if (text.EndsWith ("Ctrl", StringComparison.InvariantCultureIgnoreCase))
{
+ separator = (Rune)text [^5];
+ }
+ else if (text.EndsWith ("Alt", StringComparison.InvariantCultureIgnoreCase))
+ {
+ separator = (Rune)text [^4];
+ }
+ else if (text.EndsWith ("Shift", StringComparison.InvariantCultureIgnoreCase))
+ {
+ separator = (Rune)text [^6];
+ }
+
+ // Split the string into parts using the set Separator
+ string [] parts = text.Split ((char)separator.Value);
+
+ if (parts.Length is > 4)
+ {
+ // Invalid
return false;
}
- // if it's just a shift key
- if (parts.Length == 1)
+ // e.g. "Ctrl++"
+ if ((Rune)text [^1] != separator && parts.Any (string.IsNullOrEmpty))
{
- switch (parts [0])
- {
- case "Ctrl":
- key = KeyCode.CtrlMask;
+ // Invalid
+ return false;
+ }
- return true;
- case "Alt":
- key = KeyCode.AltMask;
+ if ((Rune)text [^1] == separator)
+ {
+ parts [^1] = separator.Value.ToString ();
+ key = (char)separator.Value;
+ }
- return true;
- case "Shift":
- key = KeyCode.ShiftMask;
+ if (separator != Separator && (parts.Length is 1 || (key is { } && parts.Length is 2)))
+ {
+ parts = text.Split ((char)separator.Value);
- return true;
+ if (parts.Length is 0 or > 4 || parts.Any (string.IsNullOrEmpty))
+ {
+ // Invalid
+ return false;
}
}
@@ -649,12 +693,12 @@ out parsedInt
{
if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
{
- key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+ key = new (parsedKeyCode | KeyCode.ShiftMask);
return true;
}
- key = new Key (parsedKeyCode | modifiers);
+ key = new (parsedKeyCode | modifiers);
return true;
}
@@ -664,7 +708,8 @@ out parsedInt
{
keyCode = keyCode & ~KeyCode.Space;
}
- key = new Key (keyCode | modifiers);
+
+ key = new (keyCode | modifiers);
return true;
}
@@ -675,7 +720,7 @@ out parsedInt
{
if (parsedKeyCode is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
{
- key = new Key (parsedKeyCode | KeyCode.ShiftMask);
+ key = new (parsedKeyCode | KeyCode.ShiftMask);
return true;
}
@@ -684,7 +729,8 @@ out parsedInt
{
parsedKeyCode = parsedKeyCode & ~KeyCode.Space;
}
- key = new Key (parsedKeyCode | modifiers);
+
+ key = new (parsedKeyCode | modifiers);
return true;
}
@@ -705,12 +751,12 @@ out parsedInt
if ((KeyCode)parsedInt is >= KeyCode.A and <= KeyCode.Z && modifiers == 0)
{
- key = new Key ((KeyCode)parsedInt | KeyCode.ShiftMask);
+ key = new ((KeyCode)parsedInt | KeyCode.ShiftMask);
return true;
}
- key = new Key ((KeyCode)parsedInt);
+ key = new ((KeyCode)parsedInt);
return true;
}
@@ -722,7 +768,7 @@ out parsedInt
if (GetIsKeyCodeAtoZ (parsedKeyCode))
{
- key = new Key (parsedKeyCode | (modifiers & ~KeyCode.Space));
+ key = new (parsedKeyCode | (modifiers & ~KeyCode.Space));
return true;
}
diff --git a/Terminal.Gui/Input/KeyBinding.cs b/Terminal.Gui/Input/KeyBinding.cs
index 8b5a4201d1..0073a483ae 100644
--- a/Terminal.Gui/Input/KeyBinding.cs
+++ b/Terminal.Gui/Input/KeyBinding.cs
@@ -8,6 +8,9 @@ namespace Terminal.Gui;
///
/// Provides a collection of objects that are scoped to .
///
+///
+///
+///
public record struct KeyBinding
{
/// Initializes a new instance.
diff --git a/Terminal.Gui/Input/KeyBindingScope.cs b/Terminal.Gui/Input/KeyBindingScope.cs
index 633e6d7b0b..4c14fecd57 100644
--- a/Terminal.Gui/Input/KeyBindingScope.cs
+++ b/Terminal.Gui/Input/KeyBindingScope.cs
@@ -1,6 +1,4 @@
-
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
///
/// Defines the scope of a that has been bound to a key with
@@ -9,41 +7,52 @@ namespace Terminal.Gui;
///
/// Key bindings are scoped to the most-focused view () by default.
///
+///
+///
+///
[Flags]
-
public enum KeyBindingScope
{
/// The key binding is disabled.
Disabled = 0,
- /// The key binding is scoped to just the view that has focus.
+ ///
+ /// The key binding is scoped to just the view that has focus.
+ ///
+ ///
+ ///
+ ///
Focused = 1,
///
- /// The key binding is scoped to the View's Superview hierarchy and will be triggered even when the View does not have
+ /// The key binding is scoped to the View's Superview hierarchy and the bound s will be invoked
+ /// even when the View does not have
/// focus, as
- /// long as the SuperView does have focus. This is typically used for s.
- ///
- ///
- /// The View must be visible.
- ///
- ///
- /// HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
- /// any of its subviews.
- ///
- ///
+ /// long as some View up the SuperView hierachy does have focus. This is typically used for s.
+ ///
+ /// The View must be visible.
+ ///
+ ///
+ /// HotKey-scoped key bindings are only invoked if the key down event was not handled by the focused view or
+ /// any of its subviews.
+ ///
///
+ ///
+ ///
HotKey = 2,
///
- /// The key binding will be triggered regardless of which view has focus. This is typically used for global
+ /// The and the bound s will be invoked regardless of which View has focus. This is typically used
+ /// for global
/// commands, which are called Shortcuts.
- ///
- ///
+ ///
+ /// The View does not need to be visible.
+ ///
///
/// Application-scoped key bindings are only invoked if the key down event was not handled by the focused view or
/// any of its subviews, and if the key was not bound to a .
///
- ///
- Application = 4,
+ ///
+ ///
+ Application = 4
}
diff --git a/Terminal.Gui/Input/KeyBindings.cs b/Terminal.Gui/Input/KeyBindings.cs
index 4df4b6e322..e2f0ed8beb 100644
--- a/Terminal.Gui/Input/KeyBindings.cs
+++ b/Terminal.Gui/Input/KeyBindings.cs
@@ -5,6 +5,9 @@ namespace Terminal.Gui;
///
/// Provides a collection of objects bound to a .
///
+///
+///
+///
public class KeyBindings
{
///
@@ -284,12 +287,21 @@ public Command [] GetCommands (Key key)
return Array.Empty ();
}
- /// Gets the Key used by a set of commands.
- ///
+ /// Gets the first Key bound to the set of commands specified by .
/// The set of commands to search.
- /// The used by a
- /// If no matching set of commands was found.
- public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
+ /// The first bound to the set of commands specified by . if the set of caommands was not found.
+ public Key? GetKeyFromCommands (params Command [] commands)
+ {
+ return Bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
+ }
+
+ /// Gets Keys bound to the set of commands specified by .
+ /// The set of commands to search.
+ /// The s bound to the set of commands specified by . An empty list if the set of caommands was not found.
+ public IEnumerable GetKeysFromCommands (params Command [] commands)
+ {
+ return Bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key);
+ }
/// Removes a from the collection.
///
diff --git a/Terminal.Gui/Resources/Strings.Designer.cs b/Terminal.Gui/Resources/Strings.Designer.cs
index a7bdd6ca9a..2befd2737b 100644
--- a/Terminal.Gui/Resources/Strings.Designer.cs
+++ b/Terminal.Gui/Resources/Strings.Designer.cs
@@ -195,6 +195,15 @@ internal static string _00FFFF {
}
}
+ ///
+ /// Looks up a localized string similar to BrightGreen.
+ ///
+ internal static string _16C60C {
+ get {
+ return ResourceManager.GetString("#16C60C", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to MidnightBlue.
///
@@ -258,6 +267,15 @@ internal static string _32CD32 {
}
}
+ ///
+ /// Looks up a localized string similar to BrightBlue.
+ ///
+ internal static string _3B78FF {
+ get {
+ return ResourceManager.GetString("#3B78FF", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to MediumSeaGreen.
///
@@ -339,6 +357,15 @@ internal static string _5F9EA0 {
}
}
+ ///
+ /// Looks up a localized string similar to BrightCyan.
+ ///
+ internal static string _61D6D6 {
+ get {
+ return ResourceManager.GetString("#61D6D6", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to CornflowerBlue.
///
@@ -402,6 +429,15 @@ internal static string _708090 {
}
}
+ ///
+ /// Looks up a localized string similar to DarkGray.
+ ///
+ internal static string _767676 {
+ get {
+ return ResourceManager.GetString("#767676", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to LightSlateGrey.
///
@@ -681,6 +717,15 @@ internal static string _B22222 {
}
}
+ ///
+ /// Looks up a localized string similar to BrightMagenta.
+ ///
+ internal static string _B4009E {
+ get {
+ return ResourceManager.GetString("#B4009E", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to DarkGoldenRod.
///
@@ -870,6 +915,15 @@ internal static string _E6E6FA {
}
}
+ ///
+ /// Looks up a localized string similar to BrightRed.
+ ///
+ internal static string _E74856 {
+ get {
+ return ResourceManager.GetString("#E74856", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to DarkSalmon.
///
@@ -996,6 +1050,15 @@ internal static string _F8F8FF {
}
}
+ ///
+ /// Looks up a localized string similar to BrightYellow.
+ ///
+ internal static string _F9F1A5 {
+ get {
+ return ResourceManager.GetString("#F9F1A5", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to Salmon.
///
@@ -1374,6 +1437,15 @@ internal static string btnYes {
}
}
+ ///
+ /// Looks up a localized string similar to Co_lors.
+ ///
+ internal static string ctxColors {
+ get {
+ return ResourceManager.GetString("ctxColors", resourceCulture);
+ }
+ }
+
///
/// Looks up a localized string similar to _Copy.
///
diff --git a/Terminal.Gui/Resources/Strings.fr-FR.resx b/Terminal.Gui/Resources/Strings.fr-FR.resx
index 746c454996..c20959da40 100644
--- a/Terminal.Gui/Resources/Strings.fr-FR.resx
+++ b/Terminal.Gui/Resources/Strings.fr-FR.resx
@@ -117,27 +117,27 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ Tout _sélectionner
+
+
+ _Tout supprimer
+
_Copier
Co_uper
-
- _Tout supprimer
-
C_oller
-
- _Rétablir
-
-
- Tout _sélectionner
-
_Annuler
+
+ _Rétablir
+
_Dossier
@@ -168,16 +168,19 @@
Prochai_n...
+
+ Ouvrir
+
Enregistrer
E_nregistrer sous
-
- Ouvrir
-
Sélecteur de Date
+
+ Cou_leurs
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.ja-JP.resx b/Terminal.Gui/Resources/Strings.ja-JP.resx
index 4a825c51c6..fa4bda4210 100644
--- a/Terminal.Gui/Resources/Strings.ja-JP.resx
+++ b/Terminal.Gui/Resources/Strings.ja-JP.resx
@@ -117,27 +117,27 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ 全て選択 (_S)
+
+
+ 全て削除 (_D)
+
コピー (_C)
切り取り (_T)
-
- 全て削除 (_D)
-
貼り付け (_P)
-
- やり直し (_R)
-
-
- 全て選択 (_S)
-
元に戻す (_U)
+
+ やり直し (_R)
+
ディレクトリ
@@ -171,44 +171,47 @@
同じ名前のディレクトリはすでに存在しました
-
- “{0}”を削除もよろしいですか?この操作は元に戻りません
-
-
- タイプ
+
+ すでに存在したディレクトリを選択してください
-
- サイズ
+
+ 同じ名前のファイルはすでに存在しました
-
- パスを入力
+
+ すでに存在したファイルを選択してください
ファイル名
-
- 新規ディレクトリ
-
-
- いいえ (_N)
-
-
- はい (_Y)
+
+ すでに存在したファイルまたはディレクトリを選択してください
変更日時
-
- すでに存在したファイルまたはディレクトリを選択してください
+
+ パスを入力
-
- すでに存在したディレクトリを選択してください
+
+ 検索を入力
-
- すでに存在したファイルを選択してください
+
+ サイズ
-
- 名前:
+
+ タイプ
+
+
+ ファイルタイプが間違っでいます
+
+
+ 任意ファイル
+
+
+ “{0}”を削除もよろしいですか?この操作は元に戻りません
+
+
+ 削除失敗
{0} を削除
@@ -216,35 +219,26 @@
新規失敗
-
- 既存
+
+ 新規ディレクトリ
-
- 名前を変更
+
+ いいえ (_N)
変更失敗
-
- 削除失敗
-
-
- 同じ名前のファイルはすでに存在しました
-
-
- 検索を入力
-
-
- ファイルタイプが間違っでいます
+
+ 名前:
-
- 任意ファイル
+
+ 名前を変更
-
- キャンセル (_C)
+
+ はい (_Y)
-
- OK (_O)
+
+ 既存
開く (_O)
@@ -255,6 +249,12 @@
名前を付けて保存 (_S)
+
+ OK (_O)
+
+
+ キャンセル (_C)
+
削除 (_D)
@@ -276,4 +276,7 @@
日付ピッカー
+
+ 絵の具 (_L)
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.pt-PT.resx b/Terminal.Gui/Resources/Strings.pt-PT.resx
index 2cffa6cd7e..28aabf522c 100644
--- a/Terminal.Gui/Resources/Strings.pt-PT.resx
+++ b/Terminal.Gui/Resources/Strings.pt-PT.resx
@@ -117,27 +117,27 @@
System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+ _Selecionar Tudo
+
+
+ _Apagar Tudo
+
_Copiar
Cor_tar
-
- _Apagar Tudo
-
Co_lar
-
- _Refazer
-
-
- _Selecionar Tudo
-
_Desfazer
+
+ _Refazer
+
Diretório
@@ -168,16 +168,19 @@
S_eguir...
-
- Guardar como
+
+ Abrir
Guardar
-
- Abrir
+
+ Guardar como
Seletor de Data
+
+ Co_res
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.resx b/Terminal.Gui/Resources/Strings.resx
index 1ce166e149..9333d71578 100644
--- a/Terminal.Gui/Resources/Strings.resx
+++ b/Terminal.Gui/Resources/Strings.resx
@@ -1,6 +1,6 @@
-
-
-
-
+
+
+
+
+
+
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
-
+
+
+
+
+
+
+
-
-
-
- text/microsoft-resx
-
-
- 2.0
-
-
- System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
- System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
-
-
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+
+ text/microsoft-resx
+
+
+ 2.0
+
+
+ System.Resources.ResXResourceReader, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
+ System.Resources.ResXResourceWriter, System.Windows.Forms, Version=4.0.0.0, Culture=neutral, PublicKeyToken=b77a5c561934e089
+
+
_Select All
-
+
_Delete All
-
+
_Copy
-
+
Cu_t
-
+
_Paste
-
+
_Undo
-
+
_Redo
-
+
Directory
-
+
File
-
+
Save
-
+
Save as
-
+
Open
-
+
Select folder
-
+
Select Mixed
-
+
_Back
-
+
Fi_nish
-
+
_Next...
-
+
Directory already exists with that name
When trying to save a file with a name already taken by a directory
-
+
Must select an existing directory
-
+
File already exists with that name
-
+
Must select an existing file
When trying to save a directory with a name already used by a file
-
+
Filename
-
+
Must select an existing file or directory
-
+
Modified
-
+
Enter Path
-
+
Enter Search
-
+
Size
-
+
Type
-
+
Wrong file type
When trying to open/save a file that does not match the provided filter (e.g. csv)
-
+
Any Files
Describes an AllowedType that matches anything
-
+
Are you sure you want to delete '{0}'? This operation is permanent
-
+
Delete Failed
-
+
Delete {0}
-
+
New Failed
-
+
New Folder
-
+
_No
-
+
Rename Failed
-
+
Name:
-
+
Rename
-
+
_Yes
-
+
Existing
-
+
O_pen
-
+
_Save
-
+
Save _as
-
+
_OK
-
+
_Cancel
-
+
_Delete
-
+
_Hide {0}
-
+
_New
-
+
_Rename
-
+
_Sort {0} ASC
-
+
_Sort {0} DESC
-
+
Date Picker
-
+
AliceBlue
-
+
AntiqueWhite
-
+
Aquamarine
-
+
Azure
-
+
Beige
-
+
Bisque
-
+
Black
-
+
BlanchedAlmond
-
+
Blue
-
+
BlueViolet
-
+
Brown
-
+
BurlyWood
-
+
CadetBlue
-
+
Chartreuse
-
+
Chocolate
-
+
Coral
-
+
CornflowerBlue
-
+
Cornsilk
-
+
Crimson
-
+
Cyan
-
+
DarkBlue
-
+
DarkCyan
-
+
DarkGoldenRod
-
+
DarkGrey
-
+
DarkGreen
-
+
DarkKhaki
-
+
DarkMagenta
-
+
DarkOliveGreen
-
+
DarkOrange
-
+
DarkOrchid
-
+
DarkRed
-
+
DarkSalmon
-
+
DarkSeaGreen
-
+
DarkSlateBlue
-
+
DarkSlateGrey
-
+
DarkTurquoise
-
+
DarkViolet
-
+
DeepPink
-
+
DeepSkyBlue
-
+
DimGray
-
+
DodgerBlue
-
+
FireBrick
-
+
FloralWhite
-
+
ForestGreen
-
+
Gainsboro
-
+
GhostWhite
-
+
Gold
-
+
GoldenRod
-
+
Gray
-
+
Green
-
+
GreenYellow
-
+
HoneyDew
-
+
HotPink
-
+
IndianRed
-
+
Indigo
-
+
Ivory
-
+
Khaki
-
+
Lavender
-
+
LavenderBlush
-
+
LawnGreen
-
+
LemonChiffon
-
+
LightBlue
-
+
LightCoral
-
+
LightCyan
-
+
LightGoldenRodYellow
-
+
LightGray
-
+
LightGreen
-
+
LightPink
-
+
LightSalmon
-
+
LightSeaGreen
-
+
LightSkyBlue
-
+
LightSlateGrey
-
+
LightSteelBlue
-
+
LightYellow
-
+
Lime
-
+
LimeGreen
-
+
Linen
-
+
Magenta
-
+
Maroon
-
+
MediumAquaMarine
-
+
MediumBlue
-
+
MediumOrchid
-
+
MediumPurple
-
+
MediumSeaGreen
-
+
MediumSlateBlue
-
+
MediumSpringGreen
-
+
MediumTurquoise
-
+
MediumVioletRed
-
+
MidnightBlue
-
+
MintCream
-
+
MistyRose
-
+
Moccasin
-
+
NavajoWhite
-
+
Navy
-
+
OldLace
-
+
Olive
-
+
OliveDrab
-
+
Orange
-
+
OrangeRed
-
+
Orchid
-
+
PaleGoldenRod
-
+
PaleGreen
-
+
PaleTurquoise
-
+
PaleVioletRed
-
+
PapayaWhip
-
+
PeachPuff
-
+
Peru
-
+
Pink
-
+
Plum
-
+
PowderBlue
-
+
Purple
-
+
RebeccaPurple
-
+
Red
-
+
RosyBrown
-
+
RoyalBlue
-
+
SaddleBrown
-
+
Salmon
-
+
SandyBrown
-
+
SeaGreen
-
+
SeaShell
-
+
Sienna
-
+
Silver
-
+
SkyBlue
-
+
SlateBlue
-
+
SlateGray
-
+
Snow
-
+
SpringGreen
-
+
SteelBlue
-
+
Tan
-
+
Teal
-
+
Thistle
-
+
Tomato
-
+
Turquoise
-
+
Violet
-
+
Wheat
-
+
White
-
+
WhiteSmoke
-
+
Yellow
-
+
YellowGreen
+
+ BrightBlue
+
+
+ BrightCyan
+
+
+ BrightRed
+
+
+ BrightGreen
+
+
+ BrightMagenta
+
+
+ BrightYellow
+
+
+ DarkGray
+
+
+ Co_lors
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/Strings.zh-Hans.resx b/Terminal.Gui/Resources/Strings.zh-Hans.resx
index 009fdd479f..8ea63e91d6 100644
--- a/Terminal.Gui/Resources/Strings.zh-Hans.resx
+++ b/Terminal.Gui/Resources/Strings.zh-Hans.resx
@@ -153,9 +153,6 @@
打开
-
- 下一步 (_N)...
-
选择文件夹 (_S)
@@ -168,6 +165,9 @@
结束 (_N)
+
+ 下一步 (_N)...
+
已存在相同名称的目录
@@ -240,9 +240,6 @@
已有
-
- 确定 (_O)
-
打开 (_O)
@@ -252,6 +249,9 @@
另存为 (_S)
+
+ 确定 (_O)
+
取消 (_C)
@@ -276,4 +276,7 @@
日期选择器
+
+ 旗帜 (_L)
+
\ No newline at end of file
diff --git a/Terminal.Gui/Resources/config.json b/Terminal.Gui/Resources/config.json
index adba1e584b..07ba336507 100644
--- a/Terminal.Gui/Resources/config.json
+++ b/Terminal.Gui/Resources/config.json
@@ -10,8 +10,7 @@
// note that not all values here will be recreated (e.g. the Light and Dark themes and any property initialized
// null).
//
- // TODO: V2 - Reference via http
- "$schema": "../../docfx/schemas/tui-config-schema.json",
+ "$schema": "https://gui-cs.github.io/Terminal.GuiV2Docs/schemas/tui-config-schema.json",
// Set this to true in a .config file to be loaded to cause JSON parsing errors
// to throw exceptions.
@@ -22,6 +21,7 @@
"Application.NextTabGroupKey": "F6",
"Application.PrevTabGroupKey": "Shift+F6",
"Application.QuitKey": "Esc",
+ "Application.ArrangeKey": "Ctrl+F5",
"Key.Separator": "+",
"Theme": "Default",
@@ -30,34 +30,35 @@
"Default": {
"Dialog.DefaultButtonAlignment": "End",
"Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+ "Dialog.DefaultBorderStyle": "Heavy",
+ "Dialog.DefaultShadow": "Transparent",
"FrameView.DefaultBorderStyle": "Single",
"Window.DefaultBorderStyle": "Single",
- "Dialog.DefaultBorderStyle": "Heavy",
"MessageBox.DefaultButtonAlignment": "Center",
"MessageBox.DefaultBorderStyle": "Heavy",
- "Button.DefaultShadow": "None",
+ "Button.DefaultShadow": "Opaque",
"ColorSchemes": [
{
"TopLevel": {
"Normal": {
"Foreground": "BrightGreen",
- "Background": "Black"
+ "Background": "#505050" // DarkerGray
},
"Focus": {
"Foreground": "White",
- "Background": "Cyan"
+ "Background": "#696969" // DimGray
},
"HotNormal": {
"Foreground": "Yellow",
- "Background": "Black"
+ "Background": "#505050" // DarkerGray
},
"HotFocus": {
- "Foreground": "Blue",
- "Background": "Cyan"
+ "Foreground": "Yellow",
+ "Background": "#696969" // DimGray
},
"Disabled": {
"Foreground": "DarkGray",
- "Background": "Black"
+ "Background": "#505050" // DarkerGray
}
}
},
@@ -68,8 +69,8 @@
"Background": "Blue"
},
"Focus": {
- "Foreground": "Black",
- "Background": "Gray"
+ "Foreground": "DarkBlue",
+ "Background": "LightGray"
},
"HotNormal": {
"Foreground": "BrightCyan",
@@ -77,7 +78,7 @@
},
"HotFocus": {
"Foreground": "BrightBlue",
- "Background": "Gray"
+ "Background": "LightGray"
},
"Disabled": {
"Foreground": "DarkGray",
@@ -89,19 +90,19 @@
"Dialog": {
"Normal": {
"Foreground": "Black",
- "Background": "Gray"
+ "Background": "LightGray"
},
"Focus": {
- "Foreground": "White",
- "Background": "DarkGray"
+ "Foreground": "DarkGray",
+ "Background": "LightGray"
},
"HotNormal": {
"Foreground": "Blue",
- "Background": "Gray"
+ "Background": "LightGray"
},
"HotFocus": {
- "Foreground": "BrightYellow",
- "Background": "DarkGray"
+ "Foreground": "BrightBlue",
+ "Background": "LightGray"
},
"Disabled": {
"Foreground": "Gray",
@@ -113,19 +114,19 @@
"Menu": {
"Normal": {
"Foreground": "White",
- "Background": "DarkGray"
+ "Background": "DarkBlue"
},
"Focus": {
"Foreground": "White",
- "Background": "Black"
+ "Background": "Blue"
},
"HotNormal": {
- "Foreground": "BrightYellow",
- "Background": "DarkGray"
+ "Foreground": "Yellow",
+ "Background": "DarkBlue"
},
"HotFocus": {
- "Foreground": "BrightYellow",
- "Background": "Black"
+ "Foreground": "Yellow",
+ "Background": "Blue"
},
"Disabled": {
"Foreground": "Gray",
@@ -137,18 +138,18 @@
"Error": {
"Normal": {
"Foreground": "Red",
- "Background": "White"
+ "Background": "Pink"
},
"Focus": {
- "Foreground": "Black",
+ "Foreground": "White",
"Background": "BrightRed"
},
"HotNormal": {
"Foreground": "Black",
- "Background": "White"
+ "Background": "Pink"
},
"HotFocus": {
- "Foreground": "White",
+ "Foreground": "Pink",
"Background": "BrightRed"
},
"Disabled": {
@@ -162,6 +163,15 @@
},
{
"Dark": {
+ "Dialog.DefaultButtonAlignment": "End",
+ "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+ "Dialog.DefaultBorderStyle": "Heavy",
+ "Dialog.DefaultShadow": "Transparent",
+ "FrameView.DefaultBorderStyle": "Single",
+ "Window.DefaultBorderStyle": "Single",
+ "MessageBox.DefaultButtonAlignment": "Center",
+ "MessageBox.DefaultBorderStyle": "Heavy",
+ "Button.DefaultShadow": "Opaque",
"ColorSchemes": [
{
"TopLevel": {
@@ -238,16 +248,16 @@
{
"Menu": {
"Normal": {
- "Foreground": "White",
- "Background": "DarkGray"
+ "Foreground": "LightGray",
+ "Background": "#505050" // DarkerGray
},
"Focus": {
"Foreground": "White",
"Background": "Black"
},
"HotNormal": {
- "Foreground": "Gray",
- "Background": "DarkGray"
+ "Foreground": "White",
+ "Background": "#505050" // DarkerGray
},
"HotFocus": {
"Foreground": "White",
@@ -288,6 +298,15 @@
},
{
"Light": {
+ "Dialog.DefaultButtonAlignment": "End",
+ "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+ "Dialog.DefaultBorderStyle": "Heavy",
+ "Dialog.DefaultShadow": "Transparent",
+ "FrameView.DefaultBorderStyle": "Single",
+ "Window.DefaultBorderStyle": "Single",
+ "MessageBox.DefaultButtonAlignment": "Center",
+ "MessageBox.DefaultBorderStyle": "Heavy",
+ "Button.DefaultShadow": "Opaque",
"ColorSchemes": [
{
"TopLevel": {
@@ -316,7 +335,7 @@
{
"Base": {
"Normal": {
- "Foreground": "DarkGray",
+ "Foreground": "#505050", // DarkerGray
"Background": "White"
},
"Focus": {
@@ -365,19 +384,19 @@
"Menu": {
"Normal": {
"Foreground": "DarkGray",
- "Background": "White"
+ "Background": "LightGray"
},
"Focus": {
"Foreground": "DarkGray",
- "Background": "Gray"
+ "Background": "White"
},
"HotNormal": {
"Foreground": "BrightRed",
- "Background": "White"
+ "Background": "LightGray"
},
"HotFocus": {
"Foreground": "BrightRed",
- "Background": "Gray"
+ "Background": "White"
},
"Disabled": {
"Foreground": "Gray",
@@ -411,6 +430,273 @@
}
]
}
+ },
+ {
+ "Black & White": {
+ "Dialog.DefaultShadow": "None",
+ "FrameView.DefaultBorderStyle": "Single",
+ "Window.DefaultBorderStyle": "Single",
+ "MessageBox.DefaultButtonAlignment": "Center",
+ "MessageBox.DefaultBorderStyle": "Heavy",
+ "Button.DefaultShadow": "None",
+ "ColorSchemes": [
+ {
+ "TopLevel": {
+ "Normal": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "Focus": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "HotNormal": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "HotFocus": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "Disabled": {
+ "Foreground": "Black",
+ "Background": "Black"
+ }
+ }
+ },
+ {
+ "Base": {
+ "Normal": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "Focus": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "HotNormal": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "HotFocus": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "Disabled": {
+ "Foreground": "Black",
+ "Background": "Black"
+ }
+ }
+ },
+ {
+ "Dialog": {
+ "Normal": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "Focus": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "HotNormal": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "HotFocus": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "Disabled": {
+ "Foreground": "White",
+ "Background": "White"
+ }
+ }
+ },
+ {
+ "Menu": {
+ "Normal": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "Focus": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "HotNormal": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "HotFocus": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "Disabled": {
+ "Foreground": "White",
+ "Background": "White"
+ }
+ }
+ },
+ {
+ "Error": {
+ "Normal": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "Focus": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "HotNormal": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "HotFocus": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "Disabled": {
+ "Foreground": "Black",
+ "Background": "Black"
+ }
+ }
+ }
+ ]
+ }
+ },
+ {
+ "Gray Scale": {
+ "Dialog.DefaultButtonAlignment": "End",
+ "Dialog.DefaultButtonAlignmentModes": "AddSpaceBetweenItems",
+ "Dialog.DefaultBorderStyle": "Heavy",
+ "Dialog.DefaultShadow": "Transparent",
+ "FrameView.DefaultBorderStyle": "Single",
+ "Window.DefaultBorderStyle": "Single",
+ "MessageBox.DefaultButtonAlignment": "Center",
+ "MessageBox.DefaultBorderStyle": "Heavy",
+ "Button.DefaultShadow": "Opaque",
+ "ColorSchemes": [
+ {
+ "TopLevel": {
+ "Normal": {
+ "Foreground": "#A9A9A9", // DarkGray
+ "Background": "#505050" // DarkerGray
+ },
+ "Focus": {
+ "Foreground": "White",
+ "Background": "#696969" // DimGray
+ },
+ "HotNormal": {
+ "Foreground": "#808080", // Gray
+ "Background": "#505050" // DarkerGray
+ },
+ "HotFocus": {
+ "Foreground": "White",
+ "Background": "#808080" // Gray
+ },
+ "Disabled": {
+ "Foreground": "#505050", // DarkerGray
+ "Background": "Black"
+ }
+ }
+ },
+ {
+ "Base": {
+ "Normal": {
+ "Foreground": "#A9A9A9", // DarkGray
+ "Background": "Black"
+ },
+ "Focus": {
+ "Foreground": "White",
+ "Background": "#505050" // DarkerGray
+ },
+ "HotNormal": {
+ "Foreground": "#808080", // Gray
+ "Background": "Black"
+ },
+ "HotFocus": {
+ "Foreground": "White",
+ "Background": "#505050" // DarkerGray
+ },
+ "Disabled": {
+ "Foreground": "#696969", // DimGray
+ "Background": "Black"
+ }
+ }
+ },
+ {
+ "Dialog": {
+ "Normal": {
+ "Foreground": "#505050", // DarkerGray
+ "Background": "White"
+ },
+ "Focus": {
+ "Foreground": "Black",
+ "Background": "#D3D3D3" // LightGray
+ },
+ "HotNormal": {
+ "Foreground": "#808080", // Gray
+ "Background": "White"
+ },
+ "HotFocus": {
+ "Foreground": "Black",
+ "Background": "#D3D3D3" // LightGray
+ },
+ "Disabled": {
+ "Foreground": "#696969", // DimGray
+ "Background": "White"
+ }
+ }
+ },
+ {
+ "Menu": {
+ "Normal": {
+ "Foreground": "#D3D3D3", // LightGray
+ "Background": "#505050" // DarkerGray
+ },
+ "Focus": {
+ "Foreground": "White",
+ "Background": "#808080" // Gray
+ },
+ "HotNormal": {
+ "Foreground": "#808080", // Gray
+ "Background": "#505050" // DarkerGray
+ },
+ "HotFocus": {
+ "Foreground": "White",
+ "Background": "#808080" // Gray
+ },
+ "Disabled": {
+ "Foreground": "#505050", // DarkerGray
+ "Background": "#505050" // DarkerGray
+ }
+ }
+ },
+ {
+ "Error": {
+ "Normal": {
+ "Foreground": "Black",
+ "Background": "White"
+ },
+ "Focus": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "HotNormal": {
+ "Foreground": "Black",
+ "Background": "#D3D3D3" // LightGray
+ },
+ "HotFocus": {
+ "Foreground": "White",
+ "Background": "Black"
+ },
+ "Disabled": {
+ "Foreground": "#696969", // DimGray
+ "Background": "White"
+ }
+ }
+ }
+ ]
+ }
}
]
}
\ No newline at end of file
diff --git a/Terminal.Gui/Terminal.Gui.csproj b/Terminal.Gui/Terminal.Gui.csproj
index 2ff2b0e99a..b11044a16e 100644
--- a/Terminal.Gui/Terminal.Gui.csproj
+++ b/Terminal.Gui/Terminal.Gui.csproj
@@ -60,7 +60,7 @@
-
+
@@ -141,4 +141,5 @@
true
Miguel de Icaza, Tig Kindel (@tig), @BDisp
+
diff --git a/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs b/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs
index 2686c10242..ed55b0d3f4 100644
--- a/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs
+++ b/Terminal.Gui/Text/Autocomplete/AutocompleteContext.cs
@@ -7,7 +7,7 @@ namespace Terminal.Gui;
public class AutocompleteContext
{
/// Creates a new instance of the class
- public AutocompleteContext (List currentLine, int cursorPosition, bool canceled = false)
+ public AutocompleteContext (List currentLine, int cursorPosition, bool canceled = false)
{
CurrentLine = currentLine;
CursorPosition = cursorPosition;
@@ -18,7 +18,7 @@ public AutocompleteContext (List currentLine, int cursorPosition, bool
public bool Canceled { get; set; }
/// The text on the current line.
- public List CurrentLine { get; set; }
+ public List CurrentLine { get; set; }
/// The position of the input cursor within the .
public int CursorPosition { get; set; }
diff --git a/Terminal.Gui/View/Adornment/Adornment.cs b/Terminal.Gui/View/Adornment/Adornment.cs
index 6c575ad99e..ca90eed765 100644
--- a/Terminal.Gui/View/Adornment/Adornment.cs
+++ b/Terminal.Gui/View/Adornment/Adornment.cs
@@ -1,4 +1,5 @@
#nullable enable
+using System.ComponentModel;
using Terminal.Gui;
using Attribute = Terminal.Gui.Attribute;
@@ -26,7 +27,9 @@ public Adornment ()
///
public Adornment (View parent)
{
- CanFocus = true;
+ // By default Adornments can't get focus; has to be enabled specifically.
+ CanFocus = false;
+ TabStop = TabBehavior.NoStop;
Parent = parent;
}
@@ -220,43 +223,40 @@ public override bool Contains (in Point location)
return false;
}
- Rectangle frame = Frame;
- frame.Offset (Parent.Frame.Location);
+ Rectangle outside = Frame;
+ outside.Offset (Parent.Frame.Location);
- return Thickness.Contains (frame, location);
+ return Thickness.Contains (outside, location);
}
- ///
- protected internal override bool? OnMouseEnter (MouseEvent mouseEvent)
- {
- // Invert Normal
- if (Diagnostics.HasFlag (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
- {
- var cs = new ColorScheme (ColorScheme)
- {
- Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
- };
- ColorScheme = cs;
- }
-
- return base.OnMouseEnter (mouseEvent);
- }
-
- ///
- protected internal override bool OnMouseLeave (MouseEvent mouseEvent)
- {
- // Invert Normal
- if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
- {
- var cs = new ColorScheme (ColorScheme)
- {
- Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
- };
- ColorScheme = cs;
- }
-
- return base.OnMouseLeave (mouseEvent);
- }
+ /////
+ //protected override bool OnMouseEnter (CancelEventArgs mouseEvent)
+ //{
+ // // Invert Normal
+ // if (Diagnostics.HasFlag (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
+ // {
+ // var cs = new ColorScheme (ColorScheme)
+ // {
+ // Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
+ // };
+ // ColorScheme = cs;
+ // }
+
+ // return false;
+ //}
+ /////
+ //protected override void OnMouseLeave ()
+ //{
+ // // Invert Normal
+ // if (Diagnostics.FastHasFlags (ViewDiagnosticFlags.MouseEnter) && ColorScheme != null)
+ // {
+ // var cs = new ColorScheme (ColorScheme)
+ // {
+ // Normal = new (ColorScheme.Normal.Background, ColorScheme.Normal.Foreground)
+ // };
+ // ColorScheme = cs;
+ // }
+ //}
#endregion Mouse Support
}
diff --git a/Terminal.Gui/View/Adornment/Border.cs b/Terminal.Gui/View/Adornment/Border.cs
index de6ebd4cc6..082df687f7 100644
--- a/Terminal.Gui/View/Adornment/Border.cs
+++ b/Terminal.Gui/View/Adornment/Border.cs
@@ -1,6 +1,9 @@
+#nullable enable
+using System.Diagnostics;
+
namespace Terminal.Gui;
-/// The Border for a .
+/// The Border for a . Accessed via
///
///
/// Renders a border around the view with the . A border using
@@ -52,8 +55,10 @@ public Border ()
///
public Border (View parent) : base (parent)
{
- /* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
Parent = parent;
+ CanFocus = false;
+ TabStop = TabBehavior.TabGroup;
+
Application.GrabbingMouse += Application_GrabbingMouse;
Application.UnGrabbingMouse += Application_UnGrabbingMouse;
@@ -73,14 +78,6 @@ public Border (View parent) : base (parent)
///
public override void BeginInit ()
{
-#if HOVER
- // TOOD: Hack - make Arrangement overridable
- if ((Parent?.Arrangement & ViewArrangement.Movable) != 0)
- {
- HighlightStyle |= HighlightStyle.Hover;
- }
-#endif
-
base.BeginInit ();
#if SUBVIEW_BASED_BORDER
@@ -131,7 +128,7 @@ private void OnLayoutStarted (object sender, LayoutEventArgs e)
/// The color scheme for the Border. If set to , gets the
/// scheme. color scheme.
/// | |
- public override ColorScheme ColorScheme
+ public override ColorScheme? ColorScheme
{
get
{
@@ -152,6 +149,7 @@ public override ColorScheme ColorScheme
internal Rectangle GetBorderRectangle ()
{
Rectangle screenRect = ViewportToScreen (Viewport);
+
return new (
screenRect.X + Math.Max (0, Thickness.Left - 1),
screenRect.Y + Math.Max (0, Thickness.Top - 1),
@@ -193,7 +191,7 @@ public LineStyle LineStyle
// TODO: Make Border.LineStyle inherit from the SuperView hierarchy
// TODO: Right now, Window and FrameView use CM to set BorderStyle, which negates
// TODO: all this.
- return Parent.SuperView?.BorderStyle ?? LineStyle.None;
+ return Parent!.SuperView?.BorderStyle ?? LineStyle.None;
}
set => _lineStyle = value;
}
@@ -223,9 +221,9 @@ public BorderSettings Settings
private Color? _savedForeColor;
- private void Border_Highlight (object sender, CancelEventArgs e)
+ private void Border_Highlight (object? sender, CancelEventArgs e)
{
- if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
+ if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
{
e.Cancel = true;
@@ -236,31 +234,22 @@ private void Border_Highlight (object sender, CancelEventArgs e)
{
if (!_savedForeColor.HasValue)
{
- _savedForeColor = ColorScheme.Normal.Foreground;
+ _savedForeColor = ColorScheme!.Normal.Foreground;
}
var cs = new ColorScheme (ColorScheme)
{
- Normal = new (ColorScheme.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background)
+ Normal = new (ColorScheme!.Normal.Foreground.GetHighlightColor (), ColorScheme.Normal.Background)
};
ColorScheme = cs;
}
-#if HOVER
- else if (e.HighlightStyle.HasFlag (HighlightStyle.Hover))
- {
- if (!_savedHighlightLineStyle.HasValue)
- {
- _savedHighlightLineStyle = Parent?.BorderStyle ?? LineStyle;
- }
- LineStyle = LineStyle.Double;
- }
-#endif
+
if (e.NewValue == HighlightStyle.None && _savedForeColor.HasValue)
{
var cs = new ColorScheme (ColorScheme)
{
- Normal = new (_savedForeColor.Value, ColorScheme.Normal.Background)
+ Normal = new (_savedForeColor.Value, ColorScheme!.Normal.Background)
};
ColorScheme = cs;
}
@@ -280,46 +269,180 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
return true;
}
- // BUGBUG: Shouldn't non-focusable views be draggable??
- //if (!Parent.CanFocus)
- //{
- // return false;
- //}
-
- if (!Parent.Arrangement.HasFlag (ViewArrangement.Movable))
- {
- return false;
- }
-
// BUGBUG: See https://github.com/gui-cs/Terminal.Gui/issues/3312
- if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed))
+ if (!_dragPosition.HasValue && mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
+ // HACK: Prevents Window from being draggable if it's Top
+ //&& Parent is Toplevel { Modal: true }
+ )
{
- Parent.SetFocus ();
- ApplicationOverlapped.BringOverlappedTopToFront ();
+ Parent!.SetFocus ();
+
+ if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
+ )
+ {
+ return false;
+ }
// Only start grabbing if the user clicks in the Thickness area
// Adornment.Contains takes Parent SuperView=relative coords.
if (Contains (new (mouseEvent.Position.X + Parent.Frame.X + Frame.X, mouseEvent.Position.Y + Parent.Frame.Y + Frame.Y)))
{
+ if (_arranging != ViewArrangement.Fixed)
+ {
+ EndArrangeMode ();
+ }
+
// Set the start grab point to the Frame coords
_startGrabPoint = new (mouseEvent.Position.X + Frame.X, mouseEvent.Position.Y + Frame.Y);
_dragPosition = mouseEvent.Position;
Application.GrabMouse (this);
- SetHighlight (HighlightStyle);
+ SetPressedHighlight (HighlightStyle);
+
+ // Arrange Mode -
+ // TODO: This code can be refactored to be more readable and maintainable.
+
+ // If not resizable, but movable: Drag anywhere is move
+ // If resizable and movable: Drag on top is move, other 3 sides are size
+ // If not movable, but resizable: Drag on any side sizes.
+
+ // Get rectangle representing Thickness.Top
+ // If mouse is in that rectangle, set _arranging to ViewArrangement.Movable
+ Rectangle sideRect;
+
+ // If mouse is in any other rectangle, set _arranging to ViewArrangement.
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+ {
+ sideRect = new (Frame.X, Frame.Y + Thickness.Top, Thickness.Left, Frame.Height - Thickness.Top - Thickness.Bottom);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.LeftResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+ {
+ sideRect = new (
+ Frame.X + Frame.Width - Thickness.Right,
+ Frame.Y + Thickness.Top,
+ Thickness.Right,
+ Frame.Height - Thickness.Top - Thickness.Bottom);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.RightResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && !Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+ {
+ sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.TopResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
+ {
+ sideRect = new (
+ Frame.X + Thickness.Left,
+ Frame.Y + Frame.Height - Thickness.Bottom,
+ Frame.Width - Thickness.Left - Thickness.Right,
+ Thickness.Bottom);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.BottomResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+ {
+ sideRect = new (Frame.X, Frame.Height - Thickness.Top, Thickness.Left, Thickness.Bottom);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.LeftResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+ {
+ sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Height - Thickness.Top, Thickness.Right, Thickness.Bottom);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.BottomResizable | ViewArrangement.RightResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+ {
+ sideRect = new (Frame.X + Frame.Width - Thickness.Right, Frame.Y, Thickness.Right, Thickness.Top);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.RightResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable) && Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+ {
+ sideRect = new (Frame.X, Frame.Y, Thickness.Left, Thickness.Top);
+
+ if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.TopResizable | ViewArrangement.LeftResizable);
+
+ return true;
+ }
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+ {
+ //sideRect = new (Frame.X + Thickness.Left, Frame.Y, Frame.Width - Thickness.Left - Thickness.Right, Thickness.Top);
+
+ //if (sideRect.Contains (_startGrabPoint))
+ {
+ EnterArrangeMode (ViewArrangement.Movable);
+
+ return true;
+ }
+ }
}
return true;
}
- if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
+ if (mouseEvent.Flags is (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition) && Application.MouseGrabView == this)
{
- if (Application.MouseGrabView == this && _dragPosition.HasValue)
+ if (_dragPosition.HasValue)
{
- if (Parent.SuperView is null)
+ if (Parent!.SuperView is null)
{
// Redraw the entire app window.
- Application.Top.SetNeedsDisplay ();
+ Application.Top!.SetNeedsDisplay ();
}
else
{
@@ -331,17 +454,123 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
Point parentLoc = Parent.SuperView?.ScreenToViewport (new (mouseEvent.ScreenPosition.X, mouseEvent.ScreenPosition.Y))
?? mouseEvent.ScreenPosition;
- GetLocationEnsuringFullVisibility (
- Parent,
- parentLoc.X - _startGrabPoint.X,
- parentLoc.Y - _startGrabPoint.Y,
- out int nx,
- out int ny,
- out _
- );
+ int minHeight = Thickness.Vertical + Parent!.Margin.Thickness.Bottom;
+ int minWidth = Thickness.Horizontal + Parent!.Margin.Thickness.Right;
+
+ // TODO: This code can be refactored to be more readable and maintainable.
+ switch (_arranging)
+ {
+ case ViewArrangement.Movable:
+
+ GetLocationEnsuringFullVisibility (
+ Parent,
+ parentLoc.X - _startGrabPoint.X,
+ parentLoc.Y - _startGrabPoint.Y,
+ out int nx,
+ out int ny
+ //,
+ // out _
+ );
+
+ Parent.X = parentLoc.X - _startGrabPoint.X;
+ Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+
+ break;
+
+ case ViewArrangement.TopResizable:
+ // Get how much the mouse has moved since the start of the drag
+ // and adjust the height of the parent by that amount
+ int deltaY = parentLoc.Y - Parent.Frame.Y;
+ int newHeight = Math.Max (minHeight, Parent.Frame.Height - deltaY);
+
+ if (newHeight != Parent.Frame.Height)
+ {
+ Parent.Height = newHeight;
+ Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+ }
+
+ break;
+
+ case ViewArrangement.BottomResizable:
+ Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
+ break;
+
+ case ViewArrangement.LeftResizable:
+ // Get how much the mouse has moved since the start of the drag
+ // and adjust the height of the parent by that amount
+ int deltaX = parentLoc.X - Parent.Frame.X;
+ int newWidth = Math.Max (minWidth, Parent.Frame.Width - deltaX);
+
+ if (newWidth != Parent.Frame.Width)
+ {
+ Parent.Width = newWidth;
+ Parent.X = parentLoc.X - _startGrabPoint.X;
+ }
- Parent.X = nx;
- Parent.Y = ny;
+ break;
+
+ case ViewArrangement.RightResizable:
+ Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
+
+ break;
+
+ case ViewArrangement.BottomResizable | ViewArrangement.RightResizable:
+ Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
+ Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
+ break;
+
+ case ViewArrangement.BottomResizable | ViewArrangement.LeftResizable:
+ int dX = parentLoc.X - Parent.Frame.X;
+ int newW = Math.Max (minWidth, Parent.Frame.Width - dX);
+
+ if (newW != Parent.Frame.Width)
+ {
+ Parent.Width = newW;
+ Parent.X = parentLoc.X - _startGrabPoint.X;
+ }
+
+ Parent.Height = Math.Max (minHeight, parentLoc.Y - Parent.Frame.Y + Parent!.Margin.Thickness.Bottom + 1);
+
+ break;
+
+ case ViewArrangement.TopResizable | ViewArrangement.RightResizable:
+ int dY = parentLoc.Y - Parent.Frame.Y;
+ int newH = Math.Max (minHeight, Parent.Frame.Height - dY);
+
+ if (newH != Parent.Frame.Height)
+ {
+ Parent.Height = newH;
+ Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+ }
+
+ Parent.Width = Math.Max (minWidth, parentLoc.X - Parent.Frame.X + Parent!.Margin.Thickness.Right + 1);
+
+ break;
+
+ case ViewArrangement.TopResizable | ViewArrangement.LeftResizable:
+ int dY2 = parentLoc.Y - Parent.Frame.Y;
+ int newH2 = Math.Max (minHeight, Parent.Frame.Height - dY2);
+
+ if (newH2 != Parent.Frame.Height)
+ {
+ Parent.Height = newH2;
+ Parent.Y = parentLoc.Y - _startGrabPoint.Y;
+ }
+
+ int dX2 = parentLoc.X - Parent.Frame.X;
+ int newW2 = Math.Max (minWidth, Parent.Frame.Width - dX2);
+
+ if (newW2 != Parent.Frame.Width)
+ {
+ Parent.Width = newW2;
+ Parent.X = parentLoc.X - _startGrabPoint.X;
+ }
+
+ break;
+ }
+ Application.Refresh ();
return true;
}
@@ -351,7 +580,9 @@ out _
{
_dragPosition = null;
Application.UngrabMouse ();
- SetHighlight (HighlightStyle.None);
+ SetPressedHighlight (HighlightStyle.None);
+
+ EndArrangeMode ();
return true;
}
@@ -359,17 +590,7 @@ out _
return false;
}
- ///
- protected override void Dispose (bool disposing)
- {
- Application.GrabbingMouse -= Application_GrabbingMouse;
- Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
-
- _dragPosition = null;
- base.Dispose (disposing);
- }
-
- private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
+ private void Application_GrabbingMouse (object? sender, GrabMouseEventArgs e)
{
if (Application.MouseGrabView == this && _dragPosition.HasValue)
{
@@ -377,7 +598,7 @@ private void Application_GrabbingMouse (object sender, GrabMouseEventArgs e)
}
}
- private void Application_UnGrabbingMouse (object sender, GrabMouseEventArgs e)
+ private void Application_UnGrabbingMouse (object? sender, GrabMouseEventArgs e)
{
if (Application.MouseGrabView == this && _dragPosition.HasValue)
{
@@ -417,7 +638,7 @@ public override void OnDrawContent (Rectangle viewport)
int maxTitleWidth = Math.Max (
0,
Math.Min (
- Parent.TitleTextFormatter.FormatAndGetSize ().Width,
+ Parent!.TitleTextFormatter.FormatAndGetSize ().Width,
Math.Min (screenBounds.Width - 4, borderBounds.Width - 4)
)
);
@@ -427,6 +648,8 @@ public override void OnDrawContent (Rectangle viewport)
int sideLineLength = borderBounds.Height;
bool canDrawBorder = borderBounds is { Width: > 0, Height: > 0 };
+ LineStyle lineStyle = LineStyle;
+
if (Settings.FastHasFlags (BorderSettings.Title))
{
if (Thickness.Top == 2)
@@ -477,7 +700,7 @@ public override void OnDrawContent (Rectangle viewport)
if (canDrawBorder && LineStyle != LineStyle.None)
{
- LineCanvas lc = Parent?.LineCanvas;
+ LineCanvas? lc = Parent?.LineCanvas;
bool drawTop = Thickness.Top > 0 && Frame.Width > 1 && Frame.Height >= 1;
bool drawLeft = Thickness.Left > 0 && (Frame.Height > 1 || Thickness.Top == 0);
@@ -492,7 +715,7 @@ public override void OnDrawContent (Rectangle viewport)
}
else
{
- Driver.SetAttribute (Parent.GetNormalColor ());
+ Driver.SetAttribute (Parent!.GetNormalColor ());
}
if (drawTop)
@@ -502,13 +725,13 @@ public override void OnDrawContent (Rectangle viewport)
if (borderBounds.Width < 4 || !Settings.FastHasFlags (BorderSettings.Title) || string.IsNullOrEmpty (Parent?.Title))
{
// ╔╡╞╗ should be ╔══╗
- lc.AddLine (
- new (borderBounds.Location.X, titleY),
- borderBounds.Width,
- Orientation.Horizontal,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.Location.X, titleY),
+ borderBounds.Width,
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
else
{
@@ -517,13 +740,13 @@ public override void OnDrawContent (Rectangle viewport)
//│
if (Thickness.Top == 2)
{
- lc.AddLine (
- new (borderBounds.X + 1, topTitleLineY),
- Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.X + 1, topTitleLineY),
+ Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
// ┌────┐
@@ -531,71 +754,71 @@ public override void OnDrawContent (Rectangle viewport)
//│
if (borderBounds.Width >= 4 && Thickness.Top > 2)
{
- lc.AddLine (
- new (borderBounds.X + 1, topTitleLineY),
- Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- LineStyle,
- Driver.GetAttribute ()
- );
-
- lc.AddLine (
- new (borderBounds.X + 1, topTitleLineY + 2),
- Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.X + 1, topTitleLineY),
+ Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
+
+ lc?.AddLine (
+ new (borderBounds.X + 1, topTitleLineY + 2),
+ Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
// ╔╡Title╞═════╗
// Add a short horiz line for ╔╡
- lc.AddLine (
- new (borderBounds.Location.X, titleY),
- 2,
- Orientation.Horizontal,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.Location.X, titleY),
+ 2,
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
// Add a vert line for ╔╡
- lc.AddLine (
- new (borderBounds.X + 1, topTitleLineY),
- titleBarsLength,
- Orientation.Vertical,
- LineStyle.Single,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.X + 1, topTitleLineY),
+ titleBarsLength,
+ Orientation.Vertical,
+ LineStyle.Single,
+ Driver.GetAttribute ()
+ );
// Add a vert line for ╞
- lc.AddLine (
- new (
- borderBounds.X
- + 1
- + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
- - 1,
- topTitleLineY
- ),
- titleBarsLength,
- Orientation.Vertical,
- LineStyle.Single,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (
+ borderBounds.X
+ + 1
+ + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
+ - 1,
+ topTitleLineY
+ ),
+ titleBarsLength,
+ Orientation.Vertical,
+ LineStyle.Single,
+ Driver.GetAttribute ()
+ );
// Add the right hand line for ╞═════╗
- lc.AddLine (
- new (
- borderBounds.X
- + 1
- + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
- - 1,
- titleY
- ),
- borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
- Orientation.Horizontal,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (
+ borderBounds.X
+ + 1
+ + Math.Min (borderBounds.Width - 2, maxTitleWidth + 2)
+ - 1,
+ titleY
+ ),
+ borderBounds.Width - Math.Min (borderBounds.Width - 2, maxTitleWidth + 2),
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
}
@@ -603,36 +826,36 @@ public override void OnDrawContent (Rectangle viewport)
if (drawLeft)
{
- lc.AddLine (
- new (borderBounds.Location.X, titleY),
- sideLineLength,
- Orientation.Vertical,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.Location.X, titleY),
+ sideLineLength,
+ Orientation.Vertical,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
#endif
if (drawBottom)
{
- lc.AddLine (
- new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
- borderBounds.Width,
- Orientation.Horizontal,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.X, borderBounds.Y + borderBounds.Height - 1),
+ borderBounds.Width,
+ Orientation.Horizontal,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
if (drawRight)
{
- lc.AddLine (
- new (borderBounds.X + borderBounds.Width - 1, titleY),
- sideLineLength,
- Orientation.Vertical,
- LineStyle,
- Driver.GetAttribute ()
- );
+ lc?.AddLine (
+ new (borderBounds.X + borderBounds.Width - 1, titleY),
+ sideLineLength,
+ Orientation.Vertical,
+ lineStyle,
+ Driver.GetAttribute ()
+ );
}
Driver.SetAttribute (prevAttr);
@@ -651,10 +874,10 @@ public override void OnDrawContent (Rectangle viewport)
// Redraw title
if (drawTop && maxTitleWidth > 0 && Settings.FastHasFlags (BorderSettings.Title))
{
- Parent.TitleTextFormatter.Draw (
- new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
- Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
- Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
+ Parent!.TitleTextFormatter.Draw (
+ new (borderBounds.X + 2, titleY, maxTitleWidth, 1),
+ Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor (),
+ Parent.HasFocus ? Parent.GetFocusColor () : Parent.GetNormalColor ());
}
//Left
@@ -681,11 +904,11 @@ public override void OnDrawContent (Rectangle viewport)
// TODO: This should not be done on each draw?
if (Settings.FastHasFlags (BorderSettings.Gradient))
{
- SetupGradientLineCanvas (lc, screenBounds);
+ SetupGradientLineCanvas (lc!, screenBounds);
}
else
{
- lc.Fill = null;
+ lc!.Fill = null;
}
}
}
@@ -705,17 +928,536 @@ private void SetupGradientLineCanvas (LineCanvas lc, Rectangle rect)
private static void GetAppealingGradientColors (out List stops, out List steps)
{
// Define the colors of the gradient stops with more appealing colors
- stops = new()
- {
+ stops =
+ [
new (0, 128, 255), // Bright Blue
new (0, 255, 128), // Bright Green
new (255, 255), // Bright Yellow
new (255, 128), // Bright Orange
- new (255, 0, 128) // Bright Pink
- };
+ new (255, 0, 128)
+ ];
// Define the number of steps between each color for smoother transitions
// If we pass only a single value then it will assume equal steps between all pairs
- steps = new() { 15 };
+ steps = [15];
+ }
+
+ private ViewArrangement _arranging;
+
+ private Button? _moveButton; // always top-left
+ private Button? _allSizeButton;
+ private Button? _leftSizeButton;
+ private Button? _rightSizeButton;
+ private Button? _topSizeButton;
+ private Button? _bottomSizeButton;
+
+ ///
+ /// Starts "Arrange Mode" where can be moved and/or resized using the mouse
+ /// or keyboard. If is keyboard mode is enabled.
+ ///
+ ///
+ /// Arrange Mode is exited by the user pressing , , or by
+ /// clicking
+ /// the mouse out of the 's Frame.
+ ///
+ ///
+ public bool? EnterArrangeMode (ViewArrangement arrangement)
+ {
+ Debug.Assert (_arranging == ViewArrangement.Fixed);
+
+ if (!Parent!.Arrangement.HasFlag (ViewArrangement.Movable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable)
+ && !Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable)
+ )
+ {
+ return false;
+ }
+
+ // Add Commands and Keybindigs - Note it's ok these get added each time. KeyBindings are cleared in EndArrange()
+ AddArrangeModeKeyBindings ();
+
+ Application.MouseEvent += ApplicationOnMouseEvent;
+
+ // TODO: This code can be refactored to be more readable and maintainable.
+
+ // Create buttons for resizing and moving
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+ {
+ Debug.Assert (_moveButton is null);
+
+ _moveButton = new ()
+ {
+ Id = "moveButton",
+ CanFocus = true,
+ Width = 1,
+ Height = 1,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ Text = $"{Glyphs.Move}",
+ Visible = false,
+ Data = ViewArrangement.Movable
+ };
+ Add (_moveButton);
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
+ {
+ Debug.Assert (_allSizeButton is null);
+
+ _allSizeButton = new ()
+ {
+ Id = "allSizeButton",
+ CanFocus = true,
+ Width = 1,
+ Height = 1,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ Text = $"{Glyphs.SizeBottomRight}",
+ X = Pos.AnchorEnd (),
+ Y = Pos.AnchorEnd (),
+ Visible = false,
+ Data = ViewArrangement.Resizable
+ };
+ Add (_allSizeButton);
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.TopResizable))
+ {
+ Debug.Assert (_topSizeButton is null);
+
+ _topSizeButton = new ()
+ {
+ Id = "topSizeButton",
+ CanFocus = true,
+ Width = 1,
+ Height = 1,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ Text = $"{Glyphs.SizeVertical}",
+ X = Pos.Center () + Parent!.Margin.Thickness.Horizontal,
+ Y = 0,
+ Visible = false,
+ Data = ViewArrangement.TopResizable
+ };
+ Add (_topSizeButton);
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.RightResizable))
+ {
+ Debug.Assert (_rightSizeButton is null);
+
+ _rightSizeButton = new ()
+ {
+ Id = "rightSizeButton",
+ CanFocus = true,
+ Width = 1,
+ Height = 1,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ Text = $"{Glyphs.SizeHorizontal}",
+ X = Pos.AnchorEnd (),
+ Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
+ Visible = false,
+ Data = ViewArrangement.RightResizable
+ };
+ Add (_rightSizeButton);
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.LeftResizable))
+ {
+ Debug.Assert (_leftSizeButton is null);
+
+ _leftSizeButton = new ()
+ {
+ Id = "leftSizeButton",
+ CanFocus = true,
+ Width = 1,
+ Height = 1,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ Text = $"{Glyphs.SizeHorizontal}",
+ X = 0,
+ Y = Pos.Center () + Parent!.Margin.Thickness.Vertical / 2,
+ Visible = false,
+ Data = ViewArrangement.LeftResizable
+ };
+ Add (_leftSizeButton);
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.BottomResizable))
+ {
+ Debug.Assert (_bottomSizeButton is null);
+
+ _bottomSizeButton = new ()
+ {
+ Id = "bottomSizeButton",
+ CanFocus = true,
+ Width = 1,
+ Height = 1,
+ NoDecorations = true,
+ NoPadding = true,
+ ShadowStyle = ShadowStyle.None,
+ Text = $"{Glyphs.SizeVertical}",
+ X = Pos.Center () + Parent!.Margin.Thickness.Horizontal / 2,
+ Y = Pos.AnchorEnd (),
+ Visible = false,
+ Data = ViewArrangement.BottomResizable
+ };
+ Add (_bottomSizeButton);
+ }
+
+ if (arrangement == ViewArrangement.Fixed)
+ {
+ // Keyboard mode
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Movable))
+ {
+ _moveButton!.Visible = true;
+ }
+
+ if (Parent!.Arrangement.HasFlag (ViewArrangement.Resizable))
+ {
+ _allSizeButton!.Visible = true;
+ }
+
+ _arranging = ViewArrangement.Movable;
+ CanFocus = true;
+ SetFocus ();
+ }
+ else
+ {
+ // Mouse mode
+ _arranging = arrangement;
+
+ switch (_arranging)
+ {
+ case ViewArrangement.Movable:
+ _moveButton!.Visible = true;
+
+ break;
+
+ case ViewArrangement.RightResizable | ViewArrangement.BottomResizable:
+ case ViewArrangement.Resizable:
+ _rightSizeButton!.Visible = true;
+ _bottomSizeButton!.Visible = true;
+
+ if (_allSizeButton is { })
+ {
+ _allSizeButton!.X = Pos.AnchorEnd ();
+ _allSizeButton!.Y = Pos.AnchorEnd ();
+ _allSizeButton!.Visible = true;
+ }
+
+ break;
+
+ case ViewArrangement.LeftResizable:
+ _leftSizeButton!.Visible = true;
+
+ break;
+
+ case ViewArrangement.RightResizable:
+ _rightSizeButton!.Visible = true;
+
+ break;
+
+ case ViewArrangement.TopResizable:
+ _topSizeButton!.Visible = true;
+
+ break;
+
+ case ViewArrangement.BottomResizable:
+ _bottomSizeButton!.Visible = true;
+
+ break;
+
+ case ViewArrangement.LeftResizable | ViewArrangement.BottomResizable:
+ _rightSizeButton!.Visible = true;
+ _bottomSizeButton!.Visible = true;
+
+ if (_allSizeButton is { })
+ {
+ _allSizeButton.X = 0;
+ _allSizeButton.Y = Pos.AnchorEnd ();
+ _allSizeButton.Visible = true;
+ }
+
+ break;
+
+ case ViewArrangement.LeftResizable | ViewArrangement.TopResizable:
+ _leftSizeButton!.Visible = true;
+ _topSizeButton!.Visible = true;
+
+ break;
+
+ case ViewArrangement.RightResizable | ViewArrangement.TopResizable:
+ _rightSizeButton!.Visible = true;
+ _topSizeButton!.Visible = true;
+
+ if (_allSizeButton is { })
+ {
+ _allSizeButton.X = Pos.AnchorEnd ();
+ _allSizeButton.Y = 0;
+ _allSizeButton.Visible = true;
+ }
+
+ break;
+ }
+ }
+
+ if (_arranging != ViewArrangement.Fixed)
+ {
+ if (arrangement == ViewArrangement.Fixed)
+ {
+ // Keyboard mode - enable nav
+ // TODO: Keyboard mode only supports sizing from bottom/right.
+ _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+ }
+
+ return true;
+ }
+
+ // Hack for now
+ EndArrangeMode ();
+
+ return false;
+ }
+
+ private void AddArrangeModeKeyBindings ()
+ {
+ AddCommand (Command.Quit, EndArrangeMode);
+
+ AddCommand (
+ Command.Up,
+ () =>
+ {
+ if (Parent is null)
+ {
+ return false;
+ }
+
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.Y = Parent.Y - 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ if (Parent!.Viewport.Height > 0)
+ {
+ Parent!.Height = Parent.Height! - 1;
+ }
+ }
+
+ Application.Refresh ();
+
+ return true;
+ });
+
+ AddCommand (
+ Command.Down,
+ () =>
+ {
+ if (Parent is null)
+ {
+ return false;
+ }
+
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.Y = Parent.Y + 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ Parent!.Height = Parent.Height! + 1;
+ }
+
+ Application.Refresh ();
+
+ return true;
+ });
+
+ AddCommand (
+ Command.Left,
+ () =>
+ {
+ if (Parent is null)
+ {
+ return false;
+ }
+
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.X = Parent.X - 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ if (Parent!.Viewport.Width > 0)
+ {
+ Parent!.Width = Parent.Width! - 1;
+ }
+ }
+
+ Application.Refresh ();
+
+ return true;
+ });
+
+ AddCommand (
+ Command.Right,
+ () =>
+ {
+ if (Parent is null)
+ {
+ return false;
+ }
+
+ if (_arranging == ViewArrangement.Movable)
+ {
+ Parent!.X = Parent.X + 1;
+ }
+
+ if (_arranging == ViewArrangement.Resizable)
+ {
+ Parent!.Width = Parent.Width! + 1;
+ }
+
+ Application.Refresh ();
+
+ return true;
+ });
+
+ AddCommand (
+ Command.Tab,
+ () =>
+ {
+ // BUGBUG: If an arrangable view has only arrangable subviews, it's not possible to activate
+ // BUGBUG: ArrangeMode with keyboard for the superview.
+ // BUGBUG: AdvanceFocus should be wise to this and when in ArrangeMode, should move across
+ // BUGBUG: the view hierachy.
+
+ AdvanceFocus (NavigationDirection.Forward, TabBehavior.TabStop);
+ _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+
+ return true; // Always eat
+ });
+
+ AddCommand (
+ Command.BackTab,
+ () =>
+ {
+ AdvanceFocus (NavigationDirection.Backward, TabBehavior.TabStop);
+ _arranging = (ViewArrangement)(Focused?.Data ?? ViewArrangement.Fixed);
+
+ return true; // Always eat
+ });
+
+ KeyBindings.Add (Key.Esc, KeyBindingScope.HotKey, Command.Quit);
+ KeyBindings.Add (Application.ArrangeKey, KeyBindingScope.HotKey, Command.Quit);
+ KeyBindings.Add (Key.CursorUp, KeyBindingScope.HotKey, Command.Up);
+ KeyBindings.Add (Key.CursorDown, KeyBindingScope.HotKey, Command.Down);
+ KeyBindings.Add (Key.CursorLeft, KeyBindingScope.HotKey, Command.Left);
+ KeyBindings.Add (Key.CursorRight, KeyBindingScope.HotKey, Command.Right);
+
+ KeyBindings.Add (Key.Tab, KeyBindingScope.HotKey, Command.Tab);
+ KeyBindings.Add (Key.Tab.WithShift, KeyBindingScope.HotKey, Command.BackTab);
+ }
+
+ private void ApplicationOnMouseEvent (object? sender, MouseEvent e)
+ {
+ if (e.Flags != MouseFlags.Button1Clicked)
+ {
+ return;
+ }
+
+ // If mouse click is outside of Border.Thickness then exit Arrange Mode
+ // e.Position is screen relative
+ Point framePos = ScreenToFrame (e.ScreenPosition);
+
+ if (!Thickness.Contains (Frame, framePos))
+ {
+ EndArrangeMode ();
+ }
+ }
+
+ private bool? EndArrangeMode ()
+ {
+ // Debug.Assert (_arranging != ViewArrangement.Fixed);
+ _arranging = ViewArrangement.Fixed;
+
+ Application.MouseEvent -= ApplicationOnMouseEvent;
+
+ if (Application.MouseGrabView == this && _dragPosition.HasValue)
+ {
+ Application.UngrabMouse ();
+ }
+
+ if (_moveButton is { })
+ {
+ Remove (_moveButton);
+ _moveButton.Dispose ();
+ _moveButton = null;
+ }
+
+ if (_allSizeButton is { })
+ {
+ Remove (_allSizeButton);
+ _allSizeButton.Dispose ();
+ _allSizeButton = null;
+ }
+
+ if (_leftSizeButton is { })
+ {
+ Remove (_leftSizeButton);
+ _leftSizeButton.Dispose ();
+ _leftSizeButton = null;
+ }
+
+ if (_rightSizeButton is { })
+ {
+ Remove (_rightSizeButton);
+ _rightSizeButton.Dispose ();
+ _rightSizeButton = null;
+ }
+
+ if (_topSizeButton is { })
+ {
+ Remove (_topSizeButton);
+ _topSizeButton.Dispose ();
+ _topSizeButton = null;
+ }
+
+ if (_bottomSizeButton is { })
+ {
+ Remove (_bottomSizeButton);
+ _bottomSizeButton.Dispose ();
+ _bottomSizeButton = null;
+ }
+
+ KeyBindings.Clear ();
+
+ if (CanFocus)
+ {
+ CanFocus = false;
+ }
+
+ return true;
+ }
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ Application.GrabbingMouse -= Application_GrabbingMouse;
+ Application.UnGrabbingMouse -= Application_UnGrabbingMouse;
+
+ _dragPosition = null;
+ base.Dispose (disposing);
}
}
diff --git a/Terminal.Gui/View/Adornment/Margin.cs b/Terminal.Gui/View/Adornment/Margin.cs
index ac2705f7e7..1f6cc81540 100644
--- a/Terminal.Gui/View/Adornment/Margin.cs
+++ b/Terminal.Gui/View/Adornment/Margin.cs
@@ -2,7 +2,7 @@
namespace Terminal.Gui;
-/// The Margin for a .
+/// The Margin for a . Accessed via
///
/// See the class.
///
@@ -18,7 +18,8 @@ public Margin (View parent) : base (parent)
{
/* Do nothing; View.CreateAdornment requires a constructor that takes a parent */
- HighlightStyle |= HighlightStyle.Pressed;
+ // BUGBUG: We should not set HighlightStyle.Pressed here, but wherever it is actually needed
+ // HighlightStyle |= HighlightStyle.Pressed;
Highlight += Margin_Highlight;
LayoutStarted += Margin_LayoutStarted;
@@ -69,7 +70,7 @@ public override void BeginInit ()
/// The color scheme for the Margin. If set to , gets the 's
/// scheme. color scheme.
///
- public override ColorScheme ColorScheme
+ public override ColorScheme? ColorScheme
{
get
{
@@ -90,17 +91,22 @@ public override ColorScheme ColorScheme
///
public override void OnDrawContent (Rectangle viewport)
{
+ if (!NeedsDisplay)
+ {
+ return;
+ }
+
Rectangle screen = ViewportToScreen (viewport);
Attribute normalAttr = GetNormalColor ();
Driver?.SetAttribute (normalAttr);
- // This just draws/clears the thickness, not the insides.
if (ShadowStyle != ShadowStyle.None)
{
screen = Rectangle.Inflate (screen, -1, -1);
}
+ // This just draws/clears the thickness, not the insides.
Thickness.Draw (screen, ToString ());
if (Subviews.Count > 0)
@@ -170,6 +176,9 @@ public override ShadowStyle ShadowStyle
set => base.ShadowStyle = SetShadow (value);
}
+ private const int PRESS_MOVE_HORIZONTAL = 1;
+ private const int PRESS_MOVE_VERTICAL = 0;
+
private void Margin_Highlight (object? sender, CancelEventArgs e)
{
if (ShadowStyle != ShadowStyle.None)
@@ -179,7 +188,7 @@ private void Margin_Highlight (object? sender, CancelEventArgs e
// If the view is pressed and the highlight is being removed, move the shadow back.
// Note, for visual effects reasons, we only move horizontally.
// TODO: Add a setting or flag that lets the view move vertically as well.
- Thickness = new (Thickness.Left - 1, Thickness.Top, Thickness.Right + 1, Thickness.Bottom);
+ Thickness = new (Thickness.Left - PRESS_MOVE_HORIZONTAL, Thickness.Top - PRESS_MOVE_VERTICAL, Thickness.Right + PRESS_MOVE_HORIZONTAL, Thickness.Bottom + PRESS_MOVE_VERTICAL);
if (_rightShadow is { })
{
@@ -201,7 +210,7 @@ private void Margin_Highlight (object? sender, CancelEventArgs e
// If the view is not pressed and we want highlight move the shadow
// Note, for visual effects reasons, we only move horizontally.
// TODO: Add a setting or flag that lets the view move vertically as well.
- Thickness = new (Thickness.Left + 1, Thickness.Top, Thickness.Right - 1, Thickness.Bottom);
+ Thickness = new (Thickness.Left + PRESS_MOVE_HORIZONTAL, Thickness.Top+ PRESS_MOVE_VERTICAL, Thickness.Right - PRESS_MOVE_HORIZONTAL, Thickness.Bottom - PRESS_MOVE_VERTICAL);
_pressed = true;
if (_rightShadow is { })
diff --git a/Terminal.Gui/View/Adornment/Padding.cs b/Terminal.Gui/View/Adornment/Padding.cs
index f979dcf903..8f51a4f9b7 100644
--- a/Terminal.Gui/View/Adornment/Padding.cs
+++ b/Terminal.Gui/View/Adornment/Padding.cs
@@ -1,6 +1,6 @@
namespace Terminal.Gui;
-/// The Padding for a .
+/// The Padding for a . Accessed via
///
/// See the class.
///
diff --git a/Terminal.Gui/View/Adornment/ShadowView.cs b/Terminal.Gui/View/Adornment/ShadowView.cs
index c9893c65ca..4e16471490 100644
--- a/Terminal.Gui/View/Adornment/ShadowView.cs
+++ b/Terminal.Gui/View/Adornment/ShadowView.cs
@@ -137,7 +137,7 @@ private void DrawVerticalShadowTransparent (Rectangle viewport)
Rectangle screen = ViewportToScreen (viewport);
// Fill the rest of the rectangle
- for (int i = screen.Y; i < screen.Y + viewport.Height; i++)
+ for (int i = Math.Max (0, screen.Y); i < screen.Y + viewport.Height; i++)
{
Driver.Move (screen.X, i);
diff --git a/Terminal.Gui/View/HighlightStyle.cs b/Terminal.Gui/View/HighlightStyle.cs
index eb4948afb9..391eeb9107 100644
--- a/Terminal.Gui/View/HighlightStyle.cs
+++ b/Terminal.Gui/View/HighlightStyle.cs
@@ -1,30 +1,31 @@
-namespace Terminal.Gui;
+using System.Text.Json.Serialization;
+
+namespace Terminal.Gui;
///
-/// Describes the highlight style of a view.
+/// Describes the highlight style of a view when the mouse is over it.
///
+[JsonConverter (typeof (JsonStringEnumConverter))]
[Flags]
public enum HighlightStyle
{
///
- /// No highlight.
+ /// No highlight.
///
None = 0,
-#if HOVER
///
- /// The mouse is hovering over the view.
+ /// The mouse is hovering over the view (but not pressed). See .
///
Hover = 1,
-#endif
///
- /// The mouse is pressed within the .
+ /// The mouse is pressed within the .
///
Pressed = 2,
///
- /// The mouse is pressed but moved outside the .
+ /// The mouse is pressed but moved outside the .
///
PressedOutside = 4
}
diff --git a/Terminal.Gui/View/Layout/DimAuto.cs b/Terminal.Gui/View/Layout/DimAuto.cs
index a2061416dc..f159b6476c 100644
--- a/Terminal.Gui/View/Layout/DimAuto.cs
+++ b/Terminal.Gui/View/Layout/DimAuto.cs
@@ -408,6 +408,48 @@ internal override int Calculate (int location, int superviewContentSize, View us
}
#endregion DimView
+
+ #region DimAuto
+ // [ ] DimAuto - Dimension is internally calculated
+
+ List dimAutoSubViews;
+
+ if (dimension == Dimension.Width && us.GetType ().Name == "Bar" && us.Subviews.Count == 3)
+ {
+
+ }
+
+ if (dimension == Dimension.Width)
+ {
+ dimAutoSubViews = includedSubviews.Where (v => v.Width is { } && v.Width.Has (out _)).ToList ();
+ }
+ else
+ {
+ dimAutoSubViews = includedSubviews.Where (v => v.Height is { } && v.Height.Has (out _)).ToList ();
+ }
+
+ for (var i = 0; i < dimAutoSubViews.Count; i++)
+ {
+ View v = dimAutoSubViews [i];
+
+ if (dimension == Dimension.Width)
+ {
+ v.SetRelativeLayout (new (maxCalculatedSize, 0));
+ }
+ else
+ {
+ v.SetRelativeLayout (new (0, maxCalculatedSize));
+ }
+
+ int maxDimAuto= dimension == Dimension.Width ? v.Frame.X + v.Frame.Width : v.Frame.Y + v.Frame.Height;
+
+ if (maxDimAuto > maxCalculatedSize)
+ {
+ maxCalculatedSize = maxDimAuto;
+ }
+ }
+
+ #endregion
}
}
diff --git a/Terminal.Gui/View/Layout/DimAutoStyle.cs b/Terminal.Gui/View/Layout/DimAutoStyle.cs
index 33e6da2c6d..3d3ec1df61 100644
--- a/Terminal.Gui/View/Layout/DimAutoStyle.cs
+++ b/Terminal.Gui/View/Layout/DimAutoStyle.cs
@@ -35,7 +35,7 @@ public enum DimAutoStyle
///
///
/// If is set, the dimension will be the maximum of the formatted text and the
- /// demension provided by . Otherwise, the dimension will be that of the formatted text.
+ /// dimension provided by . Otherwise, the dimension will be that of the formatted text.
///
///
Text = 2,
diff --git a/Terminal.Gui/View/Navigation/TabBehavior.cs b/Terminal.Gui/View/Navigation/TabBehavior.cs
index 2192a60754..ba8b189c95 100644
--- a/Terminal.Gui/View/Navigation/TabBehavior.cs
+++ b/Terminal.Gui/View/Navigation/TabBehavior.cs
@@ -6,25 +6,23 @@
public enum TabBehavior
{
///
- /// The View will not be a stop-poknt for keyboard-based navigation.
- ///
- ///
+ /// The View will not be a stop-point for keyboard-based navigation.
///
/// This flag has no impact on whether the view can be focused via means other than the keyboard. Use
///
/// to control whether a View can focus or not.
///
- ///
+ ///
NoStop = 0,
///
- /// The View will be a stop-point for keybaord-based navigation across Views (e.g. if the user presses `Tab`).
+ /// The View will be a stop-point for keyboard-based navigation across Views (e.g. if the user presses `Tab`).
///
TabStop = 1,
///
- /// The View will be a stop-point for keyboard-based navigation across groups (e.g. if the user presses
- /// (`Ctrl-PageDown`).
+ /// The View will be a stop-point for keyboard-based navigation across groups. (e.g. if the user presses
+ /// (`Ctrl+PageDown`)).
///
TabGroup = 2
}
diff --git a/Terminal.Gui/View/View.Adornments.cs b/Terminal.Gui/View/View.Adornments.cs
index 6013f5b5bf..f3f37d40f8 100644
--- a/Terminal.Gui/View/View.Adornments.cs
+++ b/Terminal.Gui/View/View.Adornments.cs
@@ -1,12 +1,9 @@
-using System.ComponentModel;
-using System.Text.Json.Serialization;
-
-namespace Terminal.Gui;
+namespace Terminal.Gui;
public partial class View // Adornments
{
///
- /// Initializes the Adornments of the View. Called by the constructor.
+ /// Initializes the Adornments of the View. Called by the constructor.
///
private void SetupAdornments ()
{
@@ -49,6 +46,9 @@ private void DisposeAdornments ()
///
///
///
+ /// Enabling will change the Thickness of the Margin to include the shadow.
+ ///
+ ///
/// The adornments (, , and ) are not part of the
/// View's content and are not clipped by the View's Clip Area.
///
@@ -61,8 +61,10 @@ private void DisposeAdornments ()
public Margin Margin { get; private set; }
private ShadowStyle _shadowStyle;
+
///
- /// Gets or sets whether the View is shown with a shadow effect. The shadow is drawn on the right and bottom sides of the
+ /// Gets or sets whether the View is shown with a shadow effect. The shadow is drawn on the right and bottom sides of
+ /// the
/// Margin.
///
///
@@ -78,7 +80,9 @@ public virtual ShadowStyle ShadowStyle
{
return;
}
+
_shadowStyle = value;
+
if (Margin is { })
{
Margin.ShadowStyle = value;
@@ -88,9 +92,15 @@ public virtual ShadowStyle ShadowStyle
///
/// The that offsets the from the .
- /// The Border provides the space for a visual border (drawn using
- /// line-drawing glyphs) and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2` the
- /// border and title will take up the first row and the second row will be filled with spaces.
+ ///
+ /// The Border provides the space for a visual border (drawn using
+ /// line-drawing glyphs) and the Title. The Border expands inward; in other words if `Border.Thickness.Top == 2`
+ /// the
+ /// border and title will take up the first row and the second row will be filled with spaces.
+ ///
+ ///
+ /// The Border provides the UI for mouse and keyboard arrangement of the View. See .
+ ///
///
///
/// provides a simple helper for turning a simple border frame on or off.
@@ -124,15 +134,15 @@ public LineStyle BorderStyle
get => Border?.LineStyle ?? LineStyle.Single;
set
{
- var old = Border?.LineStyle ?? LineStyle.None;
+ LineStyle old = Border?.LineStyle ?? LineStyle.None;
CancelEventArgs e = new (ref old, ref value);
OnBorderStyleChanging (e);
-
}
}
///
- /// Called when the is changing. Invokes , which allows the event to be cancelled.
+ /// Called when the is changing. Invokes , which allows the
+ /// event to be cancelled.
///
///
/// Override to prevent the from changing.
@@ -146,6 +156,7 @@ protected void OnBorderStyleChanging (CancelEventArgs e)
}
BorderStyleChanging?.Invoke (this, e);
+
if (e.Cancel)
{
return;
@@ -154,8 +165,6 @@ protected void OnBorderStyleChanging (CancelEventArgs e)
SetBorderStyle (e.NewValue);
LayoutAdornments ();
SetNeedsLayout ();
-
- return;
}
///
@@ -163,7 +172,8 @@ protected void OnBorderStyleChanging (CancelEventArgs e)
///
///
///
- /// is a helper for manipulating the view's . Setting this property to any value other
+ /// is a helper for manipulating the view's . Setting this property
+ /// to any value other
/// than is equivalent to setting 's
/// to `1` and to the value.
///
@@ -218,9 +228,9 @@ public virtual void SetBorderStyle (LineStyle value)
/// Gets the thickness describing the sum of the Adornments' thicknesses.
///
///
- ///
- /// The is offset from the by the thickness returned by this method.
- ///
+ ///
+ /// The is offset from the by the thickness returned by this method.
+ ///
///
/// A thickness that describes the sum of the Adornments' thicknesses.
public Thickness GetAdornmentsThickness ()
@@ -229,6 +239,7 @@ public Thickness GetAdornmentsThickness ()
{
return Thickness.Empty;
}
+
return Margin.Thickness + Border.Thickness + Padding.Thickness;
}
diff --git a/Terminal.Gui/View/View.Arrangement.cs b/Terminal.Gui/View/View.Arrangement.cs
index 0fea93324b..aec9e65530 100644
--- a/Terminal.Gui/View/View.Arrangement.cs
+++ b/Terminal.Gui/View/View.Arrangement.cs
@@ -3,12 +3,11 @@
public partial class View
{
///
- /// Gets or sets the user actions that are enabled for the view within it's .
+ /// Gets or sets the user actions that are enabled for the arranging this view within it's .
///
///
///
- /// Sizing or moving a view is only possible if the is part of a and
- /// the relevant position and dimensions of the are independent of other SubViews
+ /// See the View Arrangement Deep Dive for more information:
///
///
public ViewArrangement Arrangement { get; set; }
diff --git a/Terminal.Gui/View/View.Command.cs b/Terminal.Gui/View/View.Command.cs
new file mode 100644
index 0000000000..1ef0dbb279
--- /dev/null
+++ b/Terminal.Gui/View/View.Command.cs
@@ -0,0 +1,353 @@
+#nullable enable
+using System.ComponentModel;
+
+namespace Terminal.Gui;
+
+public partial class View // Command APIs
+{
+ #region Default Implementation
+
+ ///
+ /// Helper to configure all things Command related for a View. Called from the View constructor.
+ ///
+ private void SetupCommands ()
+ {
+ // Enter - Raise Accepted
+ AddCommand (Command.Accept, RaiseAccepting);
+
+ // HotKey - SetFocus and raise HandlingHotKey
+ AddCommand (Command.HotKey,
+ () =>
+ {
+ if (RaiseHandlingHotKey () is true)
+ {
+ return true;
+ }
+
+ SetFocus ();
+
+ return true;
+ });
+
+ // Space or single-click - Raise Selecting
+ AddCommand (Command.Select, (ctx) =>
+ {
+ if (RaiseSelecting (ctx) is true)
+ {
+ return true;
+ }
+
+ if (CanFocus)
+ {
+ SetFocus ();
+
+ return true;
+ }
+
+ return false;
+ });
+ }
+
+ ///
+ /// Called when the user is accepting the state of the View and the has been invoked. Calls which can be cancelled; if not cancelled raises .
+ /// event. The default handler calls this method.
+ ///
+ ///
+ ///
+ /// The event should raised after the state of the View has changed (after is raised).
+ ///
+ ///
+ /// If the Accepting event is not handled, will be invoked on the SuperView, enabling default Accept behavior.
+ ///
+ ///
+ /// If a peer-View raises the Accepting event and the event is not cancelled, the will be invoked on the
+ /// first Button in the SuperView that has set to .
+ ///
+ ///
+ ///
+ /// if no event was raised; input proessing should continue.
+ /// if the event was raised and was not handled (or cancelled); input proessing should continue.
+ /// if the event was raised and handled (or cancelled); input proessing should stop.
+ ///
+ protected bool? RaiseAccepting (CommandContext ctx)
+ {
+ CommandEventArgs args = new () { Context = ctx };
+
+ // Best practice is to invoke the virtual method first.
+ // This allows derived classes to handle the event and potentially cancel it.
+ args.Cancel = OnAccepting (args) || args.Cancel;
+
+ if (!args.Cancel)
+ {
+ // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+ Accepting?.Invoke (this, args);
+ }
+
+ // Accept is a special case where if the event is not canceled, the event is
+ // - Invoked on any peer-View with IsDefault == true
+ // - bubbled up the SuperView hierarchy.
+ if (!args.Cancel)
+ {
+ // If there's an IsDefault peer view in Subviews, try it
+ var isDefaultView = SuperView?.Subviews.FirstOrDefault (v => v is Button { IsDefault: true });
+
+ if (isDefaultView != this && isDefaultView is Button { IsDefault: true } button)
+ {
+ bool? handled = isDefaultView.InvokeCommand (Command.Accept, ctx: new (Command.Accept, null, null, this));
+ if (handled == true)
+ {
+ return true;
+ }
+ }
+
+ return SuperView?.InvokeCommand (Command.Accept, ctx: new (Command.Accept, null, null, this)) == true;
+ }
+
+ return Accepting is null ? null : args.Cancel;
+ }
+
+ ///
+ /// Called when the user is accepting the state of the View and the has been invoked. Set CommandEventArgs.Cancel to
+ /// and return to stop processing.
+ ///
+ ///
+ ///
+ /// See for more information.
+ ///
+ ///
+ ///
+ /// to stop processing.
+ protected virtual bool OnAccepting (CommandEventArgs args) { return false; }
+
+ ///
+ /// Cancelable event raised when the user is accepting the state of the View and the has been invoked. Set
+ /// CommandEventArgs.Cancel to cancel the event.
+ ///
+ ///
+ ///
+ /// See for more information.
+ ///
+ ///
+ public event EventHandler? Accepting;
+
+ ///
+ /// Called when the user has performed an action (e.g. ) causing the View to change state. Calls which can be cancelled; if not cancelled raises .
+ /// event. The default handler calls this method.
+ ///
+ ///
+ /// The event should raised after the state of the View has been changed and before see .
+ ///
+ ///
+ /// if no event was raised; input proessing should continue.
+ /// if the event was raised and was not handled (or cancelled); input proessing should continue.
+ /// if the event was raised and handled (or cancelled); input proessing should stop.
+ ///
+ protected bool? RaiseSelecting (CommandContext ctx)
+ {
+ CommandEventArgs args = new () { Context = ctx };
+
+ // Best practice is to invoke the virtual method first.
+ // This allows derived classes to handle the event and potentially cancel it.
+ if (OnSelecting (args) || args.Cancel)
+ {
+ return true;
+ }
+
+ // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+ Selecting?.Invoke (this, args);
+
+ return Selecting is null ? null : args.Cancel;
+ }
+
+ ///
+ /// Called when the user has performed an action (e.g. ) causing the View to change state.
+ /// Set CommandEventArgs.Cancel to
+ /// and return to cancel the state change. The default implementation does nothing.
+ ///
+ /// The event arguments.
+ /// to stop processing.
+ protected virtual bool OnSelecting (CommandEventArgs args) { return false; }
+
+ ///
+ /// Cancelable event raised when the user has performed an action (e.g. ) causing the View to change state.
+ /// CommandEventArgs.Cancel to to cancel the state change.
+ ///
+ public event EventHandler? Selecting;
+
+ ///
+ /// Called when the View is handling the user pressing the View's s. Calls which can be cancelled; if not cancelled raises .
+ /// event. The default handler calls this method.
+ ///
+ ///
+ /// if no event was raised; input proessing should continue.
+ /// if the event was raised and was not handled (or cancelled); input proessing should continue.
+ /// if the event was raised and handled (or cancelled); input proessing should stop.
+ ///
+ protected bool? RaiseHandlingHotKey ()
+ {
+ CommandEventArgs args = new ();
+
+ // Best practice is to invoke the virtual method first.
+ // This allows derived classes to handle the event and potentially cancel it.
+ if (OnHandlingHotKey (args) || args.Cancel)
+ {
+ return true;
+ }
+
+ // If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
+ HandlingHotKey?.Invoke (this, args);
+
+ return HandlingHotKey is null ? null : args.Cancel;
+ }
+
+ ///
+ /// Called when the View is handling the user pressing the View's . Set CommandEventArgs.Cancel to
+ /// to stop processing.
+ ///
+ ///
+ /// to stop processing.
+ protected virtual bool OnHandlingHotKey (CommandEventArgs args) { return false; }
+
+ ///
+ /// Cancelable event raised when the View is handling the user pressing the View's . Set
+ /// CommandEventArgs.Cancel to cancel the event.
+ ///
+ public event EventHandler? HandlingHotKey;
+
+ #endregion Default Implementation
+
+ ///
+ /// Function signature commands.
+ ///
+ /// Provides information about the circumstances of invoking the command (e.g. )
+ ///
+ /// if no command was found; input proessing should continue.
+ /// if the command was invoked and was not handled (or cancelled); input proessing should continue.
+ /// if the command was invoked the command was handled (or cancelled); input proessing should stop.
+ ///
+ public delegate bool? CommandImplementation (CommandContext ctx);
+
+ ///
+ ///
+ /// Sets the function that will be invoked for a . Views should call
+ /// AddCommand for each command they support.
+ ///
+ ///
+ /// If AddCommand has already been called for will
+ /// replace the old one.
+ ///
+ ///
+ ///
+ ///
+ /// This version of AddCommand is for commands that require .
+ ///
+ ///
+ /// The command.
+ /// The delegate.
+ protected void AddCommand (Command command, CommandImplementation impl) { CommandImplementations [command] = impl; }
+
+ ///
+ ///
+ /// Sets the function that will be invoked for a . Views should call
+ /// AddCommand for each command they support.
+ ///
+ ///
+ /// If AddCommand has already been called for will
+ /// replace the old one.
+ ///
+ ///
+ ///
+ ///
+ /// This version of AddCommand is for commands that do not require a .
+ /// If the command requires context, use
+ ///
+ ///
+ ///
+ /// The command.
+ /// The delegate.
+ protected void AddCommand (Command command, Func impl) { CommandImplementations [command] = ctx => impl (); }
+
+ /// Returns all commands that are supported by this .
+ ///
+ public IEnumerable GetSupportedCommands () { return CommandImplementations.Keys; }
+
+ ///
+ /// Invokes the specified commands.
+ ///
+ /// The set of commands to invoke.
+ /// The key that caused the command to be invoked, if any. This will be passed as context with the command.
+ /// The key binding that was bound to the key and caused the invocation, if any. This will be passed as context with the command.
+ ///
+ /// if no command was found; input proessing should continue.
+ /// if at least one command was invoked and was not handled (or cancelled); input proessing should continue.
+ /// if at least one command was invoked the command was handled (or cancelled); input proessing should stop.
+ ///
+ public bool? InvokeCommands (Command [] commands, Key? key = null, KeyBinding? keyBinding = null)
+ {
+ bool? toReturn = null;
+
+ foreach (Command command in commands)
+ {
+ if (!CommandImplementations.ContainsKey (command))
+ {
+ throw new NotSupportedException (
+ @$"A KeyBinding was set up for the command {command} ({key}) but that command is not supported by this View ({GetType ().Name})"
+ );
+ }
+
+ // each command has its own return value
+ bool? thisReturn = InvokeCommand (command, key, keyBinding);
+
+ // if we haven't got anything yet, the current command result should be used
+ toReturn ??= thisReturn;
+
+ // if ever see a true then that's what we will return
+ if (thisReturn ?? false)
+ {
+ toReturn = true;
+ }
+ }
+
+ return toReturn;
+ }
+
+ /// Invokes the specified command.
+ /// The command to invoke.
+ /// The key that caused the command to be invoked, if any. This will be passed as context with the command.
+ /// The key binding that was bound to the key and caused the invocation, if any. This will be passed as context with the command.
+ ///
+ /// if no command was found; input proessing should continue.
+ /// if the command was invoked and was not handled (or cancelled); input proessing should continue.
+ /// if the command was invoked the command was handled (or cancelled); input proessing should stop.
+ ///
+ public bool? InvokeCommand (Command command, Key? key = null, KeyBinding? keyBinding = null)
+ {
+ if (CommandImplementations.TryGetValue (command, out CommandImplementation? implementation))
+ {
+ var context = new CommandContext (command, key, keyBinding); // Create the context here
+ return implementation (context);
+ }
+
+ return null;
+ }
+
+ ///
+ /// Invokes the specified command.
+ ///
+ /// The command to invoke.
+ /// Context to pass with the invocation.
+ ///
+ /// if no command was found; input proessing should continue.
+ /// if the command was invoked and was not handled (or cancelled); input proessing should continue.
+ /// if the command was invoked the command was handled (or cancelled); input proessing should stop.
+ ///
+ public bool? InvokeCommand (Command command, CommandContext ctx)
+ {
+ if (CommandImplementations.TryGetValue (command, out CommandImplementation? implementation))
+ {
+ return implementation (ctx);
+ }
+
+ return null;
+ }
+}
diff --git a/Terminal.Gui/View/View.Content.cs b/Terminal.Gui/View/View.Content.cs
index 12851d12cb..95433903ec 100644
--- a/Terminal.Gui/View/View.Content.cs
+++ b/Terminal.Gui/View/View.Content.cs
@@ -12,6 +12,9 @@ public partial class View
///
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
/// Negative sizes are not supported.
///
///
@@ -55,6 +58,9 @@ public void SetContentSize (Size? contentSize)
///
/// a>
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
/// If the content size was not explicitly set by , and the View has no visible subviews, will return the
/// size of
/// .
@@ -85,6 +91,9 @@ public void SetContentSize (Size? contentSize)
/// size or not.
///
///
+ ///
+ /// See the View Layout Deep Dive for more information:
+ ///
///
///
/// Value Result
@@ -236,6 +245,9 @@ public ViewportSettings ViewportSettings
///
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
/// Positive values for the location indicate the visible area is offset into (down-and-right) the View's virtual
/// . This enables scrolling down and to the right (e.g. in a
/// .
diff --git a/Terminal.Gui/View/View.Diagnostics.cs b/Terminal.Gui/View/View.Diagnostics.cs
index 6e5797e26c..8145af8fc2 100644
--- a/Terminal.Gui/View/View.Diagnostics.cs
+++ b/Terminal.Gui/View/View.Diagnostics.cs
@@ -20,10 +20,9 @@ public enum ViewDiagnosticFlags : uint
Padding = 0b_0000_0010,
///
- /// When enabled, and
- /// will invert the foreground and background colors.
+ /// When enabled the View's colors will be darker when the mouse is hovering over the View (See and .
///
- MouseEnter = 0b_0000_00100
+ Hover = 0b_0000_00100
}
public partial class View
diff --git a/Terminal.Gui/View/View.Drawing.cs b/Terminal.Gui/View/View.Drawing.cs
index 9762ba98b4..4401d29580 100644
--- a/Terminal.Gui/View/View.Drawing.cs
+++ b/Terminal.Gui/View/View.Drawing.cs
@@ -1,13 +1,12 @@
-using System.Drawing;
-
+#nullable enable
namespace Terminal.Gui;
public partial class View // Drawing APIs
{
- private ColorScheme _colorScheme;
+ private ColorScheme? _colorScheme;
/// The color scheme for this view, if it is not defined, it returns the 's color scheme.
- public virtual ColorScheme ColorScheme
+ public virtual ColorScheme? ColorScheme
{
get
{
@@ -23,6 +22,11 @@ public virtual ColorScheme ColorScheme
if (_colorScheme != value)
{
_colorScheme = value;
+
+ if (Border is { } && Border.LineStyle != LineStyle.None && Border.ColorScheme is { })
+ {
+ Border.ColorScheme = _colorScheme;
+ }
SetNeedsDisplay ();
}
}
@@ -56,7 +60,7 @@ public bool NeedsDisplay
public bool SubViewNeedsDisplay { get; private set; }
///
- /// Gets or sets whether this View will use it's SuperView's for rendering any
+ /// Gets or sets whether this View will use it's SuperView's for rendering any
/// lines. If the rendering of any borders drawn by this Frame will be done by its parent's
/// SuperView. If (the default) this View's method will be
/// called to render the borders.
@@ -86,7 +90,8 @@ public void AddRune (int col, int row, Rune rune)
///
/// If has only
/// the portion of the content
- /// area that is visible within the will be cleared. This is useful for views that have a
+ /// area that is visible within the will be cleared. This is useful for views that have
+ /// a
/// content area larger than the Viewport (e.g. when is
/// enabled) and want
/// the area outside the content to be visually distinct.
@@ -143,15 +148,15 @@ public void FillRect (Rectangle rect, Color? color = null)
/// Sets the 's clip region to .
///
- ///
- /// By default, the clip rectangle is set to the intersection of the current clip region and the
- /// . This ensures that drawing is constrained to the viewport, but allows
- /// content to be drawn beyond the viewport.
- ///
- ///
- /// If has set, clipping will be
- /// applied to just the visible content area.
- ///
+ ///
+ /// By default, the clip rectangle is set to the intersection of the current clip region and the
+ /// . This ensures that drawing is constrained to the viewport, but allows
+ /// content to be drawn beyond the viewport.
+ ///
+ ///
+ /// If has set, clipping will be
+ /// applied to just the visible content area.
+ ///
///
///
/// The current screen-relative clip region, which can be then re-applied by setting
@@ -182,11 +187,15 @@ public Rectangle SetClip ()
}
///
- /// Draws the view. Causes the following virtual methods to be called (along with their related events):
+ /// Draws the view if it needs to be drawn. Causes the following virtual methods to be called (along with their related events):
/// , .
///
///
///
+ /// The view will only be drawn if it is visible, and has any of , ,
+ /// or set.
+ ///
+ ///
/// Always use (view-relative) when calling , NOT
/// (superview-relative).
///
@@ -202,6 +211,23 @@ public Rectangle SetClip ()
///
public void Draw ()
{
+ if (!CanBeVisible (this))
+ {
+ return;
+ }
+
+ // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient.
+ // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413
+ if (Arrangement.HasFlag (ViewArrangement.Overlapped))
+ {
+ SetNeedsDisplay ();
+ }
+
+ if (!NeedsDisplay && !SubViewNeedsDisplay && !LayoutNeeded)
+ {
+ return;
+ }
+
OnDrawAdornments ();
if (ColorScheme is { })
@@ -257,8 +283,7 @@ public void Draw ()
/// .
///
///
- [CanBeNull]
- public event EventHandler DrawContent;
+ public event EventHandler? DrawContent;
/// Event invoked when the content area of the View is completed drawing.
///
@@ -268,8 +293,7 @@ public void Draw ()
/// .
///
///
- [CanBeNull]
- public event EventHandler DrawContentComplete;
+ public event EventHandler? DrawContentComplete;
/// Utility function to draw strings that contain a hotkey.
/// String to display, the hotkey specifier before a letter flags the next letter as the hotkey.
@@ -310,19 +334,18 @@ public void DrawHotString (string text, Attribute hotColor, Attribute normalColo
/// If set to this uses the focused colors from the color scheme, otherwise
/// the regular ones.
///
- /// The color scheme to use.
- public void DrawHotString (string text, bool focused, ColorScheme scheme)
+ public void DrawHotString (string text, bool focused)
{
if (focused)
{
- DrawHotString (text, scheme.HotFocus, scheme.Focus);
+ DrawHotString (text, GetHotFocusColor (), GetFocusColor ());
}
else
{
DrawHotString (
text,
- Enabled ? scheme.HotNormal : scheme.Disabled,
- Enabled ? scheme.Normal : scheme.Disabled
+ Enabled ? GetHotNormalColor () : ColorScheme!.Disabled,
+ Enabled ? GetNormalColor () : ColorScheme!.Disabled
);
}
}
@@ -335,13 +358,27 @@ public void DrawHotString (string text, bool focused, ColorScheme scheme)
///
public virtual Attribute GetFocusColor ()
{
- ColorScheme cs = ColorScheme;
+ ColorScheme? cs = ColorScheme;
+
if (cs is null)
{
cs = new ();
}
- return Enabled ? cs.Focus : cs.Disabled;
+ return Enabled ? GetColor (cs.Focus) : cs.Disabled;
+ }
+
+ /// Determines the current based on the value.
+ ///
+ /// if is or
+ /// if is . If it's
+ /// overridden can return other values.
+ ///
+ public virtual Attribute GetHotFocusColor ()
+ {
+ ColorScheme? cs = ColorScheme ?? new ();
+
+ return Enabled ? GetColor (cs.HotFocus) : cs.Disabled;
}
/// Determines the current based on the value.
@@ -352,14 +389,14 @@ public virtual Attribute GetFocusColor ()
///
public virtual Attribute GetHotNormalColor ()
{
- ColorScheme cs = ColorScheme;
+ ColorScheme? cs = ColorScheme;
if (cs is null)
{
cs = new ();
}
- return Enabled ? cs.HotNormal : cs.Disabled;
+ return Enabled ? GetColor (cs.HotNormal) : cs.Disabled;
}
/// Determines the current based on the value.
@@ -370,14 +407,30 @@ public virtual Attribute GetHotNormalColor ()
///
public virtual Attribute GetNormalColor ()
{
- ColorScheme cs = ColorScheme;
+ ColorScheme? cs = ColorScheme;
if (cs is null)
{
cs = new ();
}
- return Enabled ? cs.Normal : cs.Disabled;
+ Attribute disabled = new (cs.Disabled.Foreground, cs.Disabled.Background);
+ if (Diagnostics.HasFlag (ViewDiagnosticFlags.Hover) && _hovering)
+ {
+ disabled = new (disabled.Foreground.GetDarkerColor (), disabled.Background.GetDarkerColor ());
+ }
+ return Enabled ? GetColor (cs.Normal) : disabled;
+ }
+
+ private Attribute GetColor (Attribute inputAttribute)
+ {
+ Attribute attr = inputAttribute;
+ if (Diagnostics.HasFlag (ViewDiagnosticFlags.Hover) && _hovering)
+ {
+ attr = new (attr.Foreground.GetDarkerColor (), attr.Background.GetDarkerColor ());
+ }
+
+ return attr;
}
/// Moves the drawing cursor to the specified -relative location in the view.
@@ -403,7 +456,7 @@ public bool Move (int col, int row)
return false;
}
- var screen = ViewportToScreen (new Point (col, row));
+ Point screen = ViewportToScreen (new Point (col, row));
Driver?.Move (screen.X, screen.Y);
return true;
@@ -442,12 +495,14 @@ public virtual bool OnDrawAdornments ()
///
///
/// The Location and Size indicate what part of the View's content, defined
- /// by , is visible and should be drawn. The coordinates taken by and
+ /// by , is visible and should be drawn. The coordinates taken by
+ /// and
/// are relative to , thus if ViewPort.Location.Y is 5
/// the 6th row of the content should be drawn using MoveTo (x, 5).
///
///
- /// If is larger than ViewPort.Size drawing code should use
+ /// If is larger than ViewPort.Size drawing code should use
+ ///
/// to constrain drawing for better performance.
///
///
@@ -467,14 +522,15 @@ public virtual void OnDrawContent (Rectangle viewport)
{
if (NeedsDisplay)
{
- if (SuperView is { })
+ if (!CanBeVisible (this))
{
- Clear ();
+ return;
}
- if (!CanBeVisible (this))
+ // BUGBUG: this clears way too frequently. Need to optimize this.
+ if (SuperView is { } || Arrangement.HasFlag (ViewArrangement.Overlapped))
{
- return;
+ Clear ();
}
if (!string.IsNullOrEmpty (TextFormatter.Text))
@@ -492,7 +548,7 @@ public virtual void OnDrawContent (Rectangle viewport)
TextFormatter?.Draw (
drawRect,
HasFocus ? GetFocusColor () : GetNormalColor (),
- HasFocus ? ColorScheme.HotFocus : GetHotNormalColor (),
+ HasFocus ? GetHotFocusColor () : GetHotNormalColor (),
Rectangle.Empty
);
SetSubViewNeedsDisplay ();
@@ -503,24 +559,14 @@ public virtual void OnDrawContent (Rectangle viewport)
// TODO: Implement OnDrawSubviews (cancelable);
if (_subviews is { } && SubViewNeedsDisplay)
{
- IEnumerable subviewsNeedingDraw;
- if (TabStop == TabBehavior.TabGroup && _subviews.Count(v => v.Arrangement.HasFlag (ViewArrangement.Overlapped)) > 0)
- {
- // TODO: This is a temporary hack to make overlapped non-Toplevels have a zorder. See also View.SetFocus
- subviewsNeedingDraw = _subviews.Where (
- view => view.Visible
- && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
- ).Reverse ();
-
- }
- else
- {
- subviewsNeedingDraw = _subviews.Where (
- view => view.Visible
- && (view.NeedsDisplay || view.SubViewNeedsDisplay || view.LayoutNeeded)
- );
+ IEnumerable subviewsNeedingDraw = _subviews.Where (
+ view => view.Visible
+ && (view.NeedsDisplay
+ || view.SubViewNeedsDisplay
+ || view.LayoutNeeded
+ || view.Arrangement.HasFlag (ViewArrangement.Overlapped)
+ ));
- }
foreach (View view in subviewsNeedingDraw)
{
if (view.LayoutNeeded)
@@ -528,6 +574,13 @@ public virtual void OnDrawContent (Rectangle viewport)
view.LayoutSubviews ();
}
+ // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient.
+ // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413
+ if (view.Arrangement.HasFlag (ViewArrangement.Overlapped))
+ {
+ view.SetNeedsDisplay ();
+ }
+
view.Draw ();
}
}
@@ -564,7 +617,7 @@ public virtual bool OnRenderLineCanvas ()
// Get the entire map
if (p.Value is { })
{
- Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme.Normal);
+ Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
Driver.Move (p.Key.X, p.Key.Y);
// TODO: #2616 - Support combining sequences that don't normalize
@@ -589,7 +642,7 @@ public virtual bool OnRenderLineCanvas ()
// Get the entire map
if (p.Value is { })
{
- Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme.Normal);
+ Driver.SetAttribute (p.Value.Value.Attribute ?? ColorScheme!.Normal);
Driver.Move (p.Key.X, p.Key.Y);
// TODO: #2616 - Support combining sequences that don't normalize
@@ -608,10 +661,7 @@ public virtual bool OnRenderLineCanvas ()
/// If the view has not been initialized ( is ), this method
/// does nothing.
///
- public void SetNeedsDisplay ()
- {
- SetNeedsDisplay (Viewport);
- }
+ public void SetNeedsDisplay () { SetNeedsDisplay (Viewport); }
/// Expands the area of this view needing to be redrawn to include .
///
@@ -671,8 +721,6 @@ public void SetSubViewNeedsDisplay ()
if (SuperView is { SubViewNeedsDisplay: false })
{
SuperView.SetSubViewNeedsDisplay ();
-
- return;
}
}
diff --git a/Terminal.Gui/View/View.Hierarchy.cs b/Terminal.Gui/View/View.Hierarchy.cs
index 4ef00a5b77..02b737896b 100644
--- a/Terminal.Gui/View/View.Hierarchy.cs
+++ b/Terminal.Gui/View/View.Hierarchy.cs
@@ -159,14 +159,17 @@ public virtual void OnRemoved (SuperViewChangedEventArgs e)
Rectangle touched = view.Frame;
- // If a view being removed is focused, it should lose focus.
- if (view.HasFocus)
+ bool hadFocus = view.HasFocus;
+ bool couldFocus = view.CanFocus;
+
+ if (hadFocus)
{
- view.HasFocus = false;
+ view.CanFocus = false; // If view had focus, this will ensure it doesn't and it stays that way
}
+ Debug.Assert (!view.HasFocus);
_subviews.Remove (view);
- view._superView = null; // Null this AFTER removing focus
+ view._superView = null;
SetNeedsLayout ();
SetNeedsDisplay ();
@@ -179,9 +182,11 @@ public virtual void OnRemoved (SuperViewChangedEventArgs e)
}
}
- if (HasFocus)
+ view.CanFocus = couldFocus; // Restore to previous value
+
+ if (_previouslyFocused == view)
{
- FocusDeepest (NavigationDirection.Forward, TabStop);
+ _previouslyFocused = null;
}
OnRemoved (new (this, view));
diff --git a/Terminal.Gui/View/View.Keyboard.cs b/Terminal.Gui/View/View.Keyboard.cs
index ce72d068a2..f9c8355127 100644
--- a/Terminal.Gui/View/View.Keyboard.cs
+++ b/Terminal.Gui/View/View.Keyboard.cs
@@ -11,14 +11,12 @@ public partial class View // Keyboard APIs
private void SetupKeyboard ()
{
KeyBindings = new (this);
+ KeyBindings.Add (Key.Space, Command.Select);
+ KeyBindings.Add (Key.Enter, Command.Accept);
+
+ // Note, setting HotKey will bind HotKey to Command.HotKey
HotKeySpecifier = (Rune)'_';
TitleTextFormatter.HotKeyChanged += TitleTextFormatter_HotKeyChanged;
-
- // By default, the HotKey command sets the focus
- AddCommand (Command.HotKey, OnHotKey);
-
- // By default, the Accept command raises the Accept event
- AddCommand (Command.Accept, OnAccept);
}
///
@@ -28,22 +26,6 @@ private void SetupKeyboard ()
#region HotKey Support
- ///
- /// Called when the HotKey command () is invoked. Causes this view to be focused.
- ///
- /// If the command was canceled.
- private bool? OnHotKey ()
- {
- if (CanFocus)
- {
- SetFocus ();
-
- return true;
- }
-
- return false;
- }
-
/// Invoked when the is changed.
public event EventHandler? HotKeyChanged;
@@ -52,16 +34,22 @@ private void SetupKeyboard ()
///
/// Gets or sets the hot key defined for this view. Pressing the hot key on the keyboard while this view has focus will
- /// invoke the and commands.
- /// causes the view to be focused and does nothing. By default, the HotKey is
- /// automatically set to the first character of that is prefixed with .
+ /// invoke . By default, the HotKey is set to the first character of
+ /// that is prefixed with .
+ ///
+ /// A HotKey is a keypress that causes a visible UI item to perform an action. For example, in a Dialog,
+ /// with a Button with the text of "_Text" Alt+T will cause the button to gain focus and to raise its
+ /// event.
+ /// Or, in a
+ /// with "_File _Edit", Alt+F will select (show) the "_File" menu. If the "_File" menu
+ /// has a
+ /// sub-menu of "_New" Alt+N or N will ONLY select the "_New" sub-menu if the "_File" menu is already
+ /// opened.
+ ///
///
- /// A HotKey is a keypress that selects a visible UI item. For selecting items across `s (e.g.a
- /// in a ) the keypress must include the
- /// modifier. For selecting items within a View that are not Views themselves, the keypress can be key without the
- /// Alt modifier. For example, in a Dialog, a Button with the text of "_Text" can be selected with Alt-T. Or, in a
- /// with "_File _Edit", Alt-F will select (show) the "_File" menu. If the "_File" menu has a
- /// sub-menu of "_New" `Alt-N` or `N` will ONLY select the "_New" sub-menu if the "_File" menu is already opened.
+ /// View subclasses can use to
+ /// define the
+ /// behavior of the hot key.
///
///
///
@@ -510,7 +498,7 @@ public virtual bool OnKeyUp (Key keyEvent)
/// Gets the key bindings for this view.
public KeyBindings KeyBindings { get; internal set; } = null!;
- private Dictionary> CommandImplementations { get; } = new ();
+ private Dictionary CommandImplementations { get; } = new ();
///
/// Low-level API called when a user presses a key; invokes any key bindings set on the view. This is called
@@ -523,8 +511,9 @@ public virtual bool OnKeyUp (Key keyEvent)
/// Contains the details about the key that produced the event.
/// The scope.
///
- /// if the key press was not handled. if the keypress was handled
- /// and no other view should see it.
+ /// if no event was raised; input proessing should continue.
+ /// if the event was raised and was not handled (or cancelled); input proessing should continue.
+ /// if the event was raised and handled (or cancelled); input proessing should stop.
///
public virtual bool? OnInvokingKeyBindings (Key keyEvent, KeyBindingScope scope)
{
@@ -579,6 +568,13 @@ public virtual bool OnKeyUp (Key keyEvent)
private bool ProcessAdornmentKeyBindings (Adornment adornment, Key keyEvent, KeyBindingScope scope, ref bool? handled)
{
+ bool? adornmentHandled = adornment.OnInvokingKeyBindings (keyEvent, scope);
+
+ if (adornmentHandled is true)
+ {
+ return true;
+ }
+
if (adornment?.Subviews is null)
{
return false;
@@ -681,7 +677,7 @@ public bool IsHotKeyKeyBound (Key key, out View? boundView)
}
///
- /// Invoked when a key is pressed that may be mapped to a key binding. Set to true to
+ /// Raised when a key is pressed that may be mapped to a key binding. Set to true to
/// stop the key from being processed by other views.
///
public event EventHandler? InvokingKeyBindings;
@@ -693,9 +689,9 @@ public bool IsHotKeyKeyBound (Key key, out View? boundView)
/// The key event passed.
/// The scope.
///
- /// if no command was bound the . if
- /// commands were invoked and at least one handled the command. if commands were invoked and at
- /// none handled the command.
+ /// if no command was invoked; input proessing should continue.
+ /// if at least one command was invoked and was not handled (or cancelled); input proessing should continue.
+ /// if at least one command was invoked and handled (or cancelled); input proessing should stop.
///
protected bool? InvokeKeyBindings (Key key, KeyBindingScope scope)
{
@@ -726,6 +722,7 @@ public bool IsHotKeyKeyBound (Key key, out View? boundView)
}
#endif
+ return InvokeCommands (binding.Commands, key, binding);
foreach (Command command in binding.Commands)
{
@@ -752,111 +749,5 @@ public bool IsHotKeyKeyBound (Key key, out View? boundView)
return toReturn;
}
- ///
- /// Invokes the specified commands.
- ///
- ///
- /// The key that caused the commands to be invoked, if any.
- ///
- ///
- /// if no command was found.
- /// if the command was invoked the command was handled.
- /// if the command was invoked and the command was not handled.
- ///
- public bool? InvokeCommands (Command [] commands, Key? key = null, KeyBinding? keyBinding = null)
- {
- bool? toReturn = null;
-
- foreach (Command command in commands)
- {
- if (!CommandImplementations.ContainsKey (command))
- {
- throw new NotSupportedException (@$"{command} is not supported by ({GetType ().Name}).");
- }
-
- // each command has its own return value
- bool? thisReturn = InvokeCommand (command, key, keyBinding);
-
- // if we haven't got anything yet, the current command result should be used
- toReturn ??= thisReturn;
-
- // if ever see a true then that's what we will return
- if (thisReturn ?? false)
- {
- toReturn = true;
- }
- }
-
- return toReturn;
- }
-
- /// Invokes the specified command.
- /// The command to invoke.
- /// The key that caused the command to be invoked, if any.
- ///
- ///
- /// if no command was found. if the command was invoked, and it
- /// handled the command. if the command was invoked, and it did not handle the command.
- ///
- public bool? InvokeCommand (Command command, Key? key = null, KeyBinding? keyBinding = null)
- {
- if (CommandImplementations.TryGetValue (command, out Func? implementation))
- {
- var context = new CommandContext (command, key, keyBinding); // Create the context here
-
- return implementation (context);
- }
-
- return null;
- }
-
- ///
- ///
- /// Sets the function that will be invoked for a . Views should call
- /// AddCommand for each command they support.
- ///
- ///
- /// If AddCommand has already been called for will
- /// replace the old one.
- ///
- ///
- ///
- ///
- /// This version of AddCommand is for commands that require . Use
- ///
- /// in cases where the command does not require a .
- ///
- ///
- /// The command.
- /// The function.
- protected void AddCommand (Command command, Func f) { CommandImplementations [command] = f; }
-
- ///
- ///
- /// Sets the function that will be invoked for a . Views should call
- /// AddCommand for each command they support.
- ///
- ///
- /// If AddCommand has already been called for will
- /// replace the old one.
- ///
- ///
- ///
- ///
- /// This version of AddCommand is for commands that do not require a .
- /// If the command requires context, use
- ///
- ///
- ///
- /// The command.
- /// The function.
- protected void AddCommand (Command command, Func f) { CommandImplementations [command] = ctx => f (); }
-
- /// Returns all commands that are supported by this .
- ///
- public IEnumerable GetSupportedCommands () { return CommandImplementations.Keys; }
-
- // TODO: Add GetKeysBoundToCommand() - given a Command, return all Keys that would invoke it
-
#endregion Key Bindings
}
diff --git a/Terminal.Gui/View/View.Layout.cs b/Terminal.Gui/View/View.Layout.cs
index 99b339da32..2d8fdfedad 100644
--- a/Terminal.Gui/View/View.Layout.cs
+++ b/Terminal.Gui/View/View.Layout.cs
@@ -1,5 +1,6 @@
#nullable enable
using System.Diagnostics;
+using Microsoft.CodeAnalysis;
namespace Terminal.Gui;
@@ -12,83 +13,7 @@ public partial class View // Layout APIs
/// if the specified SuperView-relative coordinates are within the View.
public virtual bool Contains (in Point location) { return Frame.Contains (location); }
- /// Finds the first Subview of that is visible at the provided location.
- ///
- ///
- /// Used to determine what view the mouse is over.
- ///
- ///
- /// The view to scope the search by.
- /// .SuperView-relative coordinate.
- ///
- /// The view that was found at the coordinate.
- /// if no view was found.
- ///
-
- // CONCURRENCY: This method is not thread-safe. Undefined behavior and likely program crashes are exposed by unsynchronized access to InternalSubviews.
- internal static View? FindDeepestView (View? start, in Point location)
- {
- Point currentLocation = location;
-
- while (start is { Visible: true } && start.Contains (currentLocation))
- {
- Adornment? found = null;
-
- if (start.Margin.Contains (currentLocation))
- {
- found = start.Margin;
- }
- else if (start.Border.Contains (currentLocation))
- {
- found = start.Border;
- }
- else if (start.Padding.Contains (currentLocation))
- {
- found = start.Padding;
- }
-
- Point viewportOffset = start.GetViewportOffsetFromFrame ();
-
- if (found is { })
- {
- start = found;
- viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
- }
-
- int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
- int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y);
-
- View? subview = null;
-
- for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
- {
- if (start.InternalSubviews [i].Visible
- && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
- {
- subview = start.InternalSubviews [i];
- currentLocation.X = startOffsetX + start.Viewport.X;
- currentLocation.Y = startOffsetY + start.Viewport.Y;
-
- // start is the deepest subview under the mouse; stop searching the subviews
- break;
- }
- }
-
- if (subview is null)
- {
- // No subview was found that's under the mouse, so we're done
- return start;
- }
-
- // We found a subview of start that's under the mouse, continue...
- start = subview;
- }
-
- return null;
- }
-
// BUGBUG: This method interferes with Dialog/MessageBox default min/max size.
-
///
/// Gets a new location of the that is within the Viewport of the 's
/// (e.g. for dragging a Window). The `out` parameters are the new X and Y coordinates.
@@ -113,13 +38,14 @@ public partial class View // Layout APIs
int targetX,
int targetY,
out int nx,
- out int ny,
- out StatusBar? statusBar
+ out int ny
+ //,
+ // out StatusBar? statusBar
)
{
int maxDimension;
View? superView;
- statusBar = null!;
+ //statusBar = null!;
if (viewToMove is not Toplevel || viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
{
@@ -187,26 +113,26 @@ out StatusBar? statusBar
ny = Math.Max (targetY, maxDimension);
- if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
- {
- statusVisible = Application.Top?.StatusBar?.Visible == true;
- statusBar = Application.Top?.StatusBar!;
- }
- else
- {
- View? t = viewToMove!.SuperView;
-
- while (t is { } and not Toplevel)
- {
- t = t.SuperView;
- }
-
- if (t is Toplevel topLevel)
- {
- statusVisible = topLevel.StatusBar?.Visible == true;
- statusBar = topLevel.StatusBar!;
- }
- }
+ //if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
+ //{
+ // statusVisible = Application.Top?.StatusBar?.Visible == true;
+ // statusBar = Application.Top?.StatusBar!;
+ //}
+ //else
+ //{
+ // View? t = viewToMove!.SuperView;
+
+ // while (t is { } and not Toplevel)
+ // {
+ // t = t.SuperView;
+ // }
+
+ // if (t is Toplevel topLevel)
+ // {
+ // statusVisible = topLevel.StatusBar?.Visible == true;
+ // statusBar = topLevel.StatusBar!;
+ // }
+ //}
if (viewToMove?.SuperView is null || viewToMove == Application.Top || viewToMove?.SuperView == Application.Top)
{
@@ -252,6 +178,10 @@ out StatusBar? statusBar
///
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
+ ///
/// Frame is relative to the 's Content, which is bound by
/// .
///
@@ -288,6 +218,8 @@ public Rectangle Frame
{
OnResizeNeeded ();
}
+
+ SetNeedsDisplay ();
}
}
@@ -369,6 +301,10 @@ public virtual Point ScreenToFrame (in Point location)
/// The object representing the X position.
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
+ ///
/// The position is relative to the 's Content, which is bound by
/// .
///
@@ -408,6 +344,10 @@ public Pos X
/// The object representing the Y position.
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
+ ///
/// The position is relative to the 's Content, which is bound by
/// .
///
@@ -446,6 +386,10 @@ public Pos Y
/// The object representing the height of the view (the number of rows).
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
+ ///
/// The dimension is relative to the 's Content, which is bound by
///
/// .
@@ -495,6 +439,10 @@ public Dim? Height
/// The object representing the width of the view (the number of columns).
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
+ ///
/// The dimension is relative to the 's Content, which is bound by
///
/// .
@@ -666,6 +614,10 @@ internal void SetRelativeLayout (Size superviewContentSize)
///
///
///
+ /// See the View Layout Deep Dive for more information:
+ ///
+ ///
+ ///
/// The position and dimensions of the view are indeterminate until the view has been initialized. Therefore, the
/// behavior of this method is indeterminate if is .
///
@@ -768,14 +720,26 @@ internal void OnResizeNeeded ()
LayoutAdornments ();
}
- SetNeedsDisplay ();
SetNeedsLayout ();
+
+ // TODO: This ensures overlapped views are drawn correctly. However, this is inefficient.
+ // TODO: The correct fix is to implement non-rectangular clip regions: https://github.com/gui-cs/Terminal.Gui/issues/3413
+ if (Arrangement.HasFlag (ViewArrangement.Overlapped))
+ {
+ foreach (Toplevel v in Application.TopLevels)
+ {
+ if (v.Visible && v != this)
+ {
+ v.SetNeedsDisplay ();
+ }
+ }
+ }
}
internal bool LayoutNeeded { get; private set; } = true;
///
- /// Sets the internal flag for this View and all of it's subviews and it's SuperView.
+ /// Sets for this View and all of it's subviews and it's SuperView.
/// The main loop will call SetRelativeLayout and LayoutSubviews for any view with set.
///
internal void SetNeedsLayout ()
diff --git a/Terminal.Gui/View/View.Mouse.cs b/Terminal.Gui/View/View.Mouse.cs
index b66c07fe35..2fc180ece0 100644
--- a/Terminal.Gui/View/View.Mouse.cs
+++ b/Terminal.Gui/View/View.Mouse.cs
@@ -5,34 +5,196 @@ namespace Terminal.Gui;
public partial class View // Mouse APIs
{
- private ColorScheme? _savedHighlightColorScheme;
+ #region MouseEnterLeave
+
+ private bool _hovering;
+ private ColorScheme? _savedNonHoverColorScheme;
///
- /// Fired when the view is highlighted. Set to
- /// to implement a custom highlight scheme or prevent the view from being highlighted.
+ /// INTERNAL Called by when the mouse moves over the View's .
+ /// will
+ /// be raised when the mouse is no longer over the . If another View occludes this View, the
+ /// that View will also receive MouseEnter/Leave events.
///
- public event EventHandler>? Highlight;
+ ///
+ ///
+ /// if the event was canceled, if not, if the
+ /// view is not visible. Cancelling the event
+ /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
+ ///
+ internal bool? NewMouseEnterEvent (CancelEventArgs eventArgs)
+ {
+ // Pre-conditions
+ if (!CanBeVisible (this))
+ {
+ return null;
+ }
+
+ // Cancellable event
+ if (OnMouseEnter (eventArgs))
+ {
+ return true;
+ }
+
+ MouseEnter?.Invoke (this, eventArgs);
+
+ _hovering = !eventArgs.Cancel;
+
+ if (eventArgs.Cancel)
+ {
+ return true;
+ }
+
+ // Post-conditions
+ if (HighlightStyle.HasFlag (HighlightStyle.Hover) || Diagnostics.HasFlag (ViewDiagnosticFlags.Hover))
+ {
+ HighlightStyle copy = HighlightStyle;
+ var hover = HighlightStyle.Hover;
+ CancelEventArgs args = new (ref copy, ref hover);
+
+ if (RaiseHighlight (args) || args.Cancel)
+ {
+ return args.Cancel;
+ }
+
+ ColorScheme? cs = ColorScheme;
+
+ if (cs is null)
+ {
+ cs = new ();
+ }
+
+ _savedNonHoverColorScheme = cs;
+
+ ColorScheme = ColorScheme?.GetHighlightColorScheme ();
+ }
+
+ return false;
+ }
///
- /// Gets or sets whether the will be highlighted visually while the mouse button is
- /// pressed.
+ /// Called when the mouse moves over the View's and no other non-Subview occludes it.
+ /// will
+ /// be raised when the mouse is no longer over the .
///
- public HighlightStyle HighlightStyle { get; set; }
+ ///
+ ///
+ /// A view must be visible to receive Enter events (Leave events are always received).
+ ///
+ ///
+ /// If the event is cancelled, the mouse event will not be propagated to other views and
+ /// will not be raised.
+ ///
+ ///
+ /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's .
+ ///
+ ///
+ /// See for more information.
+ ///
+ ///
+ ///
+ ///
+ /// if the event was canceled, if not. Cancelling the event
+ /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
+ ///
+ protected virtual bool OnMouseEnter (CancelEventArgs eventArgs) { return false; }
- /// Event fired when a mouse click occurs.
+ ///
+ /// Raised when the mouse moves over the View's . will
+ /// be raised when the mouse is no longer over the . If another View occludes this View, the
+ /// that View will also receive MouseEnter/Leave events.
+ ///
///
///
- /// Fired when the mouse is either clicked or double-clicked. Check
- /// to see which button was clicked.
+ /// A view must be visible to receive Enter events (Leave events are always received).
///
///
- /// The coordinates are relative to .
+ /// If the event is cancelled, the mouse event will not be propagated to other views.
+ ///
+ ///
+ /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's .
+ ///
+ ///
+ /// Set to if the event was canceled,
+ /// if not. Cancelling the event
+ /// prevents Views higher in the visible hierarchy from receiving Enter/Leave events.
+ ///
+ ///
+ /// See for more information.
///
///
- public event EventHandler? MouseClick;
+ public event EventHandler? MouseEnter;
+
+ ///
+ /// INTERNAL Called by when the mouse leaves , or is occluded
+ /// by another non-SubView.
+ ///
+ ///
+ ///
+ /// This method calls and raises the event.
+ ///
+ ///
+ /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's .
+ ///
+ ///
+ /// See for more information.
+ ///
+ ///
+ internal void NewMouseLeaveEvent ()
+ {
+ // Pre-conditions
+
+ // Non-cancellable event
+ OnMouseLeave ();
+
+ MouseLeave?.Invoke (this, EventArgs.Empty);
+
+ // Post-conditions
+ _hovering = false;
- /// Event fired when the mouse moves into the View's .
- public event EventHandler? MouseEnter;
+ if (HighlightStyle.HasFlag (HighlightStyle.Hover) || Diagnostics.HasFlag (ViewDiagnosticFlags.Hover))
+ {
+ HighlightStyle copy = HighlightStyle;
+ var hover = HighlightStyle.None;
+ RaiseHighlight (new (ref copy, ref hover));
+
+ if (_savedNonHoverColorScheme is { })
+ {
+ ColorScheme = _savedNonHoverColorScheme;
+ _savedNonHoverColorScheme = null;
+ }
+ }
+ }
+
+ ///
+ /// Called when the mouse moves outside View's , or is occluded by another non-SubView.
+ ///
+ ///
+ ///
+ /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's .
+ ///
+ ///
+ /// See for more information.
+ ///
+ ///
+ protected virtual void OnMouseLeave () { }
+
+ ///
+ /// Raised when the mouse moves outside View's , or is occluded by another non-SubView.
+ ///
+ ///
+ ///
+ /// Adornments receive MouseEnter/Leave events when the mouse is over the Adornment's .
+ ///
+ ///
+ /// See for more information.
+ ///
+ ///
+ public event EventHandler? MouseLeave;
+
+ #endregion MouseEnterLeave
+
+ #region Low Level Mouse Events
/// Event fired when a mouse event occurs.
///
@@ -42,9 +204,6 @@ public partial class View // Mouse APIs
///
public event EventHandler? MouseEvent;
- /// Event fired when the mouse leaves the View's .
- public event EventHandler? MouseLeave;
-
///
/// Processes a . This method is called by when a mouse
/// event occurs.
@@ -58,7 +217,7 @@ public partial class View // Mouse APIs
/// mouse buttons was clicked, it calls to process the click.
///
///
- /// See for more information.
+ /// See for more information.
///
///
/// If is , the event
@@ -69,6 +228,7 @@ public partial class View // Mouse APIs
/// if the event was handled, otherwise.
public bool? NewMouseEvent (MouseEvent mouseEvent)
{
+ // Pre-conditions
if (!Enabled)
{
// A disabled view should not eat mouse events
@@ -80,6 +240,12 @@ public partial class View // Mouse APIs
return false;
}
+ if (!WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
+ {
+ return false;
+ }
+
+ // Cancellable event
if (OnMouseEvent (mouseEvent))
{
// Technically mouseEvent.Handled should already be true if implementers of OnMouseEvent
@@ -87,19 +253,22 @@ public partial class View // Mouse APIs
return mouseEvent.Handled = true;
}
+ // BUGBUG: MouseEvent should be fired from here. Fix this in https://github.com/gui-cs/Terminal.Gui/issues/3029
+
+ // Post-Conditions
if (HighlightStyle != HighlightStyle.None || (WantContinuousButtonPressed && WantMousePositionReports))
{
- if (HandlePressed (mouseEvent))
+ if (WhenGrabbedHandlePressed (mouseEvent))
{
return mouseEvent.Handled;
}
- if (HandleReleased (mouseEvent))
+ if (WhenGrabbedHandleReleased (mouseEvent))
{
return mouseEvent.Handled;
}
- if (HandleClicked (mouseEvent))
+ if (WhenGrabbedHandleClicked (mouseEvent))
{
return mouseEvent.Handled;
}
@@ -119,7 +288,7 @@ public partial class View // Mouse APIs
|| mouseEvent.Flags.HasFlag (MouseFlags.Button4TripleClicked)
)
{
- // If it's a click, and we didn't handle it, then we'll call OnMouseClick
+ // If it's a click, and we didn't handle it, then we need to generate a click event
// We get here if the view did not handle the mouse event via OnMouseEvent/MouseEvent and
// it did not handle the press/release/clicked events via HandlePress/HandleRelease/HandleClicked
return OnMouseClick (new (mouseEvent));
@@ -135,29 +304,6 @@ public partial class View // Mouse APIs
/// if mouse position reports are wanted; otherwise, .
public virtual bool WantMousePositionReports { get; set; }
- ///
- /// Called by when the mouse enters . The view will
- /// then receive mouse events until is called indicating the mouse has left
- /// the view.
- ///
- ///
- ///
- /// Override this method or subscribe to to change the default enter behavior.
- ///
- ///
- /// The coordinates are relative to .
- ///
- ///
- ///
- /// , if the event was handled, otherwise.
- protected internal virtual bool? OnMouseEnter (MouseEvent mouseEvent)
- {
- var args = new MouseEventEventArgs (mouseEvent);
- MouseEnter?.Invoke (this, args);
-
- return args.Handled;
- }
-
/// Called when a mouse event occurs within the view's .
///
///
@@ -175,61 +321,22 @@ protected internal virtual bool OnMouseEvent (MouseEvent mouseEvent)
return args.Handled;
}
- ///
- /// Called by when a mouse leaves . The view will
- /// no longer receive mouse events.
- ///
+ #endregion Low Level Mouse Events
+
+ #region Mouse Click Events
+
+ /// Event fired when a mouse click occurs.
+ ///
///
///
- /// Override this method or subscribe to to change the default leave behavior.
+ /// Fired when the mouse is either clicked or double-clicked. Check
+ /// to see which button was clicked.
///
///
/// The coordinates are relative to .
///
///
- ///
- /// , if the event was handled, otherwise.
- protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent)
- {
- if (!Enabled)
- {
- return true;
- }
-
- if (!CanBeVisible (this))
- {
- return false;
- }
-
- var args = new MouseEventEventArgs (mouseEvent);
- MouseLeave?.Invoke (this, args);
-
- return args.Handled;
- }
-
- ///
- /// Called when the view is to be highlighted.
- ///
- /// , if the event was handled, otherwise.
- protected virtual bool? OnHighlight (CancelEventArgs args)
- {
- Highlight?.Invoke (this, args);
-
- if (args.Cancel)
- {
- return true;
- }
-
- Margin?.Highlight?.Invoke (this, args);
-
- //args = new (highlight);
- //Border?.Highlight?.Invoke (this, args);
-
- //args = new (highlight);
- //Padding?.Highlight?.Invoke (this, args);
-
- return args.Cancel;
- }
+ public event EventHandler? MouseClick;
/// Invokes the MouseClick event.
///
@@ -241,12 +348,19 @@ protected internal virtual bool OnMouseLeave (MouseEvent mouseEvent)
/// , if the event was handled, otherwise.
protected bool OnMouseClick (MouseEventEventArgs args)
{
+ // BUGBUG: This should be named NewMouseClickEvent. Fix this in https://github.com/gui-cs/Terminal.Gui/issues/3029
+
+ // Pre-conditions
if (!Enabled)
{
// QUESTION: Is this right? Should a disabled view eat mouse clicks?
return args.Handled = false;
}
+ // Cancellable event
+
+ // BUGBUG: There should be a call to a protected virtual OnMouseClick here. Fix this in https://github.com/gui-cs/Terminal.Gui/issues/3029
+
MouseClick?.Invoke (this, args);
if (args.Handled)
@@ -254,17 +368,17 @@ protected bool OnMouseClick (MouseEventEventArgs args)
return true;
}
- if (!HasFocus && CanFocus)
- {
- args.Handled = true;
- SetFocus ();
- }
+ // Post-conditions
+
+ // Always invoke Select command on MouseClick
+ // By default, this will raise Selecting/OnSelecting - Subclasses can override this via AddCommand (Command.Select ...).
+ args.Handled = InvokeCommand (Command.Select, ctx: new (Command.Select, key: null, data: args.MouseEvent)) == true;
return args.Handled;
}
///
- /// For cases where the view is grabbed and the mouse is clicked, this method handles the click event (typically
+ /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the click event (typically
/// when or are set).
///
///
@@ -272,7 +386,7 @@ protected bool OnMouseClick (MouseEventEventArgs args)
///
///
/// , if the event was handled, otherwise.
- internal bool HandleClicked (MouseEvent mouseEvent)
+ internal bool WhenGrabbedHandleClicked (MouseEvent mouseEvent)
{
if (Application.MouseGrabView == this
&& (mouseEvent.Flags.HasFlag (MouseFlags.Button1Clicked)
@@ -283,13 +397,13 @@ internal bool HandleClicked (MouseEvent mouseEvent)
// We're grabbed. Clicked event comes after the last Release. This is our signal to ungrab
Application.UngrabMouse ();
- if (SetHighlight (HighlightStyle.None))
+ if (SetPressedHighlight (HighlightStyle.None))
{
return true;
}
- // If mouse is still in bounds, click
- if (!WantContinuousButtonPressed && Viewport.Contains (mouseEvent.Position))
+ // If mouse is still in bounds, generate a click
+ if (!WantMousePositionReports && Viewport.Contains (mouseEvent.Position))
{
return OnMouseClick (new (mouseEvent));
}
@@ -301,7 +415,7 @@ internal bool HandleClicked (MouseEvent mouseEvent)
}
///
- /// For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
+ /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
/// when or are set).
///
///
@@ -309,7 +423,7 @@ internal bool HandleClicked (MouseEvent mouseEvent)
///
///
/// , if the event was handled, otherwise.
- internal bool HandleReleased (MouseEvent mouseEvent)
+ internal bool WhenGrabbedHandleReleased (MouseEvent mouseEvent)
{
if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Released)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button2Released)
@@ -318,7 +432,7 @@ internal bool HandleReleased (MouseEvent mouseEvent)
{
if (Application.MouseGrabView == this)
{
- SetHighlight (HighlightStyle.None);
+ SetPressedHighlight (HighlightStyle.None);
}
return mouseEvent.Handled = true;
@@ -328,111 +442,134 @@ internal bool HandleReleased (MouseEvent mouseEvent)
}
///
- /// Called by when the mouse enters . The view will
- /// then receive mouse events until is called indicating the mouse has left
- /// the view.
+ /// INTERNAL For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
+ /// when or are set).
///
///
///
- /// A view must be both enabled and visible to receive mouse events.
- ///
- ///
- /// This method calls to fire the event.
- ///
- ///
- /// See for more information.
+ /// Marked internal just to support unit tests
///
///
///
- /// if the event was handled, otherwise.
- internal bool? NewMouseEnterEvent (MouseEvent mouseEvent)
+ /// , if the event was handled, otherwise.
+ private bool WhenGrabbedHandlePressed (MouseEvent mouseEvent)
{
- if (!Enabled)
+ if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
+ || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
+ || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)
+ || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed))
{
- return true;
- }
+ // The first time we get pressed event, grab the mouse and set focus
+ if (Application.MouseGrabView != this)
+ {
+ Application.GrabMouse (this);
- if (!CanBeVisible (this))
- {
- return false;
- }
+ if (!HasFocus && CanFocus)
+ {
+ // Set the focus, but don't invoke Accept
+ SetFocus ();
+ }
- if (OnMouseEnter (mouseEvent) == true)
- {
- return true;
- }
+ mouseEvent.Handled = true;
+ }
-#if HOVER
- if (HighlightStyle.HasFlag(HighlightStyle.Hover))
- {
- if (SetHighlight (HighlightStyle.Hover))
+ if (Viewport.Contains (mouseEvent.Position))
{
- return true;
+ if (this is not Adornment
+ && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None))
+ {
+ return true;
+ }
+ }
+ else
+ {
+ if (this is not Adornment
+ && SetPressedHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None))
+
+ {
+ return true;
+ }
+ }
+
+ if (WantContinuousButtonPressed && Application.MouseGrabView == this)
+ {
+ // If this is not the first pressed event, generate a click
+ return OnMouseClick (new (mouseEvent));
}
+
+ return mouseEvent.Handled = true;
}
-#endif
+
return false;
}
+ #endregion Mouse Click Events
+
+ #region Highlight Handling
+
+ // Used for Pressed highlighting
+ private ColorScheme? _savedHighlightColorScheme;
+
///
- /// Called by when the mouse leaves . The view will
- /// then no longer receive mouse events.
+ /// Gets or sets whether the will be highlighted visually by mouse interaction.
///
- ///
- ///
- /// A view must be both enabled and visible to receive mouse events.
- ///
- ///
- /// This method calls to fire the event.
- ///
- ///
- /// See for more information.
- ///
- ///
- ///
- /// if the event was handled, otherwise.
- internal bool? NewMouseLeaveEvent (MouseEvent mouseEvent)
+ public HighlightStyle HighlightStyle { get; set; }
+
+ ///
+ /// INTERNAL Raises the event. Returns if the event was handled,
+ /// otherwise.
+ ///
+ ///
+ ///
+ private bool RaiseHighlight (CancelEventArgs args)
{
- if (!Enabled)
+ if (OnHighlight (args))
{
return true;
}
- if (!CanBeVisible (this))
- {
- return false;
- }
-
- if (OnMouseLeave (mouseEvent))
- {
- return true;
- }
-#if HOVER
- if (HighlightStyle.HasFlag (HighlightStyle.Hover))
- {
- SetHighlight (HighlightStyle.None);
- }
-#endif
+ Highlight?.Invoke (this, args);
- return false;
+ return args.Cancel;
}
///
- /// Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent.
+ /// Called when the view is to be highlighted. The passed in the event indicates the
+ /// highlight style that will be applied. The view can modify the highlight style by setting the
+ /// property.
+ ///
+ ///
+ /// Set the property to , to cancel, indicating custom
+ /// highlighting.
+ ///
+ /// , to cancel, indicating custom highlighting.
+ protected virtual bool OnHighlight (CancelEventArgs args) { return false; }
+
+ ///
+ /// Raised when the view is to be highlighted. The passed in the event indicates the
+ /// highlight style that will be applied. The view can modify the highlight style by setting the
+ /// property.
+ /// Set to , to cancel, indicating custom highlighting.
+ ///
+ public event EventHandler>? Highlight;
+
+ ///
+ /// INTERNAL Enables the highlight for the view when the mouse is pressed. Called from OnMouseEvent.
///
///
///
- /// Set to have the view highlighted based on the mouse.
+ /// Set to and/or
+ /// to enable.
///
///
- /// Calls which fires the event.
+ /// Calls and raises the event.
///
///
/// Marked internal just to support unit tests
///
///
/// , if the Highlight event was handled, otherwise.
- internal bool SetHighlight (HighlightStyle newHighlightStyle)
+ internal bool SetPressedHighlight (HighlightStyle newHighlightStyle)
{
// TODO: Make the highlight colors configurable
if (!CanFocus)
@@ -440,32 +577,18 @@ internal bool SetHighlight (HighlightStyle newHighlightStyle)
return false;
}
- // Enable override via virtual method and/or event
HighlightStyle copy = HighlightStyle;
- var args = new CancelEventArgs (ref copy, ref newHighlightStyle);
+ CancelEventArgs args = new (ref copy, ref newHighlightStyle);
- if (OnHighlight (args) == true)
+ if (RaiseHighlight (args) || args.Cancel)
{
return true;
}
-#if HOVER
- if (style.HasFlag (HighlightStyle.Hover))
- {
- if (_savedHighlightColorScheme is null && ColorScheme is { })
- {
- _savedHighlightColorScheme ??= ColorScheme;
- var cs = new ColorScheme (ColorScheme)
- {
- Normal = GetFocusColor (),
- HotNormal = ColorScheme.HotFocus
- };
- ColorScheme = cs;
- }
+ // For 3D Pressed Style - Note we don't care about canceling the event here
+ Margin?.RaiseHighlight (args);
+ args.Cancel = false; // Just in case
- return true;
- }
-#endif
if (args.NewValue.HasFlag (HighlightStyle.Pressed) || args.NewValue.HasFlag (HighlightStyle.PressedOutside))
{
if (_savedHighlightColorScheme is null && ColorScheme is { })
@@ -509,65 +632,78 @@ internal bool SetHighlight (HighlightStyle newHighlightStyle)
return false;
}
+ #endregion Highlight Handling
+
///
- /// For cases where the view is grabbed and the mouse is clicked, this method handles the released event (typically
- /// when or are set).
+ /// INTERNAL: Gets the Views that are under the mouse at , including Adornments.
///
- ///
- ///
- /// Marked internal just to support unit tests
- ///
- ///
- ///
- /// , if the event was handled, otherwise.
- private bool HandlePressed (MouseEvent mouseEvent)
+ ///
+ ///
+ internal static List GetViewsUnderMouse (in Point location)
{
- if (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
- || mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
- || mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)
- || mouseEvent.Flags.HasFlag (MouseFlags.Button4Pressed))
+ List viewsUnderMouse = new ();
+
+ View? start = Application.Top;
+
+ Point currentLocation = location;
+
+ while (start is { Visible: true } && start.Contains (currentLocation))
{
- // The first time we get pressed event, grab the mouse and set focus
- if (Application.MouseGrabView != this)
- {
- Application.GrabMouse (this);
+ viewsUnderMouse.Add (start);
- if (!HasFocus && CanFocus)
- {
- // Set the focus, but don't invoke Accept
- SetFocus ();
- }
+ Adornment? found = null;
- mouseEvent.Handled = true;
+ if (start.Margin.Contains (currentLocation))
+ {
+ found = start.Margin;
}
-
- if (Viewport.Contains (mouseEvent.Position))
+ else if (start.Border.Contains (currentLocation))
{
- if (this is not Adornment
- && SetHighlight (HighlightStyle.HasFlag (HighlightStyle.Pressed) ? HighlightStyle.Pressed : HighlightStyle.None))
- {
- return true;
- }
+ found = start.Border;
}
- else
+ else if (start.Padding.Contains (currentLocation))
{
- if (this is not Adornment
- && SetHighlight (HighlightStyle.HasFlag (HighlightStyle.PressedOutside) ? HighlightStyle.PressedOutside : HighlightStyle.None))
+ found = start.Padding;
+ }
+
+ Point viewportOffset = start.GetViewportOffsetFromFrame ();
+
+ if (found is { })
+ {
+ start = found;
+ viewsUnderMouse.Add (start);
+ viewportOffset = found.Parent?.Frame.Location ?? Point.Empty;
+ }
+
+ int startOffsetX = currentLocation.X - (start.Frame.X + viewportOffset.X);
+ int startOffsetY = currentLocation.Y - (start.Frame.Y + viewportOffset.Y);
+
+ View? subview = null;
+ for (int i = start.InternalSubviews.Count - 1; i >= 0; i--)
+ {
+ if (start.InternalSubviews [i].Visible
+ && start.InternalSubviews [i].Contains (new (startOffsetX + start.Viewport.X, startOffsetY + start.Viewport.Y)))
{
- return true;
+ subview = start.InternalSubviews [i];
+ currentLocation.X = startOffsetX + start.Viewport.X;
+ currentLocation.Y = startOffsetY + start.Viewport.Y;
+
+ // start is the deepest subview under the mouse; stop searching the subviews
+ break;
}
}
- if (WantContinuousButtonPressed && Application.MouseGrabView == this)
+ if (subview is null)
{
- // If this is not the first pressed event, click
- return OnMouseClick (new (mouseEvent));
+ // No subview was found that's under the mouse, so we're done
+ return viewsUnderMouse;
}
- return mouseEvent.Handled = true;
+ // We found a subview of start that's under the mouse, continue...
+ start = subview;
}
- return false;
+ return viewsUnderMouse;
}
}
diff --git a/Terminal.Gui/View/View.Navigation.cs b/Terminal.Gui/View/View.Navigation.cs
index 65ae2a7bf2..a8a2022aac 100644
--- a/Terminal.Gui/View/View.Navigation.cs
+++ b/Terminal.Gui/View/View.Navigation.cs
@@ -1,5 +1,6 @@
#nullable enable
using System.Diagnostics;
+using System.Reflection.PortableExecutable;
namespace Terminal.Gui;
@@ -16,6 +17,9 @@ public partial class View // Focus and cross-view navigation management (TabStop
///
/// If there is no next/previous view to advance to, the focus is set to the view itself.
///
+ ///
+ /// See the View Navigation Deep Dive for more information:
+ ///
///
///
///
@@ -39,7 +43,7 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
// AdvanceFocus did not advance - do we wrap, or move up to the superview?
- View [] focusChain = GetSubviewFocusChain (direction, behavior);
+ View [] focusChain = GetFocusChain (direction, behavior);
if (focusChain.Length == 0)
{
@@ -52,11 +56,11 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
if (direction == NavigationDirection.Forward && focused == focusChain [^1] && SuperView is null)
{
// We're at the top of the focus chain. Go back down the focus chain and focus the first TabGroup
- View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
+ View [] views = GetFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
if (views.Length > 0)
{
- View [] subViews = views [0].GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
+ View [] subViews = views [0].GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
if (subViews.Length > 0)
{
@@ -71,11 +75,11 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
if (direction == NavigationDirection.Backward && focused == focusChain [0])
{
// We're at the bottom of the focus chain
- View [] views = GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
+ View [] views = GetFocusChain (NavigationDirection.Forward, TabBehavior.TabGroup);
if (views.Length > 0)
{
- View [] subViews = views [^1].GetSubviewFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
+ View [] subViews = views [^1].GetFocusChain (NavigationDirection.Forward, TabBehavior.TabStop);
if (subViews.Length > 0)
{
@@ -102,7 +106,7 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
if (SuperView is { })
{
// If we are TabStop, and we have at least one other focusable peer, move to the SuperView's chain
- if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetSubviewFocusChain (direction, behavior).Length > 1)
+ if (TabStop == TabBehavior.TabStop && SuperView is { } && SuperView.GetFocusChain (direction, behavior).Length > 1)
{
return false;
}
@@ -110,7 +114,7 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
// TabGroup is special-cased.
if (focused?.TabStop == TabBehavior.TabGroup)
{
- if (SuperView?.GetSubviewFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
+ if (SuperView?.GetFocusChain (direction, TabBehavior.TabGroup)?.Length > 0)
{
// Our superview has a TabGroup subview; signal we couldn't move so we nav out to it
return false;
@@ -136,6 +140,9 @@ public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
/// Gets or sets a value indicating whether this can be focused.
///
///
+ /// See the View Navigation Deep Dive for more information:
+ ///
+ ///
/// must also have set to .
///
///
@@ -176,7 +183,7 @@ public bool CanFocus
HasFocus = false;
}
- if (_canFocus && !HasFocus && Visible && SuperView is { } && SuperView.Focused is null)
+ if (_canFocus && !HasFocus && Visible && SuperView is { Focused: null })
{
// If CanFocus is set to true and this view does not have focus, make it enter focus
SetFocus ();
@@ -211,14 +218,40 @@ public bool FocusDeepest (NavigationDirection direction, TabBehavior? behavior)
return SetFocus ();
}
- /// Gets the currently focused Subview of this view, or if nothing is focused.
+ /// Gets the currently focused Subview or Adornment of this view, or if nothing is focused.
public View? Focused
{
- get { return Subviews.FirstOrDefault (v => v.HasFocus); }
+ get
+ {
+ View? focused = Subviews.FirstOrDefault (v => v.HasFocus);
+
+ if (focused is { })
+ {
+ return focused;
+ }
+
+ // How about in Adornments?
+ if (Margin is { HasFocus: true })
+ {
+ return Margin;
+ }
+
+ if (Border is { HasFocus: true })
+ {
+ return Border;
+ }
+
+ if (Padding is { HasFocus: true })
+ {
+ return Padding;
+ }
+
+ return null;
+ }
}
/// Returns a value indicating if this View is currently on Top (Active)
- public bool IsCurrentTop => Application.Current == this;
+ public bool IsCurrentTop => Application.Top == this;
///
/// Returns the most focused Subview down the subview-hierarchy.
@@ -259,14 +292,11 @@ public View? MostFocused
///
internal bool RestoreFocus ()
{
- if (Focused is null && _subviews?.Count > 0)
- {
- if (_previouslyMostFocused is { })
- {
- return _previouslyMostFocused.SetFocus ();
- }
+ View [] indicies = GetFocusChain (NavigationDirection.Forward, null);
- return false;
+ if (Focused is null && _previouslyFocused is { } && indicies.Contains (_previouslyFocused))
+ {
+ return _previouslyFocused.SetFocus ();
}
return false;
@@ -274,7 +304,7 @@ internal bool RestoreFocus ()
private View? FindDeepestFocusableView (NavigationDirection direction, TabBehavior? behavior)
{
- View [] indicies = GetSubviewFocusChain (direction, behavior);
+ View [] indicies = GetFocusChain (direction, behavior);
foreach (View v in indicies)
{
@@ -294,6 +324,9 @@ internal bool RestoreFocus ()
///
///
///
+ /// See the View Navigation Deep Dive for more information:
+ ///
+ ///
/// Only Views that are visible, enabled, and have set to are
/// focusable. If
/// these conditions are not met when this property is set to will
@@ -341,6 +374,12 @@ public bool HasFocus
else
{
SetHasFocusFalse (null);
+
+ if (_hasFocus)
+ {
+ // force it.
+ _hasFocus = false;
+ }
}
}
get => _hasFocus;
@@ -350,6 +389,12 @@ public bool HasFocus
/// Causes this view to be focused. Calling this method has the same effect as setting to
/// but with the added benefit of returning a value indicating whether the focus was set.
///
+ ///
+ ///
+ /// See the View Navigation Deep Dive for more information:
+ ///
+ ///
+ /// if the focus changed; false otherwise.
public bool SetFocus ()
{
(bool focusSet, bool _) = SetHasFocusTrue (Application.Navigation?.GetFocused ());
@@ -358,9 +403,9 @@ public bool SetFocus ()
}
///
- /// Caches the most focused subview when this view is losing focus. This is used by .
+ /// A cache of the subview that was focused when this view last lost focus. This is used by .
///
- private View? _previouslyMostFocused;
+ private View? _previouslyFocused;
///
/// INTERNAL: Called when focus is going to change to this view. This method is called by and
@@ -376,7 +421,7 @@ public bool SetFocus ()
///
private (bool focusSet, bool cancelled) SetHasFocusTrue (View? previousFocusedView, bool traversingUp = false)
{
- Debug.Assert (ApplicationNavigation.IsInHierarchy (SuperView, this));
+ Debug.Assert (SuperView is null || ApplicationNavigation.IsInHierarchy (SuperView, this));
// Pre-conditions
if (_hasFocus)
@@ -384,7 +429,10 @@ public bool SetFocus ()
return (false, false);
}
- if (CanFocus && SuperView is { CanFocus: false })
+ var thisAsAdornment = this as Adornment;
+ View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
+
+ if (CanFocus && superViewOrParent is { CanFocus: false })
{
Debug.WriteLine ($@"WARNING: Attempt to FocusChanging where SuperView.CanFocus == false. {this}");
@@ -412,7 +460,7 @@ public bool SetFocus ()
// Make sure superviews up the superview hierarchy have focus.
// Any of them may cancel gaining focus. In which case we need to back out.
- if (SuperView is { HasFocus: false } sv)
+ if (superViewOrParent is { HasFocus: false } sv)
{
(bool focusSet, bool svCancelled) = sv.SetHasFocusTrue (previousFocusedView, true);
@@ -431,7 +479,7 @@ public bool SetFocus ()
// By setting _hasFocus to true we definitively change HasFocus for this view.
// Get whatever peer has focus, if any
- View? focusedPeer = SuperView?.Focused;
+ View? focusedPeer = superViewOrParent?.Focused;
_hasFocus = true;
@@ -440,31 +488,34 @@ public bool SetFocus ()
if (!traversingUp)
{
- // Restore focus to the previously most focused subview in the subview-hierarchy
+ // Restore focus to the previously focused subview
if (!RestoreFocus ())
{
// Couldn't restore focus, so use Advance to navigate to the next focusable subview
if (!AdvanceFocus (NavigationDirection.Forward, null))
{
// Couldn't advance, so we're the most focused view in the application
- _previouslyMostFocused = null;
Application.Navigation?.SetFocused (this);
}
}
}
- if (previousFocusedView is { HasFocus: true } && Subviews.Contains (previousFocusedView))
+ if (previousFocusedView is { HasFocus: true } && GetFocusChain (NavigationDirection.Forward, TabStop).Contains (previousFocusedView))
{
previousFocusedView.SetHasFocusFalse (this);
}
+ _previouslyFocused = null;
+
if (Arrangement.HasFlag (ViewArrangement.Overlapped))
{
- SuperView?.MoveSubviewToStart (this);
+ SuperView?.MoveSubviewToEnd (this);
}
NotifyFocusChanged (HasFocus, previousFocusedView, this);
+ SetNeedsDisplay ();
+
// Post-conditions - prove correctness
if (HasFocus == previousValue)
{
@@ -547,29 +598,40 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
throw new InvalidOperationException ("SetHasFocusFalse should not be called if the view does not have focus.");
}
+ var thisAsAdornment = this as Adornment;
+ View? superViewOrParent = thisAsAdornment?.Parent ?? SuperView;
+
// If newFocusedVew is null, we need to find the view that should get focus, and SetFocus on it.
if (!traversingDown && newFocusedView is null)
{
- if (SuperView?._previouslyMostFocused is { } && SuperView?._previouslyMostFocused != this)
+ if (superViewOrParent?._previouslyFocused is { })
{
- SuperView?._previouslyMostFocused?.SetFocus ();
+ if (superViewOrParent._previouslyFocused != this)
+ {
+ superViewOrParent?._previouslyFocused?.SetFocus ();
- // The above will cause SetHasFocusFalse, so we can return
- return;
+ // The above will cause SetHasFocusFalse, so we can return
+ return;
+ }
}
- if (SuperView is { } && SuperView.AdvanceFocus (NavigationDirection.Forward, TabStop))
+ if (superViewOrParent is { })
{
- // The above will cause SetHasFocusFalse, so we can return
- return;
+ if (superViewOrParent.AdvanceFocus (NavigationDirection.Forward, TabStop))
+ {
+ // The above will cause SetHasFocusFalse, so we can return
+ return;
+ }
+
+ newFocusedView = superViewOrParent;
}
- if (Application.Navigation is { } && Application.Current is { })
+ if (Application.Navigation is { } && Application.Top is { })
{
// Temporarily ensure this view can't get focus
bool prevCanFocus = _canFocus;
_canFocus = false;
- bool restoredFocus = Application.Current!.RestoreFocus ();
+ bool restoredFocus = Application.Top!.RestoreFocus ();
_canFocus = prevCanFocus;
if (restoredFocus)
@@ -582,6 +644,7 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
// No other focusable view to be found. Just "leave" us...
}
+
// Before we can leave focus, we need to make sure that all views down the subview-hierarchy have left focus.
View? mostFocused = MostFocused;
@@ -595,12 +658,27 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
if (bottom.HasFocus)
{
bottom.SetHasFocusFalse (newFocusedView, true);
+
+ Debug.Assert (_hasFocus);
}
bottom = bottom.SuperView;
}
- _previouslyMostFocused = mostFocused;
+ if (bottom == this && bottom.SuperView is Adornment a)
+ {
+ //a.SetHasFocusFalse (newFocusedView, true);
+
+ Debug.Assert (_hasFocus);
+ }
+
+ Debug.Assert (_hasFocus);
+
+ }
+
+ if (superViewOrParent is { })
+ {
+ superViewOrParent._previouslyFocused = this;
}
bool previousValue = HasFocus;
@@ -608,8 +686,10 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
// Note, can't be cancelled.
NotifyFocusChanging (HasFocus, !HasFocus, newFocusedView, this);
- // Get whatever peer has focus, if any
- View? focusedPeer = SuperView?.Focused;
+ // Get whatever peer has focus, if any so we can update our superview's _previouslyMostFocused
+ View? focusedPeer = superViewOrParent?.Focused;
+
+ // Set HasFocus false
_hasFocus = false;
if (Application.Navigation is { })
@@ -618,7 +698,7 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
if (appFocused is { } || appFocused == this)
{
- Application.Navigation.SetFocused (newFocusedView ?? SuperView);
+ Application.Navigation.SetFocused (newFocusedView ?? superViewOrParent);
}
}
@@ -630,11 +710,6 @@ private void SetHasFocusFalse (View? newFocusedView, bool traversingDown = false
return;
}
- if (SuperView is { })
- {
- //SuperView._previouslyMostFocused = focusedPeer;
- }
-
// Post-conditions - prove correctness
if (HasFocus == previousValue)
{
@@ -681,32 +756,48 @@ protected virtual void OnHasFocusChanged (bool newHasFocus, View? previousFocuse
#region Tab/Focus Handling
///
- /// Gets TabIndexes that are scoped to the specified behavior and direction. If behavior is null, all TabIndexes are
- /// returned.
+ /// Gets the subviews and Adornments of this view that are scoped to the specified behavior and direction. If behavior is null, all focusable subviews and
+ /// Adornments are returned.
///
///
///
///
- /// GetScopedTabIndexes
- internal View [] GetSubviewFocusChain (NavigationDirection direction, TabBehavior? behavior)
+ internal View [] GetFocusChain (NavigationDirection direction, TabBehavior? behavior)
{
- IEnumerable? fitleredSubviews;
+ IEnumerable? filteredSubviews;
if (behavior.HasValue)
{
- fitleredSubviews = _subviews?.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
+ filteredSubviews = _subviews?.Where (v => v.TabStop == behavior && v is { CanFocus: true, Visible: true, Enabled: true });
}
else
{
- fitleredSubviews = _subviews?.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
+ filteredSubviews = _subviews?.Where (v => v is { CanFocus: true, Visible: true, Enabled: true });
+ }
+
+
+ // How about in Adornments?
+ if (Padding is { CanFocus: true, Visible: true, Enabled: true } && Padding.TabStop == behavior)
+ {
+ filteredSubviews = filteredSubviews?.Append (Padding);
+ }
+
+ if (Border is { CanFocus: true, Visible: true, Enabled: true } && Border.TabStop == behavior)
+ {
+ filteredSubviews = filteredSubviews?.Append (Border);
+ }
+
+ if (Margin is { CanFocus: true, Visible: true, Enabled: true } && Margin.TabStop == behavior)
+ {
+ filteredSubviews = filteredSubviews?.Append (Margin);
}
if (direction == NavigationDirection.Backward)
{
- fitleredSubviews = fitleredSubviews?.Reverse ();
+ filteredSubviews = filteredSubviews?.Reverse ();
}
- return fitleredSubviews?.ToArray () ?? Array.Empty ();
+ return filteredSubviews?.ToArray () ?? Array.Empty ();
}
private TabBehavior? _tabStop;
@@ -715,7 +806,11 @@ internal View [] GetSubviewFocusChain (NavigationDirection direction, TabBehavio
/// Gets or sets the behavior of for keyboard navigation.
///
///
+ ///
///
+ /// See the View Navigation Deep Dive for more information:
+ ///
+ /// ///
/// If the tab stop has not been set and setting to true will set it
/// to
/// .
@@ -723,7 +818,7 @@ internal View [] GetSubviewFocusChain (NavigationDirection direction, TabBehavio
///
/// TabStop is independent of . If is , the
/// view will not gain
- /// focus even if this property is set and vice-versa.
+ /// focus even if this property is set and vice versa.
///
///
/// The default keys are (Key.Tab)
diff --git a/Terminal.Gui/View/View.cs b/Terminal.Gui/View/View.cs
index f01c9c78d0..706409c5b0 100644
--- a/Terminal.Gui/View/View.cs
+++ b/Terminal.Gui/View/View.cs
@@ -7,7 +7,7 @@ namespace Terminal.Gui;
#region API Docs
///
-/// View is the base class for all views on the screen and represents a visible element that can render itself and
+/// View is the base class all visible elements. View can render itself and
/// contains zero or more nested views, called SubViews. View provides basic functionality for layout, positioning, and
/// drawing. In addition, View provides keyboard and mouse event handling.
///
@@ -100,20 +100,14 @@ namespace Terminal.Gui;
/// inheritance hierarchies to override base class layout code optimally by doing so only on
/// first run, instead of on every run.
///
-/// See for an overview of View keyboard handling.
-/// ///
+/// See for an overview of View keyboard handling.
///
#endregion API Docs
public partial class View : Responder, ISupportInitializeNotification
{
- ///
- /// Cancelable event fired when the command is invoked. Set
- ///
- /// to cancel the event.
- ///
- public event EventHandler? Accept;
+ #region Constructors and Initialization
/// Gets or sets arbitrary data for the view.
/// This property is not used internally.
@@ -124,47 +118,6 @@ public partial class View : Responder, ISupportInitializeNotification
/// The id should be unique across all Views that share a SuperView.
public string Id { get; set; } = "";
- /// Pretty prints the View
- ///
- public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
-
- ///
- protected override void Dispose (bool disposing)
- {
- LineCanvas.Dispose ();
-
- DisposeKeyboard ();
- DisposeAdornments ();
-
- for (int i = InternalSubviews.Count - 1; i >= 0; i--)
- {
- View subview = InternalSubviews [i];
- Remove (subview);
- subview.Dispose ();
- }
-
- base.Dispose (disposing);
- Debug.Assert (InternalSubviews.Count == 0);
- }
-
- ///
- /// Called when the command is invoked. Raises
- /// event.
- ///
- ///
- /// If the event was canceled. If the event was raised but not canceled.
- /// If no event was raised.
- ///
- protected bool? OnAccept ()
- {
- var args = new HandledEventArgs ();
- Accept?.Invoke (this, args);
-
- return Accept is null ? null : args.Handled;
- }
-
- #region Constructors and Initialization
-
///
/// Points to the current driver in use by the view, it is a convenience property for simplifying the development
/// of new views.
@@ -181,14 +134,19 @@ protected override void Dispose (bool disposing)
public View ()
{
SetupAdornments ();
+
+ SetupCommands ();
+
SetupKeyboard ();
//SetupMouse ();
+
SetupText ();
+
}
///
- /// Event called only once when the is being initialized for the first time. Allows
+ /// Raised once when the is being initialized for the first time. Allows
/// configurations and assignments to be performed before the being shown.
/// View implements to allow for more sophisticated initialization.
///
@@ -287,7 +245,7 @@ public virtual void EndInit ()
Initialized?.Invoke (this, EventArgs.Empty);
}
-#endregion Constructors and Initialization
+ #endregion Constructors and Initialization
#region Visibility
@@ -314,7 +272,10 @@ public bool Enabled
HasFocus = false;
}
- if (_enabled && CanFocus && Visible && !HasFocus
+ if (_enabled
+ && CanFocus
+ && Visible
+ && !HasFocus
&& SuperView is null or { HasFocus: true, Visible: true, Enabled: true, Focused: null })
{
SetFocus ();
@@ -346,15 +307,17 @@ public bool Enabled
}
}
- /// Event fired when the value is being changed.
+ /// Raised when the value is being changed.
public event EventHandler? EnabledChanged;
- /// Method invoked when the property from a view is changed.
+ // TODO: Change this event to match the standard TG event model.
+ /// Invoked when the property from a view is changed.
public virtual void OnEnabledChanged () { EnabledChanged?.Invoke (this, EventArgs.Empty); }
private bool _visible = true;
- /// Gets or sets a value indicating whether this and all its child controls are displayed.
+ // TODO: Remove virtual once Menu/MenuBar are removed. MenuBar is the only override.
+ /// Gets or sets a value indicating whether this is visible.
public virtual bool Visible
{
get => _visible;
@@ -365,6 +328,19 @@ public virtual bool Visible
return;
}
+ if (OnVisibleChanging ())
+ {
+ return;
+ }
+
+ CancelEventArgs args = new (in _visible, ref value);
+ VisibleChanging?.Invoke (this, args);
+
+ if (args.Cancel)
+ {
+ return;
+ }
+
_visible = value;
if (!_visible)
@@ -375,29 +351,45 @@ public virtual bool Visible
}
}
- if (_visible && CanFocus && Enabled && !HasFocus
+ if (_visible
+ && CanFocus
+ && Enabled
+ && !HasFocus
&& SuperView is null or { HasFocus: true, Visible: true, Enabled: true, Focused: null })
{
SetFocus ();
}
OnVisibleChanged ();
+ VisibleChanged?.Invoke (this, EventArgs.Empty);
+
SetNeedsDisplay ();
}
}
- /// Method invoked when the property from a view is changed.
- public virtual void OnVisibleChanged () { VisibleChanged?.Invoke (this, EventArgs.Empty); }
+ /// Called when is changing. Can be cancelled by returning .
+ protected virtual bool OnVisibleChanging () { return false; }
+
+ ///
+ /// Raised when the value is being changed. Can be cancelled by setting Cancel to
+ /// .
+ ///
+ public event EventHandler>? VisibleChanging;
+
+ /// Called when has changed.
+ protected virtual void OnVisibleChanged () { }
- /// Event fired when the value is being changed.
+ /// Raised when has changed.
public event EventHandler? VisibleChanged;
- // TODO: This API is a hack. We should make Visible propogate automatically, no? See https://github.com/gui-cs/Terminal.Gui/issues/3703
///
/// INTERNAL Indicates whether all views up the Superview hierarchy are visible.
///
/// The view to test.
- /// if `view.Visible` is or any Superview is not visible, otherwise.
+ ///
+ /// if `view.Visible` is or any Superview is not visible,
+ /// otherwise.
+ ///
internal static bool CanBeVisible (View view)
{
if (!view.Visible)
@@ -416,7 +408,7 @@ internal static bool CanBeVisible (View view)
return true;
}
-#endregion Visibility
+ #endregion Visibility
#region Title
@@ -492,18 +484,16 @@ public string Title
private void SetTitleTextFormatterSize ()
{
TitleTextFormatter.ConstrainToSize = new (
- TextFormatter.GetWidestLineLength (TitleTextFormatter.Text)
- - (TitleTextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
- ? Math.Max (HotKeySpecifier.GetColumns (), 0)
- : 0),
- 1);
+ TextFormatter.GetWidestLineLength (TitleTextFormatter.Text)
+ - (TitleTextFormatter.Text?.Contains ((char)HotKeySpecifier.Value) == true
+ ? Math.Max (HotKeySpecifier.GetColumns (), 0)
+ : 0),
+ 1);
}
+ // TODO: Change this event to match the standard TG event model.
/// Called when the has been changed. Invokes the event.
- protected void OnTitleChanged ()
- {
- TitleChanged?.Invoke (this, new (in _title));
- }
+ protected void OnTitleChanged () { TitleChanged?.Invoke (this, new (in _title)); }
///
/// Called before the changes. Invokes the event, which can
@@ -519,14 +509,37 @@ protected bool OnTitleChanging (ref string newTitle)
return args.Cancel;
}
- /// Event fired after the has been changed.
+ /// Raised after the has been changed.
public event EventHandler>? TitleChanged;
///
- /// Event fired when the is changing. Set to `true`
+ /// Raised when the is changing. Set to `true`
/// to cancel the Title change.
///
public event EventHandler>? TitleChanging;
#endregion
+
+ /// Pretty prints the View
+ ///
+ public override string ToString () { return $"{GetType ().Name}({Id}){Frame}"; }
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ LineCanvas.Dispose ();
+
+ DisposeKeyboard ();
+ DisposeAdornments ();
+
+ for (int i = InternalSubviews.Count - 1; i >= 0; i--)
+ {
+ View subview = InternalSubviews [i];
+ Remove (subview);
+ subview.Dispose ();
+ }
+
+ base.Dispose (disposing);
+ Debug.Assert (InternalSubviews.Count == 0);
+ }
}
diff --git a/Terminal.Gui/View/ViewArrangement.cs b/Terminal.Gui/View/ViewArrangement.cs
index 5b38fd6587..b4843f7dd5 100644
--- a/Terminal.Gui/View/ViewArrangement.cs
+++ b/Terminal.Gui/View/ViewArrangement.cs
@@ -7,6 +7,10 @@
///
///
///
+/// See the View Arrangement Deep Dive for more information:
+///
+///
+///
/// Sizing or moving a view is only possible if the is part of a
/// and
/// the relevant position and dimensions of the are independent of other SubViews
@@ -37,11 +41,11 @@ public enum ViewArrangement
///
/// The top edge of the view can be resized.
+ ///
+ /// This flag is mutually exclusive with . If both are set, takes
+ /// precedence.
+ ///
///
- ///
- /// This flag is mutually exclusive with . If both are set, takes
- /// precedence.
- ///
TopResizable = 8,
///
@@ -51,21 +55,20 @@ public enum ViewArrangement
///
/// The view can be resized in any direction.
+ ///
+ /// If is also set, the top will not be resizable.
+ ///
///
- ///
- /// If is also set, the top will not be resizable.
- ///
Resizable = LeftResizable | RightResizable | TopResizable | BottomResizable,
///
- /// The view overlap other views.
- ///
- ///
+ /// The view overlaps other views (the order of dicates the Z-order). If this flag is not
+ /// set the view will operate in tiled mode.
///
/// When set, Tab and Shift-Tab will be constrained to the subviews of the view (normally, they will navigate to
/// the next/prev view in the next/prev Tabindex).
/// Use Ctrl-Tab (Ctrl-PageDown) / Ctrl-Shift-Tab (Ctrl-PageUp) to move between overlapped views.
///
- ///
- Overlapped = 32,
+ ///
+ Overlapped = 32
}
diff --git a/Terminal.Gui/View/ViewportSettings.cs b/Terminal.Gui/View/ViewportSettings.cs
index f0e2af75eb..f023d3f756 100644
--- a/Terminal.Gui/View/ViewportSettings.cs
+++ b/Terminal.Gui/View/ViewportSettings.cs
@@ -3,6 +3,9 @@
///
/// Settings for how the behaves relative to the View's Content area.
///
+///
+/// See the Layout Deep Dive for more information:
+///
[Flags]
public enum ViewportSettings
{
@@ -15,43 +18,35 @@ public enum ViewportSettings
/// If set, .X can be set to negative values enabling scrolling beyond the left of
/// the
/// content area.
- ///
- ///
///
/// When not set, .X is constrained to positive values.
///
- ///
+ ///
AllowNegativeX = 1,
///
/// If set, .Y can be set to negative values enabling scrolling beyond the top of the
/// content area.
- ///
- ///
///
/// When not set, .Y is constrained to positive values.
///
- ///
+ ///
AllowNegativeY = 2,
///
/// If set, .Size can be set to negative coordinates enabling scrolling beyond the
/// top-left of the
/// content area.
- ///
- ///
///
/// When not set, .Size is constrained to positive coordinates.
///
- ///
+ ///
AllowNegativeLocation = AllowNegativeX | AllowNegativeY,
///
/// If set, .X can be set values greater than
/// .Width enabling scrolling beyond the right
/// of the content area.
- ///
- ///
///
/// When not set, .X is constrained to
/// .Width - 1.
@@ -61,15 +56,13 @@ public enum ViewportSettings
///
/// The practical effect of this is that the last column of the content will always be visible.
///
- ///
+ ///
AllowXGreaterThanContentWidth = 4,
///
/// If set, .Y can be set values greater than
/// .Height enabling scrolling beyond the right
/// of the content area.
- ///
- ///
///
/// When not set, .Y is constrained to
/// .Height - 1.
@@ -79,21 +72,19 @@ public enum ViewportSettings
///
/// The practical effect of this is that the last row of the content will always be visible.
///
- ///
+ ///
AllowYGreaterThanContentHeight = 8,
///
/// If set, .Size can be set values greater than
/// enabling scrolling beyond the bottom-right
/// of the content area.
- ///
- ///
///
/// When not set, is constrained to -1.
/// This means the last column and row of the content will remain visible even if there is an attempt to
/// scroll the Viewport past the last column or row.
///
- ///
+ ///
AllowLocationGreaterThanContentSize = AllowXGreaterThanContentWidth | AllowYGreaterThanContentHeight,
///
@@ -106,10 +97,10 @@ public enum ViewportSettings
/// If set will clear only the portion of the content
/// area that is visible within the . This is useful for views that have a
/// content area larger than the Viewport and want the area outside the content to be visually distinct.
+ ///
+ /// must be set for this setting to work (clipping beyond the visible area must be
+ /// disabled).
+ ///
///
- ///
- /// must be set for this setting to work (clipping beyond the visible area must be
- /// disabled).
- ///
ClearContentOnly = 32
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/Views/AutocompleteFilepathContext.cs b/Terminal.Gui/Views/AutocompleteFilepathContext.cs
index f577e554fe..b21724816e 100644
--- a/Terminal.Gui/Views/AutocompleteFilepathContext.cs
+++ b/Terminal.Gui/Views/AutocompleteFilepathContext.cs
@@ -6,7 +6,7 @@ namespace Terminal.Gui;
internal class AutocompleteFilepathContext : AutocompleteContext
{
public AutocompleteFilepathContext (string currentLine, int cursorPosition, FileDialogState state)
- : base (TextModel.ToRuneCellList (currentLine), cursorPosition)
+ : base (Cell.ToCellList (currentLine), cursorPosition)
{
State = state;
}
@@ -30,7 +30,7 @@ public IEnumerable GenerateSuggestions (AutocompleteContext context)
return Enumerable.Empty ();
}
- var path = TextModel.ToString (context.CurrentLine);
+ var path = Cell.ToString (context.CurrentLine);
int last = path.LastIndexOfAny (FileDialog.Separators);
if (string.IsNullOrWhiteSpace (path) || !Path.IsPathRooted (path))
diff --git a/Terminal.Gui/Views/Bar.cs b/Terminal.Gui/Views/Bar.cs
index d30ced79e8..ddaa444365 100644
--- a/Terminal.Gui/Views/Bar.cs
+++ b/Terminal.Gui/Views/Bar.cs
@@ -32,6 +32,7 @@ public Bar (IEnumerable shortcuts)
_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
Initialized += Bar_Initialized;
+ MouseEvent += OnMouseEvent;
if (shortcuts is null)
{
@@ -44,7 +45,43 @@ public Bar (IEnumerable shortcuts)
}
}
- private void Bar_Initialized (object? sender, EventArgs e) { ColorScheme = Colors.ColorSchemes ["Menu"]; }
+ private void OnMouseEvent (object? sender, MouseEventEventArgs e)
+ {
+ NavigationDirection direction = NavigationDirection.Backward;
+
+ if (e.MouseEvent.Flags == MouseFlags.WheeledDown)
+ {
+ e.Handled = true;
+ }
+
+ if (e.MouseEvent.Flags == MouseFlags.WheeledUp)
+ {
+ direction = NavigationDirection.Forward;
+ e.Handled = true;
+ }
+
+ if (e.MouseEvent.Flags == MouseFlags.WheeledRight)
+ {
+ e.Handled = true;
+ }
+
+ if (e.MouseEvent.Flags == MouseFlags.WheeledLeft)
+ {
+ direction = NavigationDirection.Forward;
+ e.Handled = true;
+ }
+
+ if (e.Handled)
+ {
+ e.Handled = AdvanceFocus (direction, TabBehavior.TabStop);
+ }
+ }
+
+ private void Bar_Initialized (object? sender, EventArgs e)
+ {
+ ColorScheme = Colors.ColorSchemes ["Menu"];
+ LayoutBarItems (GetContentSize ());
+ }
///
public override void SetBorderStyle (LineStyle value)
@@ -159,6 +196,11 @@ internal override void OnLayoutStarted (LayoutEventArgs args)
{
base.OnLayoutStarted (args);
+ LayoutBarItems (args.OldContentSize);
+ }
+
+ private void LayoutBarItems (Size contentSize)
+ {
View? prevBarItem = null;
switch (Orientation)
@@ -171,8 +213,6 @@ internal override void OnLayoutStarted (LayoutEventArgs args)
barItem.ColorScheme = ColorScheme;
barItem.X = Pos.Align (Alignment.Start, AlignmentModes);
barItem.Y = 0; //Pos.Center ();
- // HACK: This should not be needed
- barItem.SetRelativeLayout (GetContentSize ());
}
break;
@@ -206,8 +246,6 @@ internal override void OnLayoutStarted (LayoutEventArgs args)
if (barItem is Shortcut scBarItem)
{
scBarItem.MinimumKeyTextSize = minKeyWidth;
- // HACK: This should not be needed
- scBarItem.SetRelativeLayout (GetContentSize ());
maxBarItemWidth = Math.Max (maxBarItemWidth, scBarItem.Frame.Width);
}
@@ -231,10 +269,6 @@ internal override void OnLayoutStarted (LayoutEventArgs args)
foreach (View barItem in Subviews)
{
barItem.Width = maxBarItemWidth;
-
- if (barItem is Line line)
- {
- }
}
Height = Dim.Auto (DimAutoStyle.Content, totalHeight);
@@ -264,6 +298,19 @@ public bool EnableForDesign ()
Add (shortcut);
+ shortcut = new Shortcut
+ {
+ Text = "Czech",
+ CommandView = new CheckBox ()
+ {
+ Title = "_Check"
+ },
+ Key = Key.F9,
+ CanFocus = false
+ };
+
+ Add (shortcut);
+
return true;
}
}
diff --git a/Terminal.Gui/Views/Button.cs b/Terminal.Gui/Views/Button.cs
index e5845fbb59..e29043ed9b 100644
--- a/Terminal.Gui/Views/Button.cs
+++ b/Terminal.Gui/Views/Button.cs
@@ -1,27 +1,22 @@
-//
-// Button.cs: Button control
-//
-// Authors:
-// Miguel de Icaza (miguel@gnome.org)
-//
-
namespace Terminal.Gui;
-/// Button is a that provides an item that invokes raises the event.
+///
+/// A button View that can be pressed with the mouse or keybaord.
+///
///
///
-/// Provides a button showing text that raises the event when clicked on with a mouse or
-/// when the user presses SPACE, ENTER, or the . The hot key is the first letter or digit
-/// following the first underscore ('_') in the button text.
+/// The Button will raise the event when the user presses ,
+/// Enter, or Space
+/// or clicks on the button with the mouse.
///
/// Use to change the hot key specifier from the default of ('_').
///
-/// When the button is configured as the default () and the user presses the ENTER key, if
-/// no other processes the key, the 's event will
-/// be fired.
+/// Button can act as the default handler for all peer-Views. See
+/// .
///
///
-/// Set to to have the event
+/// Set to to have the
+/// event
/// invoked repeatedly while the button is pressed.
///
///
@@ -34,11 +29,17 @@ public class Button : View, IDesignable
private bool _isDefault;
///
- /// Gets or sets whether s are shown with a shadow effect by default.
+ /// Gets or sets whether s are shown with a shadow effect by default.
///
[SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
public static ShadowStyle DefaultShadow { get; set; } = ShadowStyle.None;
+ ///
+ /// Gets or sets the default Highlight Style.
+ ///
+ [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+ public static HighlightStyle DefaultHighlightStyle { get; set; } = HighlightStyle.Pressed | HighlightStyle.Hover;
+
/// Initializes a new instance of .
public Button ()
{
@@ -54,30 +55,53 @@ public Button ()
Width = Dim.Auto (DimAutoStyle.Text);
CanFocus = true;
- HighlightStyle |= HighlightStyle.Pressed;
-#if HOVER
- HighlightStyle |= HighlightStyle.Hover;
-#endif
- // Override default behavior of View
- AddCommand (Command.HotKey, () =>
- {
- SetFocus ();
- return !OnAccept ();
- });
+ AddCommand (Command.HotKey, HandleHotKeyCommand);
+ KeyBindings.Remove (Key.Space);
KeyBindings.Add (Key.Space, Command.HotKey);
+ KeyBindings.Remove (Key.Enter);
KeyBindings.Add (Key.Enter, Command.HotKey);
TitleChanged += Button_TitleChanged;
MouseClick += Button_MouseClick;
ShadowStyle = DefaultShadow;
+ HighlightStyle = DefaultHighlightStyle;
+ }
+
+ private bool? HandleHotKeyCommand (CommandContext ctx)
+ {
+ bool cachedIsDefault = IsDefault; // Supports "Swap Default" in Buttons scenario where IsDefault changes
+
+ if (RaiseSelecting (ctx) is true)
+ {
+ return true;
+ }
+
+ bool? handled = RaiseAccepting (ctx);
+
+ if (handled == true)
+ {
+ return true;
+ }
+
+ SetFocus ();
+
+ // TODO: If `IsDefault` were a property on `View` *any* View could work this way. That's theoretical as
+ // TODO: no use-case has been identified for any View other than Button to act like this.
+ // If Accept was not handled...
+ if (cachedIsDefault && SuperView is { })
+ {
+ return SuperView.InvokeCommand (Command.Accept);
+ }
+
+ return false;
}
private bool _wantContinuousButtonPressed;
- ///
+ ///
public override bool WantContinuousButtonPressed
{
get => _wantContinuousButtonPressed;
@@ -103,7 +127,13 @@ public override bool WantContinuousButtonPressed
private void Button_MouseClick (object sender, MouseEventEventArgs e)
{
- e.Handled = InvokeCommand (Command.HotKey) == true;
+ if (e.Handled)
+ {
+ return;
+ }
+
+ // TODO: With https://github.com/gui-cs/Terminal.Gui/issues/3778 we won't have to pass data:
+ e.Handled = InvokeCommand (Command.HotKey, new (Command.HotKey, null, data: this)) == true;
}
private void Button_TitleChanged (object sender, EventArgs e)
@@ -112,37 +142,68 @@ private void Button_TitleChanged (object sender, EventArgs e)
TextFormatter.HotKeySpecifier = HotKeySpecifier;
}
- ///
+ ///
public override string Text
{
- get => base.Title;
- set => base.Text = base.Title = value;
+ get => Title;
+ set => base.Text = Title = value;
}
- ///
+ ///
public override Rune HotKeySpecifier
{
get => base.HotKeySpecifier;
set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
}
- /// Gets or sets whether the is the default action to activate in a dialog.
- /// true if is default; otherwise, false.
+ ///
+ /// Gets or sets whether the will act as the default handler for
+ /// commands on the .
+ ///
+ ///
+ ///
+ /// If :
+ ///
+ ///
+ /// - the Button will display an indicator that it is the default Button.
+ ///
+ ///
+ /// - when clicked, if the Accepting event is not handled, will be
+ /// invoked on the SuperView.
+ ///
+ ///
+ /// - If a peer-View receives and does not handle it, the command will be passed to
+ /// the
+ /// first Button in the SuperView that has set to . See
+ /// for more information.
+ ///
+ ///
public bool IsDefault
{
get => _isDefault;
set
{
+ if (_isDefault == value)
+ {
+ return;
+ }
+
_isDefault = value;
+
UpdateTextFormatterText ();
OnResizeNeeded ();
}
}
- ///
+ ///
+ /// Gets or sets whether the Button will show decorations or not. If the glyphs that normally
+ /// brakcet the Button Title and the indicator will not be shown.
+ ///
public bool NoDecorations { get; set; }
- ///
+ ///
+ /// Gets or sets whether the Button will include padding on each side of the Title.
+ ///
public bool NoPadding { get; set; }
///
@@ -155,6 +216,7 @@ public bool IsDefault
if (TextFormatter.Text [i] == Text [0])
{
Move (i, 0);
+
return null; // Don't show the cursor
}
}
@@ -166,7 +228,8 @@ public bool IsDefault
///
protected override void UpdateTextFormatterText ()
{
- base.UpdateTextFormatterText();
+ base.UpdateTextFormatterText ();
+
if (NoDecorations)
{
TextFormatter.Text = Text;
@@ -188,11 +251,11 @@ protected override void UpdateTextFormatterText ()
}
}
- ///
+ ///
public bool EnableForDesign ()
{
Title = "_Button";
return true;
}
-}
\ No newline at end of file
+}
diff --git a/Terminal.Gui/Views/CheckBox.cs b/Terminal.Gui/Views/CheckBox.cs
index d806e7abb9..af58ecedd9 100644
--- a/Terminal.Gui/Views/CheckBox.cs
+++ b/Terminal.Gui/Views/CheckBox.cs
@@ -1,35 +1,54 @@
#nullable enable
namespace Terminal.Gui;
-/// Shows a check box that can be cycled between three states.
+/// Shows a check box that can be cycled between two or three states.
public class CheckBox : View
{
+ ///
+ /// Gets or sets the default Highlight Style.
+ ///
+ [SerializableConfigurationProperty (Scope = typeof (ThemeScope))]
+ public static HighlightStyle DefaultHighlightStyle { get; set; } = HighlightStyle.PressedOutside | HighlightStyle.Pressed | HighlightStyle.Hover;
+
///
/// Initializes a new instance of .
///
public CheckBox ()
{
Width = Dim.Auto (DimAutoStyle.Text);
- Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
+ Height = Dim.Auto (DimAutoStyle.Text, 1);
CanFocus = true;
- // Things this view knows how to do
- AddCommand (Command.Accept, AdvanceCheckState);
- AddCommand (Command.HotKey, AdvanceCheckState);
+ // Select (Space key and single-click) - Advance state and raise Select event - DO NOT raise Accept
+ AddCommand (Command.Select, AdvanceAndSelect);
- // Default keybindings for this view
- KeyBindings.Add (Key.Space, Command.Accept);
+ // Hotkey - Advance state and raise Select event - DO NOT raise Accept
+ AddCommand (Command.HotKey, AdvanceAndSelect);
+
+ // Accept (Enter key) - Raise Accept event - DO NOT advance state
+ AddCommand (Command.Accept, RaiseAccepting);
TitleChanged += Checkbox_TitleChanged;
- HighlightStyle = Gui.HighlightStyle.PressedOutside | Gui.HighlightStyle.Pressed;
- MouseClick += CheckBox_MouseClick;
+ HighlightStyle = DefaultHighlightStyle;
}
- private void CheckBox_MouseClick (object? sender, MouseEventEventArgs e)
+ private bool? AdvanceAndSelect (CommandContext ctx)
{
- e.Handled = AdvanceCheckState () == true;
+ bool? cancelled = AdvanceCheckState ();
+
+ if (cancelled is true)
+ {
+ return true;
+ }
+
+ if (RaiseSelecting (ctx) is true)
+ {
+ return true;
+ }
+
+ return ctx.Command == Command.HotKey ? cancelled : cancelled is false;
}
private void Checkbox_TitleChanged (object? sender, EventArgs e)
@@ -38,24 +57,25 @@ private void Checkbox_TitleChanged (object? sender, EventArgs e)
TextFormatter.HotKeySpecifier = HotKeySpecifier;
}
- ///
+ ///
public override string Text
{
- get => base.Title;
- set => base.Text = base.Title = value;
+ get => Title;
+ set => base.Text = Title = value;
}
- ///
+ ///
public override Rune HotKeySpecifier
{
get => base.HotKeySpecifier;
set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
}
- private bool _allowNone = false;
+ private bool _allowNone;
///
- /// If allows to be . The default is .
+ /// If allows to be . The default is
+ /// .
///
public bool AllowCheckStateNone
{
@@ -66,6 +86,7 @@ public bool AllowCheckStateNone
{
return;
}
+
_allowNone = value;
if (CheckedState == CheckState.None)
@@ -82,48 +103,106 @@ public bool AllowCheckStateNone
///
///
///
- /// If is and , the
- /// will display the ConfigurationManager.Glyphs.CheckStateNone character (☒).
+ /// If is and , the
+ ///
+ /// will display the ConfigurationManager.Glyphs.CheckStateNone character (☒).
///
///
- /// If , the
- /// will display the ConfigurationManager.Glyphs.CheckStateUnChecked character (☐).
+ /// If , the
+ /// will display the ConfigurationManager.Glyphs.CheckStateUnChecked character (☐).
///
///
- /// If , the
- /// will display the ConfigurationManager.Glyphs.CheckStateChecked character (☑).
+ /// If , the
+ /// will display the ConfigurationManager.Glyphs.CheckStateChecked character (☑).
///
///
public CheckState CheckedState
{
get => _checkedState;
- set
+ set => ChangeCheckedState (value);
+ }
+
+ ///
+ /// INTERNAL Sets CheckedState.
+ ///
+ ///
+ ///
+ /// if state change was canceled, if the state changed, and
+ /// if the state was not changed for some other reason.
+ ///
+ private bool? ChangeCheckedState (CheckState value)
+ {
+ if (_checkedState == value || (value is CheckState.None && !AllowCheckStateNone))
{
- if (_checkedState == value || (value is CheckState.None && !AllowCheckStateNone))
- {
- return;
- }
+ return null;
+ }
+
+ CancelEventArgs e = new (in _checkedState, ref value);
- _checkedState = value;
- UpdateTextFormatterText ();
- OnResizeNeeded ();
+ if (OnCheckedStateChanging (e))
+ {
+ return true;
}
+
+ CheckedStateChanging?.Invoke (this, e);
+
+ if (e.Cancel)
+ {
+ return e.Cancel;
+ }
+
+ _checkedState = value;
+ UpdateTextFormatterText ();
+ OnResizeNeeded ();
+
+ EventArgs args = new (in _checkedState);
+ OnCheckedStateChanged (args);
+
+ CheckedStateChanged?.Invoke (this, args);
+
+ return false;
}
- ///
- /// Advances to the next value. Invokes the cancelable event.
- ///
+ /// Called when the state is changing.
///
+ ///
+ /// The state cahnge can be cancelled by setting the args.Cancel to .
+ ///
///
- /// If the event was canceled.
+ protected virtual bool OnCheckedStateChanging (CancelEventArgs args) { return false; }
+
+ /// Raised when the state is changing.
///
- ///
- /// Cycles through the states , , and .
- ///
- ///
- /// If the event is not canceled, the will be updated and the event will be raised.
- ///
+ ///
+ /// This event can be cancelled. If cancelled, the will not change its state.
+ ///
+ ///
+ public event EventHandler>? CheckedStateChanging;
+
+ /// Called when the state has changed.
+ protected virtual void OnCheckedStateChanged (EventArgs args) { }
+
+ /// Raised when the state has changed.
+ public event EventHandler>? CheckedStateChanged;
+
+ ///
+ /// Advances to the next value. Invokes the cancelable
+ /// event.
+ ///
+ ///
+ ///
+ /// Cycles through the states , , and
+ /// .
+ ///
+ ///
+ /// If the event is not canceled, the will be updated
+ /// and the event will be raised.
+ ///
///
+ ///
+ /// if state change was canceled, if the state changed, and
+ /// if the state was not changed for some other reason.
+ ///
public bool? AdvanceCheckState ()
{
CheckState oldValue = CheckedState;
@@ -152,35 +231,16 @@ public CheckState CheckedState
break;
}
- CheckedStateChanging?.Invoke (this, e);
- if (e.Cancel)
- {
- return e.Cancel;
- }
+ bool? cancelled = ChangeCheckedState (e.NewValue);
- // By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the Accept event is fired.
- if (OnAccept () == true)
- {
- return true;
- }
-
- CheckedState = e.NewValue;
-
- return true;
+ return cancelled;
}
- /// Raised when the state is changing.
- ///
- ///
- /// This event can be cancelled. If cancelled, the will not change its state.
- ///
- ///
- public event EventHandler>? CheckedStateChanging;
-
///
protected override void UpdateTextFormatterText ()
{
base.UpdateTextFormatterText ();
+
switch (TextAlignment)
{
case Alignment.Start:
diff --git a/Terminal.Gui/Views/ColorBar.cs b/Terminal.Gui/Views/ColorBar.cs
index 54dc54cb69..066ad72b2f 100644
--- a/Terminal.Gui/Views/ColorBar.cs
+++ b/Terminal.Gui/Views/ColorBar.cs
@@ -23,14 +23,14 @@ protected ColorBar ()
AddCommand (Command.LeftExtend, _ => Adjust (-MaxValue / 20));
AddCommand (Command.RightExtend, _ => Adjust (MaxValue / 20));
- AddCommand (Command.LeftHome, _ => SetZero ());
+ AddCommand (Command.LeftStart, _ => SetZero ());
AddCommand (Command.RightEnd, _ => SetMax ());
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.CursorRight, Command.Right);
KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
- KeyBindings.Add (Key.Home, Command.LeftHome);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
KeyBindings.Add (Key.End, Command.RightEnd);
}
diff --git a/Terminal.Gui/Views/ColorPicker16.cs b/Terminal.Gui/Views/ColorPicker.16.cs
similarity index 93%
rename from Terminal.Gui/Views/ColorPicker16.cs
rename to Terminal.Gui/Views/ColorPicker.16.cs
index 7312f113be..c0ae787177 100644
--- a/Terminal.Gui/Views/ColorPicker16.cs
+++ b/Terminal.Gui/Views/ColorPicker.16.cs
@@ -61,7 +61,7 @@ public Point Cursor
set
{
int colorIndex = value.Y * _cols + value.X;
- SelectedColor = (ColorName)colorIndex;
+ SelectedColor = (ColorName16)colorIndex;
}
}
@@ -132,7 +132,7 @@ public override void OnDrawContent (Rectangle viewport)
continue;
}
- Driver.SetAttribute (new ((ColorName)foregroundColorIndex, (ColorName)colorIndex));
+ Driver.SetAttribute (new ((ColorName16)foregroundColorIndex, (ColorName16)colorIndex));
bool selected = x == Cursor.X && y == Cursor.Y;
DrawColorBox (x, y, selected);
colorIndex++;
@@ -141,12 +141,12 @@ public override void OnDrawContent (Rectangle viewport)
}
/// Selected color.
- public ColorName SelectedColor
+ public ColorName16 SelectedColor
{
- get => (ColorName)_selectColorIndex;
+ get => (ColorName16)_selectColorIndex;
set
{
- if (value == (ColorName)_selectColorIndex)
+ if (value == (ColorName16)_selectColorIndex)
{
return;
}
@@ -166,8 +166,8 @@ private void AddCommands ()
{
AddCommand (Command.Left, () => MoveLeft ());
AddCommand (Command.Right, () => MoveRight ());
- AddCommand (Command.LineUp, () => MoveUp ());
- AddCommand (Command.LineDown, () => MoveDown ());
+ AddCommand (Command.Up, () => MoveUp ());
+ AddCommand (Command.Down, () => MoveDown ());
}
/// Add the KeyBindinds.
@@ -175,8 +175,8 @@ private void AddKeyBindings ()
{
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.CursorRight, Command.Right);
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
}
// TODO: Decouple Cursor from SelectedColor so that mouse press-and-hold can show the color under the cursor.
diff --git a/Terminal.Gui/Views/ColorPicker.Prompt.cs b/Terminal.Gui/Views/ColorPicker.Prompt.cs
new file mode 100644
index 0000000000..3f2372db9a
--- /dev/null
+++ b/Terminal.Gui/Views/ColorPicker.Prompt.cs
@@ -0,0 +1,123 @@
+namespace Terminal.Gui;
+
+public partial class ColorPicker
+{
+ ///
+ /// Open a with two or , based on the
+ /// is false or true, respectively, for
+ /// and colors.
+ ///
+ /// The title to show in the dialog.
+ /// The current attribute used.
+ /// The new attribute.
+ /// if a new color was accepted, otherwise .
+ public static bool Prompt (string title, Attribute? currentAttribute, out Attribute newAttribute)
+ {
+ var accept = false;
+
+ var d = new Dialog
+ {
+ Title = title,
+ Width = Application.Force16Colors ? 37 : Dim.Auto (DimAutoStyle.Auto, Dim.Percent (80), Dim.Percent (90)),
+ Height = 20
+ };
+
+ var btnOk = new Button
+ {
+ X = Pos.Center () - 5,
+ Y = Application.Force16Colors ? 6 : 4,
+ Text = "Ok",
+ Width = Dim.Auto (),
+ IsDefault = true
+ };
+
+ btnOk.Accepting += (s, e) =>
+ {
+ accept = true;
+ e.Cancel = true;
+ Application.RequestStop ();
+ };
+
+ var btnCancel = new Button
+ {
+ X = Pos.Center () + 5,
+ Y = 4,
+ Text = "Cancel",
+ Width = Dim.Auto ()
+ };
+
+ btnCancel.Accepting += (s, e) =>
+ {
+ e.Cancel = true;
+ Application.RequestStop ();
+ };
+
+ d.Add (btnOk);
+ d.Add (btnCancel);
+
+ d.AddButton (btnOk);
+ d.AddButton (btnCancel);
+
+ View cpForeground;
+
+ if (Application.Force16Colors)
+ {
+ cpForeground = new ColorPicker16
+ {
+ SelectedColor = currentAttribute!.Value.Foreground.GetClosestNamedColor16 (),
+ Width = Dim.Fill (),
+ BorderStyle = LineStyle.Single,
+ Title = "Foreground"
+ };
+ }
+ else
+ {
+ cpForeground = new ColorPicker
+ {
+ SelectedColor = currentAttribute!.Value.Foreground,
+ Width = Dim.Fill (),
+ Style = new () { ShowColorName = true, ShowTextFields = true },
+ BorderStyle = LineStyle.Single,
+ Title = "Foreground"
+ };
+ ((ColorPicker)cpForeground).ApplyStyleChanges ();
+ }
+
+ View cpBackground;
+
+ if (Application.Force16Colors)
+ {
+ cpBackground = new ColorPicker16
+ {
+ SelectedColor = currentAttribute!.Value.Background.GetClosestNamedColor16 (),
+ Y = Pos.Bottom (cpForeground) + 1,
+ Width = Dim.Fill (),
+ BorderStyle = LineStyle.Single,
+ Title = "Background"
+ };
+ }
+ else
+ {
+ cpBackground = new ColorPicker
+ {
+ SelectedColor = currentAttribute!.Value.Background,
+ Width = Dim.Fill (),
+ Y = Pos.Bottom (cpForeground) + 1,
+ Style = new () { ShowColorName = true, ShowTextFields = true },
+ BorderStyle = LineStyle.Single,
+ Title = "Background"
+ };
+ ((ColorPicker)cpBackground).ApplyStyleChanges ();
+ }
+
+ d.Add (cpForeground, cpBackground);
+
+ Application.Run (d);
+ d.Dispose ();
+ Color newForeColor = Application.Force16Colors ? ((ColorPicker16)cpForeground).SelectedColor : ((ColorPicker)cpForeground).SelectedColor;
+ Color newBackColor = Application.Force16Colors ? ((ColorPicker16)cpBackground).SelectedColor : ((ColorPicker)cpBackground).SelectedColor;
+ newAttribute = new (newForeColor, newBackColor);
+
+ return accept;
+ }
+}
diff --git a/Terminal.Gui/Views/ColorPickerStyle.cs b/Terminal.Gui/Views/ColorPicker.Style.cs
similarity index 100%
rename from Terminal.Gui/Views/ColorPickerStyle.cs
rename to Terminal.Gui/Views/ColorPicker.Style.cs
diff --git a/Terminal.Gui/Views/ColorPicker.cs b/Terminal.Gui/Views/ColorPicker.cs
index 9c5cec83be..a4209ffe42 100644
--- a/Terminal.Gui/Views/ColorPicker.cs
+++ b/Terminal.Gui/Views/ColorPicker.cs
@@ -7,7 +7,7 @@ namespace Terminal.Gui;
///
/// True color picker using HSL
///
-public class ColorPicker : View
+public partial class ColorPicker : View
{
///
/// Creates a new instance of . Use
@@ -64,7 +64,7 @@ public void ApplyStyleChanges ()
Width = textFieldWidth
};
tfValue.HasFocusChanged += UpdateSingleBarValueFromTextField;
- tfValue.Accept += (s, _)=>UpdateSingleBarValueFromTextField(s);
+ tfValue.Accepting += (s, _)=>UpdateSingleBarValueFromTextField(s);
_textFields.Add (bar, tfValue);
}
@@ -154,7 +154,7 @@ private void CreateNameField ()
_tfName.Autocomplete = auto;
_tfName.HasFocusChanged += UpdateValueFromName;
- _tfName.Accept += (s, _) => UpdateValueFromName ();
+ _tfName.Accepting += (s, _) => UpdateValueFromName ();
}
private void CreateTextField ()
@@ -184,7 +184,7 @@ private void CreateTextField ()
Add (_tfHex);
_tfHex.HasFocusChanged += UpdateValueFromTextField;
- _tfHex.Accept += (_,_)=> UpdateValueFromTextField();
+ _tfHex.Accepting += (_,_)=> UpdateValueFromTextField();
}
private void DisposeOldViews ()
diff --git a/Terminal.Gui/Views/ComboBox.cs b/Terminal.Gui/Views/ComboBox.cs
index bfd207aa75..3f92174c82 100644
--- a/Terminal.Gui/Views/ComboBox.cs
+++ b/Terminal.Gui/Views/ComboBox.cs
@@ -7,8 +7,6 @@
using System.Collections.ObjectModel;
using System.ComponentModel;
-using System.Diagnostics;
-using System.Threading.Channels;
namespace Terminal.Gui;
@@ -35,20 +33,17 @@ public ComboBox ()
_listview = new ComboListView (this, HideDropdownListOnClick) { CanFocus = true, TabStop = TabBehavior.NoStop };
_search.TextChanged += Search_Changed;
- _search.Accept += Search_Accept;
_listview.Y = Pos.Bottom (_search);
- _listview.OpenSelectedItem += (sender, a) => Selected ();
-
- Add (_search, _listview);
-
- // BUGBUG: This should not be needed; LayoutComplete will handle
- Initialized += (s, e) => ProcessLayout ();
-
- // On resize
- LayoutComplete += (sender, a) => ProcessLayout ();
- ;
-
+ _listview.OpenSelectedItem += (sender, a) => SelectText ();
+ _listview.Accepting += (sender, args) =>
+ {
+ // This prevents Accepted from bubbling up to the combobox
+ args.Cancel = true;
+
+ // But OpenSelectedItem won't be fired because of that. So do it here.
+ SelectText ();
+ };
_listview.SelectedItemChanged += (sender, e) =>
{
if (!HideDropdownListOnClick && _searchSet.Count > 0)
@@ -56,6 +51,13 @@ public ComboBox ()
SetValue (_searchSet [_listview.SelectedItem]);
}
};
+ Add (_search, _listview);
+
+ // BUGBUG: This should not be needed; LayoutComplete will handle
+ Initialized += (s, e) => ProcessLayout ();
+
+ // On resize
+ LayoutComplete += (sender, a) => ProcessLayout ();
Added += (s, e) =>
{
@@ -76,28 +78,34 @@ public ComboBox ()
};
// Things this view knows how to do
- AddCommand (Command.Accept, () => ActivateSelected ());
- AddCommand (Command.ToggleExpandCollapse, () => ExpandCollapse ());
+ AddCommand (Command.Accept, (ctx) =>
+ {
+ if (ctx.Data == _search)
+ {
+ return null;
+ }
+ return ActivateSelected (ctx);
+ });
+ AddCommand (Command.Toggle, () => ExpandCollapse ());
AddCommand (Command.Expand, () => Expand ());
AddCommand (Command.Collapse, () => Collapse ());
- AddCommand (Command.LineDown, () => MoveDown ());
- AddCommand (Command.LineUp, () => MoveUp ());
+ AddCommand (Command.Down, () => MoveDown ());
+ AddCommand (Command.Up, () => MoveUp ());
AddCommand (Command.PageDown, () => PageDown ());
AddCommand (Command.PageUp, () => PageUp ());
- AddCommand (Command.TopHome, () => MoveHome ());
- AddCommand (Command.BottomEnd, () => MoveEnd ());
+ AddCommand (Command.Start, () => MoveHome ());
+ AddCommand (Command.End, () => MoveEnd ());
AddCommand (Command.Cancel, () => CancelSelected ());
AddCommand (Command.UnixEmulation, () => UnixEmulation ());
// Default keybindings for this view
- KeyBindings.Add (Key.Enter, Command.Accept);
- KeyBindings.Add (Key.F4, Command.ToggleExpandCollapse);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
+ KeyBindings.Add (Key.F4, Command.Toggle);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
KeyBindings.Add (Key.PageDown, Command.PageDown);
KeyBindings.Add (Key.PageUp, Command.PageUp);
- KeyBindings.Add (Key.Home, Command.TopHome);
- KeyBindings.Add (Key.End, Command.BottomEnd);
+ KeyBindings.Add (Key.Home, Command.Start);
+ KeyBindings.Add (Key.End, Command.End);
KeyBindings.Add (Key.Esc, Command.Cancel);
KeyBindings.Add (Key.U.WithCtrl, Command.UnixEmulation);
}
@@ -316,7 +324,7 @@ protected override void OnHasFocusChanged (bool newHasFocus, View previousFocuse
_search.CursorPosition = _search.Text.GetRuneCount ();
}
else
- {
+ {
if (_source?.Count > 0
&& _selectedItem > -1
&& _selectedItem < _source.Count - 1
@@ -384,13 +392,16 @@ public void SetSource (ObservableCollection source)
}
}
- private bool ActivateSelected ()
+ private bool ActivateSelected (CommandContext ctx)
{
if (HasItems ())
{
- Selected ();
+ if (SelectText ())
+ {
+ return false;
+ }
- return true;
+ return RaiseAccepting (ctx) == true;
}
return false;
@@ -658,9 +669,6 @@ private void ResetSearchSet (bool noCopy = false)
SetSearchSet ();
}
- // Tell TextField to handle Accept Command (Enter)
- void Search_Accept (object sender, HandledEventArgs e) { e.Handled = true; }
-
private void Search_Changed (object sender, EventArgs e)
{
if (_source is null)
@@ -720,7 +728,7 @@ private void ShowHideList (string oldText)
}
}
- private void Selected ()
+ private bool SelectText ()
{
IsShow = false;
_listview.TabStop = TabBehavior.NoStop;
@@ -731,7 +739,7 @@ private void Selected ()
HideList ();
IsShow = false;
- return;
+ return false;
}
SetValue (_listview.SelectedItem > -1 ? _searchSet [_listview.SelectedItem] : _text);
@@ -741,6 +749,8 @@ private void Selected ()
Reset (true);
HideList ();
IsShow = false;
+
+ return true;
}
private void SetSearchSet ()
@@ -992,7 +1002,7 @@ private void SetInitialProperties (ComboBox container, bool hideDropdownListOnCl
"ComboBox container cannot be null."
);
HideDropdownListOnClick = hideDropdownListOnClick;
- AddCommand (Command.LineUp, () => _container.MoveUpList ());
+ AddCommand (Command.Up, () => _container.MoveUpList ());
}
}
diff --git a/Terminal.Gui/Views/DateField.cs b/Terminal.Gui/Views/DateField.cs
index 0bb3d9ad2a..2e07f0afa8 100644
--- a/Terminal.Gui/Views/DateField.cs
+++ b/Terminal.Gui/Views/DateField.cs
@@ -395,7 +395,7 @@ private void SetInitialProperties (DateTime date)
return true;
}
);
- AddCommand (Command.LeftHome, () => MoveHome ());
+ AddCommand (Command.LeftStart, () => MoveHome ());
AddCommand (Command.Left, () => MoveLeft ());
AddCommand (Command.RightEnd, () => MoveEnd ());
AddCommand (Command.Right, () => MoveRight ());
@@ -406,8 +406,8 @@ private void SetInitialProperties (DateTime date)
KeyBindings.ReplaceCommands (Key.Backspace, Command.DeleteCharLeft);
- KeyBindings.ReplaceCommands (Key.Home, Command.LeftHome);
- KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftHome);
+ KeyBindings.ReplaceCommands (Key.Home, Command.LeftStart);
+ KeyBindings.ReplaceCommands (Key.A.WithCtrl, Command.LeftStart);
KeyBindings.ReplaceCommands (Key.CursorLeft, Command.Left);
KeyBindings.ReplaceCommands (Key.B.WithCtrl, Command.Left);
diff --git a/Terminal.Gui/Views/DatePicker.cs b/Terminal.Gui/Views/DatePicker.cs
index c4ac8951e6..aca0c086ee 100644
--- a/Terminal.Gui/Views/DatePicker.cs
+++ b/Terminal.Gui/Views/DatePicker.cs
@@ -184,7 +184,6 @@ private void SelectDayOnCalendar (int day)
private void SetInitialProperties (DateTime date)
{
_date = date;
- Title = "Date Picker";
BorderStyle = LineStyle.Single;
Date = date;
_dateLabel = new Label { X = 0, Y = 0, Text = "Date: " };
@@ -228,7 +227,7 @@ private void SetInitialProperties (DateTime date)
ShadowStyle = ShadowStyle.None
};
- _previousMonthButton.Accept += (sender, e) =>
+ _previousMonthButton.Accepting += (sender, e) =>
{
Date = _date.AddMonths (-1);
CreateCalendar ();
@@ -248,7 +247,7 @@ private void SetInitialProperties (DateTime date)
ShadowStyle = ShadowStyle.None
};
- _nextMonthButton.Accept += (sender, e) =>
+ _nextMonthButton.Accepting += (sender, e) =>
{
Date = _date.AddMonths (1);
CreateCalendar ();
diff --git a/Terminal.Gui/Views/Dialog.cs b/Terminal.Gui/Views/Dialog.cs
index d6626659d4..44cf5a168f 100644
--- a/Terminal.Gui/Views/Dialog.cs
+++ b/Terminal.Gui/Views/Dialog.cs
@@ -66,7 +66,7 @@ public class Dialog : Window
///
public Dialog ()
{
- Arrangement = ViewArrangement.Movable;
+ Arrangement = ViewArrangement.Movable | ViewArrangement.Overlapped;
ShadowStyle = DefaultShadow;
BorderStyle = DefaultBorderStyle;
@@ -80,17 +80,6 @@ public Dialog ()
Modal = true;
ButtonAlignment = DefaultButtonAlignment;
ButtonAlignmentModes = DefaultButtonAlignmentModes;
-
- AddCommand (
- Command.QuitToplevel,
- () =>
- {
- Canceled = true;
- RequestStop ();
-
- return true;
- });
- KeyBindings.Add (Key.Esc, Command.QuitToplevel);
}
// BUGBUG: We override GetNormal/FocusColor because "Dialog" ColorScheme is goofy.
diff --git a/Terminal.Gui/Views/FileDialog.cs b/Terminal.Gui/Views/FileDialog.cs
index 00f07a16c9..d0022fea00 100644
--- a/Terminal.Gui/Views/FileDialog.cs
+++ b/Terminal.Gui/Views/FileDialog.cs
@@ -78,7 +78,7 @@ internal FileDialog (IFileSystem fileSystem)
Y = Pos.AnchorEnd (),
IsDefault = true, Text = Style.OkButtonText
};
- _btnOk.Accept += (s, e) => Accept (true);
+ _btnOk.Accepting += (s, e) => Accept (true);
_btnCancel = new Button
@@ -88,7 +88,7 @@ internal FileDialog (IFileSystem fileSystem)
Text = Strings.btnCancel
};
- _btnCancel.Accept += (s, e) =>
+ _btnCancel.Accepting += (s, e) =>
{
Canceled = true;
Application.RequestStop ();
@@ -96,15 +96,15 @@ internal FileDialog (IFileSystem fileSystem)
_btnUp = new Button { X = 0, Y = 1, NoPadding = true };
_btnUp.Text = GetUpButtonText ();
- _btnUp.Accept += (s, e) => _history.Up ();
+ _btnUp.Accepting += (s, e) => _history.Up ();
_btnBack = new Button { X = Pos.Right (_btnUp) + 1, Y = 1, NoPadding = true };
_btnBack.Text = GetBackButtonText ();
- _btnBack.Accept += (s, e) => _history.Back ();
+ _btnBack.Accepting += (s, e) => _history.Back ();
_btnForward = new Button { X = Pos.Right (_btnBack) + 1, Y = 1, NoPadding = true };
_btnForward.Text = GetForwardButtonText ();
- _btnForward.Accept += (s, e) => _history.Forward ();
+ _btnForward.Accepting += (s, e) => _history.Forward ();
_tbPath = new TextField { Width = Dim.Fill (), CaptionColor = new Color (Color.Black) };
@@ -182,7 +182,7 @@ internal FileDialog (IFileSystem fileSystem)
Y = Pos.AnchorEnd (), Text = GetToggleSplitterText (false)
};
- _btnToggleSplitterCollapse.Accept += (s, e) =>
+ _btnToggleSplitterCollapse.Accepting += (s, e) =>
{
Tile tile = _splitContainer.Tiles.ElementAt (0);
@@ -236,10 +236,10 @@ internal FileDialog (IFileSystem fileSystem)
_tableView.KeyUp += (s, k) => k.Handled = TableView_KeyUp (k);
_tableView.SelectedCellChanged += TableView_SelectedCellChanged;
- _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.TopHome);
- _tableView.KeyBindings.ReplaceCommands (Key.End, Command.BottomEnd);
- _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.TopHomeExtend);
- _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.BottomEndExtend);
+ _tableView.KeyBindings.ReplaceCommands (Key.Home, Command.Start);
+ _tableView.KeyBindings.ReplaceCommands (Key.End, Command.End);
+ _tableView.KeyBindings.ReplaceCommands (Key.Home.WithShift, Command.StartExtend);
+ _tableView.KeyBindings.ReplaceCommands (Key.End.WithShift, Command.EndExtend);
AllowsMultipleSelection = false;
@@ -610,7 +610,7 @@ internal void SortColumn (int col, bool isAsc)
ApplySort ();
}
- private new void Accept (IEnumerable toMultiAccept)
+ private void Accept (IEnumerable toMultiAccept)
{
if (!AllowsMultipleSelection)
{
@@ -629,7 +629,7 @@ internal void SortColumn (int col, bool isAsc)
FinishAccept ();
}
- private new void Accept (IFileInfo f)
+ private void Accept (IFileInfo f)
{
if (!IsCompatibleWithOpenMode (f.FullName, out string reason))
{
@@ -649,7 +649,7 @@ internal void SortColumn (int col, bool isAsc)
FinishAccept ();
}
- private new void Accept (bool allowMulti)
+ private void Accept (bool allowMulti)
{
if (allowMulti && TryAcceptMulti ())
{
diff --git a/Terminal.Gui/Views/FrameView.cs b/Terminal.Gui/Views/FrameView.cs
index f109674133..f8b1ffc8bf 100644
--- a/Terminal.Gui/Views/FrameView.cs
+++ b/Terminal.Gui/Views/FrameView.cs
@@ -1,5 +1,6 @@
namespace Terminal.Gui;
+// TODO: FrameView is mis-named, really. It's far more about it being a TabGroup than a frame.
///
/// The FrameView is a container View with a border around it.
///
@@ -23,7 +24,8 @@ public FrameView ()
private void FrameView_MouseClick (object sender, MouseEventEventArgs e)
{
- e.Handled = InvokeCommand (Command.HotKey) == true;
+ // base sets focus on HotKey
+ e.Handled = InvokeCommand (Command.HotKey, ctx: new (Command.HotKey, key: null, data: this)) == true;
}
diff --git a/Terminal.Gui/Views/HexView.cs b/Terminal.Gui/Views/HexView.cs
index 49228ecc9b..16a3637559 100644
--- a/Terminal.Gui/Views/HexView.cs
+++ b/Terminal.Gui/Views/HexView.cs
@@ -25,7 +25,7 @@ namespace Terminal.Gui;
///
/// Control the first byte shown by setting the property to an offset in the stream.
///
-public class HexView : View
+public class HexView : View, IDesignable
{
private const int bsize = 4;
private const int displayWidth = 9;
@@ -33,7 +33,8 @@ public class HexView : View
private int bpl;
private long displayStart, pos;
private SortedDictionary edits = [];
- private bool firstNibble, leftSide;
+ private bool firstNibble;
+ private bool leftSide;
private Stream source;
private static readonly Rune SpaceCharRune = new (' ');
private static readonly Rune PeriodCharRune = new ('.');
@@ -46,8 +47,7 @@ public class HexView : View
public HexView (Stream source)
{
Source = source;
- // BUG: This will always call the most-derived definition of CanFocus.
- // Either seal it or don't set it here.
+
CanFocus = true;
CursorVisibility = CursorVisibility.Default;
leftSide = true;
@@ -59,15 +59,16 @@ public HexView (Stream source)
// Things this view knows how to do
AddCommand (Command.Left, () => MoveLeft ());
AddCommand (Command.Right, () => MoveRight ());
- AddCommand (Command.LineDown, () => MoveDown (bytesPerLine));
- AddCommand (Command.LineUp, () => MoveUp (bytesPerLine));
- AddCommand (Command.Accept, () => ToggleSide ());
+ AddCommand (Command.Down, () => MoveDown (bytesPerLine));
+ AddCommand (Command.Up, () => MoveUp (bytesPerLine));
+ AddCommand (Command.Tab, () => Navigate (NavigationDirection.Forward));
+ AddCommand (Command.BackTab, () => Navigate (NavigationDirection.Backward));
AddCommand (Command.PageUp, () => MoveUp (bytesPerLine * Frame.Height));
AddCommand (Command.PageDown, () => MoveDown (bytesPerLine * Frame.Height));
- AddCommand (Command.TopHome, () => MoveHome ());
- AddCommand (Command.BottomEnd, () => MoveEnd ());
- AddCommand (Command.StartOfLine, () => MoveStartOfLine ());
- AddCommand (Command.EndOfLine, () => MoveEndOfLine ());
+ AddCommand (Command.Start, () => MoveHome ());
+ AddCommand (Command.End, () => MoveEnd ());
+ AddCommand (Command.LeftStart, () => MoveLeftStart ());
+ AddCommand (Command.RightEnd, () => MoveEndOfLine ());
AddCommand (Command.StartOfPage, () => MoveUp (bytesPerLine * ((int)(position - displayStart) / bytesPerLine)));
AddCommand (
@@ -78,9 +79,8 @@ public HexView (Stream source)
// Default keybindings for this view
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.CursorRight, Command.Right);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
- KeyBindings.Add (Key.Enter, Command.Accept);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
KeyBindings.Add (Key.PageUp, Command.PageUp);
@@ -88,13 +88,16 @@ public HexView (Stream source)
KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
KeyBindings.Add (Key.PageDown, Command.PageDown);
- KeyBindings.Add (Key.Home, Command.TopHome);
- KeyBindings.Add (Key.End, Command.BottomEnd);
- KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.StartOfLine);
- KeyBindings.Add (Key.CursorRight.WithCtrl, Command.EndOfLine);
+ KeyBindings.Add (Key.Home, Command.Start);
+ KeyBindings.Add (Key.End, Command.End);
+ KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.LeftStart);
+ KeyBindings.Add (Key.CursorRight.WithCtrl, Command.RightEnd);
KeyBindings.Add (Key.CursorUp.WithCtrl, Command.StartOfPage);
KeyBindings.Add (Key.CursorDown.WithCtrl, Command.EndOfPage);
+ KeyBindings.Add (Key.Tab, Command.Tab);
+ KeyBindings.Add (Key.Tab.WithShift, Command.BackTab);
+
LayoutComplete += HexView_LayoutComplete;
}
@@ -247,7 +250,7 @@ public void ApplyEdits (Stream stream = null)
public event EventHandler Edited;
///
- protected internal override bool OnMouseEvent (MouseEvent me)
+ protected internal override bool OnMouseEvent (MouseEvent me)
{
if (!me.Flags.HasFlag (MouseFlags.Button1Clicked)
&& !me.Flags.HasFlag (MouseFlags.Button1DoubleClicked)
@@ -529,12 +532,14 @@ public override bool OnProcessKeyDown (Key keyEvent)
int x = displayWidth + block * 14 + column + (firstNibble ? 0 : 1);
int y = line;
+
if (!leftSide)
{
x = displayWidth + bytesPerLine / bsize * 14 + item - 1;
}
Move (x, y);
+
return new (x, y);
}
@@ -728,7 +733,7 @@ private bool MoveRight ()
return true;
}
- private bool MoveStartOfLine ()
+ private bool MoveLeftStart ()
{
position = position / bytesPerLine * bytesPerLine;
SetNeedsDisplay ();
@@ -764,17 +769,50 @@ private void RedisplayLine (long pos)
{
return;
}
+
var delta = (int)(pos - DisplayStart);
int line = delta / bytesPerLine;
SetNeedsDisplay (new (0, line, Viewport.Width, 1));
}
- private bool ToggleSide ()
+ private bool Navigate (NavigationDirection direction)
{
- leftSide = !leftSide;
- RedisplayLine (position);
- firstNibble = true;
+ switch (direction)
+ {
+ case NavigationDirection.Forward:
+ if (leftSide)
+ {
+ leftSide = false;
+ RedisplayLine (position);
+ firstNibble = true;
+
+ return true;
+ }
+
+ break;
+
+ case NavigationDirection.Backward:
+ if (!leftSide)
+ {
+ leftSide = true;
+ RedisplayLine (position);
+ firstNibble = true;
+ return true;
+ }
+
+
+ break;
+ }
+
+ return false;
+ }
+
+
+ ///
+ bool IDesignable.EnableForDesign ()
+ {
+ Source = new MemoryStream (Encoding.UTF8.GetBytes ("HexEditor Unicode that shouldn't 𝔹Aℝ𝔽!"));
return true;
}
diff --git a/Terminal.Gui/Views/HistoryTextItemEventArgs.cs b/Terminal.Gui/Views/HistoryTextItemEventArgs.cs
index ca2ad3ad7e..c75f4a5e8b 100644
--- a/Terminal.Gui/Views/HistoryTextItemEventArgs.cs
+++ b/Terminal.Gui/Views/HistoryTextItemEventArgs.cs
@@ -9,11 +9,11 @@ public class HistoryTextItemEventArgs : EventArgs
public Point CursorPosition;
public Point FinalCursorPosition;
public bool IsUndoing;
- public List> Lines;
+ public List> Lines;
public LineStatus LineStatus;
public HistoryTextItemEventArgs RemovedOnAdded;
- public HistoryTextItemEventArgs (List> lines, Point curPos, LineStatus linesStatus)
+ public HistoryTextItemEventArgs (List> lines, Point curPos, LineStatus linesStatus)
{
Lines = lines;
CursorPosition = curPos;
@@ -22,7 +22,7 @@ public HistoryTextItemEventArgs (List> lines, Point curPos, LineS
public HistoryTextItemEventArgs (HistoryTextItemEventArgs historyTextItem)
{
- Lines = new List> (historyTextItem.Lines);
+ Lines = new List> (historyTextItem.Lines);
CursorPosition = new Point (historyTextItem.CursorPosition.X, historyTextItem.CursorPosition.Y);
LineStatus = historyTextItem.LineStatus;
}
diff --git a/Terminal.Gui/Views/Label.cs b/Terminal.Gui/Views/Label.cs
index 344120ebaf..3a76a8a717 100644
--- a/Terminal.Gui/Views/Label.cs
+++ b/Terminal.Gui/Views/Label.cs
@@ -1,14 +1,22 @@
namespace Terminal.Gui;
///
-/// The Label displays a string at a given position and supports multiple lines separated by
-/// newline characters. Multi-line Labels support word wrap.
+/// The Label displays text that describes the View next in the . When
+/// Label
+/// recieves a command it will pass it to the next in
+/// .
///
///
-/// The view is functionality identical to and is included for API
-/// backwards compatibility.
+///
+/// Title and Text are the same property. When Title is set Text s also set. When Text is set Title is also set.
+///
+///
+/// If is and the use clicks on the Label,
+/// the will be invoked on the next in
+/// ."
+///
///
-public class Label : View
+public class Label : View, IDesignable
{
///
public Label ()
@@ -16,21 +24,19 @@ public Label ()
Height = Dim.Auto (DimAutoStyle.Text);
Width = Dim.Auto (DimAutoStyle.Text);
- // Things this view knows how to do
- AddCommand (Command.HotKey, FocusNext);
-
- // Default key bindings for this view
- KeyBindings.Add (Key.Space, Command.Accept);
+ // On HoKey, pass it to the next view
+ AddCommand (Command.HotKey, InvokeHotKeyOnNext);
TitleChanged += Label_TitleChanged;
MouseClick += Label_MouseClick;
}
+ // TODO: base raises Select, but we want to raise HotKey. This can be simplified?
private void Label_MouseClick (object sender, MouseEventEventArgs e)
{
if (!CanFocus)
{
- e.Handled = InvokeCommand (Command.HotKey) == true;
+ e.Handled = InvokeCommand (Command.HotKey, ctx: new (Command.HotKey, key: null, data: this)) == true;
}
}
@@ -40,28 +46,49 @@ private void Label_TitleChanged (object sender, EventArgs e)
TextFormatter.HotKeySpecifier = HotKeySpecifier;
}
- ///
+ ///
public override string Text
{
- get => base.Title;
- set => base.Text = base.Title = value;
+ get => Title;
+ set => base.Text = Title = value;
}
- ///
+ ///
public override Rune HotKeySpecifier
{
get => base.HotKeySpecifier;
set => TextFormatter.HotKeySpecifier = base.HotKeySpecifier = value;
}
- private bool? FocusNext ()
+ private bool? InvokeHotKeyOnNext (CommandContext context)
{
+ if (RaiseHandlingHotKey () == true)
+ {
+ return true;
+ }
+
+ if (CanFocus)
+ {
+ SetFocus ();
+
+ return true;
+ }
+
int me = SuperView?.Subviews.IndexOf (this) ?? -1;
+
if (me != -1 && me < SuperView?.Subviews.Count - 1)
{
- SuperView?.Subviews [me + 1].SetFocus ();
+ return SuperView?.Subviews [me + 1].InvokeCommand (Command.HotKey, context.Key, context.KeyBinding) == true;
}
+ return false;
+ }
+
+ ///
+ bool IDesignable.EnableForDesign ()
+ {
+ Text = "_Label";
+
return true;
}
}
diff --git a/Terminal.Gui/Views/ListView.cs b/Terminal.Gui/Views/ListView.cs
index 2c15717802..0c54aa2612 100644
--- a/Terminal.Gui/Views/ListView.cs
+++ b/Terminal.Gui/Views/ListView.cs
@@ -6,7 +6,7 @@
namespace Terminal.Gui;
/// Implement to provide custom rendering for a .
-public interface IListDataSource: IDisposable
+public interface IListDataSource : IDisposable
{
///
/// Event to raise when an item is added, removed, or moved, or the entire list is refreshed.
@@ -123,47 +123,95 @@ public ListView ()
// Things this view knows how to do
//
- // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
- AddCommand (Command.LineUp, () => MoveUp ());
- // BUGBUG: SHould return false if selectokn doesn't change (to support nav to next view)
- AddCommand (Command.LineDown, () => MoveDown ());
+ // BUGBUG: Should return false if selection doesn't change (to support nav to next view)
+ AddCommand (Command.Up, () => MoveUp ());
+ // BUGBUG: Should return false if selection doesn't change (to support nav to next view)
+ AddCommand (Command.Down, () => MoveDown ());
+
AddCommand (Command.ScrollUp, () => ScrollVertical (-1));
AddCommand (Command.ScrollDown, () => ScrollVertical (1));
AddCommand (Command.PageUp, () => MovePageUp ());
AddCommand (Command.PageDown, () => MovePageDown ());
- AddCommand (Command.TopHome, () => MoveHome ());
- AddCommand (Command.BottomEnd, () => MoveEnd ());
- AddCommand (Command.Accept, () => OnOpenSelectedItem ());
- AddCommand (Command.OpenSelectedItem, () => OnOpenSelectedItem ());
- AddCommand (Command.Select, () => MarkUnmarkRow ());
-
+ AddCommand (Command.Start, () => MoveHome ());
+ AddCommand (Command.End, () => MoveEnd ());
AddCommand (Command.ScrollLeft, () => ScrollHorizontal (-1));
AddCommand (Command.ScrollRight, () => ScrollHorizontal (1));
+ // Accept (Enter key) - Raise Accept event - DO NOT advance state
+ AddCommand (Command.Accept, (ctx) =>
+ {
+ if (RaiseAccepting (ctx) == true)
+ {
+ return true;
+ }
+
+ if (OnOpenSelectedItem ())
+ {
+ return true;
+ }
+
+ return false;
+ });
+
+ // Select (Space key and single-click) - If markable, change mark and raise Select event
+ AddCommand (Command.Select, (ctx) =>
+ {
+ if (_allowsMarking)
+ {
+ if (RaiseSelecting (ctx) == true)
+ {
+ return true;
+ }
+
+ if (MarkUnmarkSelectedItem ())
+ {
+ return true;
+ }
+ }
+
+ return false;
+ });
+
+
+ // Hotkey - If none set, select and raise Select event. SetFocus. - DO NOT raise Accept
+ AddCommand (Command.HotKey, (ctx) =>
+ {
+ if (SelectedItem == -1)
+ {
+ SelectedItem = 0;
+ if (RaiseSelecting (ctx) == true)
+ {
+ return true;
+
+ }
+ }
+
+ return !SetFocus ();
+ });
+
+
// Default keybindings for all ListViews
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
- KeyBindings.Add (Key.P.WithCtrl, Command.LineUp);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
+ KeyBindings.Add (Key.P.WithCtrl, Command.Up);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
- KeyBindings.Add (Key.N.WithCtrl, Command.LineDown);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
+ KeyBindings.Add (Key.N.WithCtrl, Command.Down);
KeyBindings.Add (Key.PageUp, Command.PageUp);
KeyBindings.Add (Key.PageDown, Command.PageDown);
KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
- KeyBindings.Add (Key.Home, Command.TopHome);
-
- KeyBindings.Add (Key.End, Command.BottomEnd);
+ KeyBindings.Add (Key.Home, Command.Start);
- KeyBindings.Add (Key.Enter, Command.OpenSelectedItem);
+ KeyBindings.Add (Key.End, Command.End);
}
/// Gets or sets whether this allows items to be marked.
/// Set to to allow marking elements of the list.
///
/// If set to , will render items marked items with "[x]", and
- /// unmarked items with "[ ]" spaces. SPACE key will toggle marking. The default is .
+ /// unmarked items with "[ ]". SPACE key will toggle marking. The default is .
///
public bool AllowsMarking
{
@@ -171,16 +219,6 @@ public bool AllowsMarking
set
{
_allowsMarking = value;
-
- if (_allowsMarking)
- {
- KeyBindings.Add (Key.Space, Command.Select);
- }
- else
- {
- KeyBindings.Remove (Key.Space);
- }
-
SetNeedsDisplay ();
}
}
@@ -334,10 +372,10 @@ public int TopItem
///
/// If and are both ,
- /// unmarks all marked items other than the currently selected.
+ /// unmarks all marked items other than .
///
/// if unmarking was successful.
- public virtual bool AllowsAll ()
+ public bool UnmarkAllButSelected ()
{
if (!_allowsMarking)
{
@@ -385,16 +423,18 @@ public void EnsureSelectedItemVisible ()
/// Marks the if it is not already marked.
/// if the was marked.
- public virtual bool MarkUnmarkRow ()
+ public bool MarkUnmarkSelectedItem ()
{
- if (AllowsAll ())
+ if (UnmarkAllButSelected ())
{
Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem));
SetNeedsDisplay ();
- return true;
+ return Source.IsMarked (SelectedItem);
}
+ // BUGBUG: Shouldn't this retrn Source.IsMarked (SelectedItem)
+
return false;
}
@@ -458,12 +498,9 @@ protected internal override bool OnMouseEvent (MouseEvent me)
_selected = Viewport.Y + me.Position.Y;
- if (AllowsAll ())
+ if (MarkUnmarkSelectedItem ())
{
- Source.SetMark (SelectedItem, !Source.IsMarked (SelectedItem));
- SetNeedsDisplay ();
-
- return true;
+ // return true;
}
OnSelectedChanged ();
@@ -471,7 +508,7 @@ protected internal override bool OnMouseEvent (MouseEvent me)
if (me.Flags == MouseFlags.Button1DoubleClicked)
{
- OnOpenSelectedItem ();
+ return InvokeCommand (Command.Accept) is true;
}
return true;
@@ -759,13 +796,9 @@ public bool OnOpenSelectedItem ()
object value = _source.ToList () [_selected];
- // By default, Command.Accept calls OnAccept, so we need to call it here to ensure that the event is fired.
- if (OnAccept () == true)
- {
- return true;
- }
-
OpenSelectedItem?.Invoke (this, new ListViewItemEventArgs (_selected, value));
+
+ // BUGBUG: this should not blindly return true.
return true;
}
@@ -794,6 +827,7 @@ public override bool OnProcessKeyDown (Key a)
///
public virtual void OnRowRender (ListViewRowEventArgs rowEventArgs) { RowRender?.Invoke (this, rowEventArgs); }
+ // TODO: Use standard event model
/// Invokes the event if it is defined.
///
public virtual bool OnSelectedChanged ()
diff --git a/Terminal.Gui/Views/Menu/ContextMenu.cs b/Terminal.Gui/Views/Menu/ContextMenu.cs
index 8751ada923..a963036dcc 100644
--- a/Terminal.Gui/Views/Menu/ContextMenu.cs
+++ b/Terminal.Gui/Views/Menu/ContextMenu.cs
@@ -177,7 +177,7 @@ public void Show (MenuBarItem? menuItems)
}
MenuItems = menuItems;
- _container = Application.Current;
+ _container = Application.Top;
_container!.Closing += Container_Closing;
_container.Deactivate += Container_Deactivate;
_container.Disposing += Container_Disposing;
diff --git a/Terminal.Gui/Views/Menu/Menu.cs b/Terminal.Gui/Views/Menu/Menu.cs
index 9fcf3fd4c0..1744251f3c 100644
--- a/Terminal.Gui/Views/Menu/Menu.cs
+++ b/Terminal.Gui/Views/Menu/Menu.cs
@@ -101,7 +101,7 @@ public override void BeginInit ()
for (var i = 0; i < _barItems.Children?.Length; i++)
{
- if (_barItems.Children [i]!.IsEnabled ())
+ if (_barItems.Children [i]?.IsEnabled () == true)
{
_currentChild = i;
@@ -144,17 +144,17 @@ _barItems.Children [_currentChild]!
public Menu ()
{
- if (Application.Current is { })
+ if (Application.Top is { })
{
- Application.Current.DrawContentComplete += Current_DrawContentComplete;
- Application.Current.SizeChanging += Current_TerminalResized;
+ Application.Top.DrawContentComplete += Current_DrawContentComplete;
+ Application.Top.SizeChanging += Current_TerminalResized;
}
Application.MouseEvent += Application_RootMouseEvent;
// Things this view knows how to do
- AddCommand (Command.LineUp, () => MoveUp ());
- AddCommand (Command.LineDown, () => MoveDown ());
+ AddCommand (Command.Up, () => MoveUp ());
+ AddCommand (Command.Down, () => MoveDown ());
AddCommand (
Command.Left,
@@ -186,16 +186,15 @@ public Menu ()
}
);
AddCommand (Command.Select, ctx => _host?.SelectItem ((ctx.KeyBinding?.Context as MenuItem)!));
- AddCommand (Command.ToggleExpandCollapse, ctx => ExpandCollapse ((ctx.KeyBinding?.Context as MenuItem)!));
+ AddCommand (Command.Toggle, ctx => ExpandCollapse ((ctx.KeyBinding?.Context as MenuItem)!));
AddCommand (Command.HotKey, ctx => _host?.SelectItem ((ctx.KeyBinding?.Context as MenuItem)!));
// Default key bindings for this view
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.CursorRight, Command.Right);
KeyBindings.Add (Key.Esc, Command.Cancel);
- KeyBindings.Add (Key.Enter, Command.Accept);
}
private void AddKeyBindingsHotKey (MenuBarItem? menuBarItem)
@@ -209,7 +208,7 @@ private void AddKeyBindingsHotKey (MenuBarItem? menuBarItem)
foreach (MenuItem menuItem in menuItems)
{
- KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuItem);
+ KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, menuItem);
if (menuItem.HotKey != Key.Empty)
{
@@ -330,7 +329,7 @@ private void Current_TerminalResized (object? sender, SizeChangedEventArgs e)
}
///
- public override void OnVisibleChanged ()
+ protected override void OnVisibleChanged ()
{
base.OnVisibleChanged ();
@@ -386,7 +385,7 @@ internal Attribute DetermineColorSchemeFor (MenuItem? item, int index)
return GetFocusColor ();
}
- return !item.IsEnabled () ? ColorScheme.Disabled : GetNormalColor ();
+ return !item.IsEnabled () ? ColorScheme!.Disabled : GetNormalColor ();
}
public override void OnDrawContent (Rectangle viewport)
@@ -518,7 +517,7 @@ public override void OnDrawContent (Rectangle viewport)
if (!item.IsEnabled ())
{
- DrawHotString (textToDraw, ColorScheme.Disabled, ColorScheme.Disabled);
+ DrawHotString (textToDraw, ColorScheme!.Disabled, ColorScheme.Disabled);
}
else if (i == 0 && _host.UseSubMenusSingleFrame && item.Parent!.Parent is { })
{
@@ -533,7 +532,7 @@ public override void OnDrawContent (Rectangle viewport)
tf.Draw (
ViewportToScreen (new Rectangle (1, i, Frame.Width - 3, 1)),
i == _currentChild ? GetFocusColor () : GetNormalColor (),
- i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+ i == _currentChild ? ColorScheme!.HotFocus : ColorScheme!.HotNormal,
SuperView?.ViewportToScreen (SuperView.Viewport) ?? Rectangle.Empty
);
}
@@ -541,7 +540,7 @@ public override void OnDrawContent (Rectangle viewport)
{
DrawHotString (
textToDraw,
- i == _currentChild ? ColorScheme.HotFocus : ColorScheme.HotNormal,
+ i == _currentChild ? ColorScheme!.HotFocus : ColorScheme!.HotNormal,
i == _currentChild ? GetFocusColor () : GetNormalColor ()
);
}
@@ -607,6 +606,7 @@ public void Run (Action? action)
Application.UngrabMouse ();
_host.CloseAllMenus ();
+ Application.Driver!.ClearContents ();
Application.Refresh ();
_host.Run (action);
@@ -952,10 +952,10 @@ protected override void Dispose (bool disposing)
{
RemoveKeyBindingsHotKey (_barItems);
- if (Application.Current is { })
+ if (Application.Top is { })
{
- Application.Current.DrawContentComplete -= Current_DrawContentComplete;
- Application.Current.SizeChanging -= Current_TerminalResized;
+ Application.Top.DrawContentComplete -= Current_DrawContentComplete;
+ Application.Top.SizeChanging -= Current_TerminalResized;
}
Application.MouseEvent -= Application_RootMouseEvent;
diff --git a/Terminal.Gui/Views/Menu/MenuBar.cs b/Terminal.Gui/Views/Menu/MenuBar.cs
index 76c00ed197..731edc11ca 100644
--- a/Terminal.Gui/Views/Menu/MenuBar.cs
+++ b/Terminal.Gui/Views/Menu/MenuBar.cs
@@ -119,14 +119,17 @@ public MenuBar ()
AddCommand (
Command.Accept,
- () =>
+ (ctx) =>
{
- ProcessMenu (_selected, Menus [_selected]);
+ if (Menus.Length > 0)
+ {
+ ProcessMenu (_selected, Menus [_selected]);
+ }
- return true;
+ return RaiseAccepting (ctx);
}
);
- AddCommand (Command.ToggleExpandCollapse, ctx =>
+ AddCommand (Command.Toggle, ctx =>
{
CloseOtherOpenedMenuBar ();
@@ -134,7 +137,12 @@ public MenuBar ()
});
AddCommand (Command.Select, ctx =>
{
- var res = Run ((ctx.KeyBinding?.Context as MenuItem)?.Action!);
+ if (ctx.Data is MouseEvent)
+ {
+ // HACK: Work around the fact that View.MouseClick always invokes Select
+ return false;
+ }
+ var res = Run ((ctx.KeyBinding?.Context as MenuItem)?.Action!);
CloseAllMenus ();
return res;
@@ -145,9 +153,8 @@ public MenuBar ()
KeyBindings.Add (Key.CursorRight, Command.Right);
KeyBindings.Add (Key.Esc, Command.Cancel);
KeyBindings.Add (Key.CursorDown, Command.Accept);
- KeyBindings.Add (Key.Enter, Command.Accept);
- KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
+ KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
KeyBindings.Add (Key, keyBinding);
// TODO: Why do we have two keybindings for opening the menu? Ctrl-Space and Key?
@@ -190,10 +197,10 @@ public MenuBarItem [] Menus
if (menuBarItem.HotKey != Key.Empty)
{
KeyBindings.Remove (menuBarItem.HotKey!);
- KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.Focused, menuBarItem);
+ KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.Focused, menuBarItem);
KeyBindings.Add (menuBarItem.HotKey!, keyBinding);
KeyBindings.Remove (menuBarItem.HotKey!.WithAlt);
- keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, menuBarItem);
+ keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, menuBarItem);
KeyBindings.Add (menuBarItem.HotKey.WithAlt, keyBinding);
}
@@ -306,7 +313,7 @@ public override void OnDrawContent (Rectangle viewport)
if (i == _selected && IsMenuOpen)
{
- hotColor = i == _selected ? ColorScheme.HotFocus : GetHotNormalColor ();
+ hotColor = i == _selected ? ColorScheme!.HotFocus : GetHotNormalColor ();
normalColor = i == _selected ? GetFocusColor () : GetNormalColor ();
}
else
@@ -351,7 +358,7 @@ public virtual MenuClosingEventArgs OnMenuClosing (MenuBarItem currentMenu, bool
/// Virtual method that will invoke the event if it's defined.
public virtual void OnMenuOpened ()
{
- MenuItem? mi;
+ MenuItem? mi = null;
MenuBarItem? parent;
if (OpenCurrentMenu?.BarItems?.Children is { Length: > 0 }
@@ -368,7 +375,11 @@ public virtual void OnMenuOpened ()
else
{
parent = _openMenu?.BarItems;
- mi = parent?.Children?.Length > 0 ? parent.Children [_openMenu!._currentChild] : null;
+
+ if (OpenCurrentMenu?._currentChild > -1)
+ {
+ mi = parent?.Children?.Length > 0 ? parent.Children [_openMenu!._currentChild] : null;
+ }
}
MenuOpened?.Invoke (this, new (parent, mi));
@@ -402,7 +413,7 @@ public void OpenMenu ()
_selected = 0;
SetNeedsDisplay ();
- _previousFocused = (SuperView is null ? Application.Current?.Focused : SuperView.Focused)!;
+ _previousFocused = (SuperView is null ? Application.Top?.Focused : SuperView.Focused)!;
OpenMenu (_selected);
if (!SelectEnabledItem (
@@ -463,7 +474,7 @@ internal void Activate (int idx, int sIdx = -1, MenuBarItem? subMenu = null!)
if (_openMenu is null)
{
- _previousFocused = (SuperView is null ? Application.Current?.Focused ?? null : SuperView.Focused)!;
+ _previousFocused = (SuperView is null ? Application.Top?.Focused ?? null : SuperView.Focused)!;
}
OpenMenu (idx, sIdx, subMenu);
@@ -540,10 +551,10 @@ internal void CloseAllMenus ()
private void CloseOtherOpenedMenuBar ()
{
- if (Application.Current is { })
+ if (Application.Top is { })
{
// Close others menu bar opened
- Menu? menu = Application.Current.Subviews.FirstOrDefault (v => v is Menu m && m.Host != this && m.Host.IsMenuOpen) as Menu;
+ Menu? menu = Application.Top.Subviews.FirstOrDefault (v => v is Menu m && m.Host != this && m.Host.IsMenuOpen) as Menu;
menu?.Host.CleanUp ();
}
}
@@ -579,7 +590,7 @@ internal bool CloseMenu (bool reopen, bool isSubMenu, bool ignoreUseSubMenusSing
case false:
if (_openMenu is { })
{
- Application.Current?.Remove (_openMenu);
+ Application.Top?.Remove (_openMenu);
}
SetNeedsDisplay ();
@@ -589,6 +600,10 @@ internal bool CloseMenu (bool reopen, bool isSubMenu, bool ignoreUseSubMenusSing
_previousFocused.SetFocus ();
}
+ if (Application.MouseGrabView == _openMenu)
+ {
+ Application.UngrabMouse();
+ }
_openMenu?.Dispose ();
_openMenu = null;
@@ -614,7 +629,11 @@ internal bool CloseMenu (bool reopen, bool isSubMenu, bool ignoreUseSubMenusSing
if (OpenCurrentMenu is { })
{
- Application.Current?.Remove (OpenCurrentMenu);
+ Application.Top?.Remove (OpenCurrentMenu);
+ if (Application.MouseGrabView == OpenCurrentMenu)
+ {
+ Application.UngrabMouse ();
+ }
OpenCurrentMenu.Dispose ();
OpenCurrentMenu = null;
}
@@ -662,7 +681,7 @@ internal Point GetScreenOffset ()
}
Rectangle superViewFrame = SuperView?.Frame ?? Application.Screen;
- View? sv = SuperView ?? Application.Current;
+ View? sv = SuperView ?? Application.Top;
if (sv is null)
{
@@ -789,7 +808,7 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null!
{
case null:
// Open a submenu below a MenuBar
- _lastFocused ??= SuperView is null ? Application.Current?.MostFocused : SuperView.MostFocused;
+ _lastFocused ??= SuperView is null ? Application.Top?.MostFocused : SuperView.MostFocused;
if (_openSubMenu is { } && !CloseMenu (false, true))
{
@@ -798,7 +817,11 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null!
if (_openMenu is { })
{
- Application.Current?.Remove (_openMenu);
+ Application.Top?.Remove (_openMenu);
+ if (Application.MouseGrabView == _openMenu)
+ {
+ Application.UngrabMouse ();
+ }
_openMenu.Dispose ();
_openMenu = null;
}
@@ -818,7 +841,7 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null!
locationOffset = GetScreenOffset ();
}
- if (SuperView is { } && SuperView != Application.Current)
+ if (SuperView is { } && SuperView != Application.Top)
{
locationOffset.X += SuperView.Border.Thickness.Left;
locationOffset.Y += SuperView.Border.Thickness.Top;
@@ -835,9 +858,9 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null!
OpenCurrentMenu = _openMenu;
OpenCurrentMenu._previousSubFocused = _openMenu;
- if (Application.Current is { })
+ if (Application.Top is { })
{
- Application.Current.Add (_openMenu);
+ Application.Top.Add (_openMenu);
}
else
{
@@ -902,7 +925,7 @@ internal void OpenMenu (int index, int sIndex = -1, MenuBarItem? subMenu = null!
OpenCurrentMenu._previousSubFocused = last._previousSubFocused;
_openSubMenu.Add (OpenCurrentMenu);
- Application.Current?.Add (OpenCurrentMenu);
+ Application.Top?.Add (OpenCurrentMenu);
if (!OpenCurrentMenu.IsInitialized)
{
@@ -985,7 +1008,11 @@ internal void RemoveAllOpensSubMenus ()
{
foreach (Menu item in _openSubMenu)
{
- Application.Current!.Remove (item);
+ Application.Top!.Remove (item);
+ if (Application.MouseGrabView == item)
+ {
+ Application.UngrabMouse ();
+ }
item.Dispose ();
}
}
@@ -1150,11 +1177,11 @@ private void MoveRight ()
SetNeedsDisplay ();
}
- private void ProcessMenu (int i, MenuBarItem mi)
+ private bool ProcessMenu (int i, MenuBarItem mi)
{
if (_selected < 0 && IsMenuOpen)
{
- return;
+ return false;
}
if (mi.IsTopLevel)
@@ -1162,6 +1189,10 @@ private void ProcessMenu (int i, MenuBarItem mi)
Point screen = ViewportToScreen (new Point (0, i));
var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = mi };
menu.Run (mi.Action);
+ if (Application.MouseGrabView == menu)
+ {
+ Application.UngrabMouse ();
+ }
menu.Dispose ();
}
else
@@ -1177,16 +1208,18 @@ out OpenCurrentMenu._currentChild
)
&& !CloseMenu ())
{
- return;
+ return true;
}
if (!OpenCurrentMenu.CheckSubMenu ())
{
- return;
+ return true;
}
}
SetNeedsDisplay ();
+
+ return true;
}
private void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false)
@@ -1224,7 +1257,7 @@ private void RemoveSubMenu (int index, bool ignoreUseSubMenusSingleFrame = false
if (_openSubMenu is { })
{
menu = _openSubMenu [i];
- Application.Current!.Remove (menu);
+ Application.Top!.Remove (menu);
_openSubMenu.Remove (menu);
if (Application.MouseGrabView == menu)
@@ -1272,7 +1305,7 @@ public Key Key
}
KeyBindings.Remove (_key);
- KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
+ KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, -1); // -1 indicates Key was used
KeyBindings.Add (value, keyBinding);
_key = value;
}
@@ -1405,6 +1438,11 @@ protected internal override bool OnMouseEvent (MouseEvent me)
Point screen = ViewportToScreen (new Point (0, i));
var menu = new Menu { Host = this, X = screen.X, Y = screen.Y, BarItems = Menus [i] };
menu.Run (Menus [i].Action);
+ if (Application.MouseGrabView == menu)
+ {
+ Application.UngrabMouse ();
+ }
+
menu.Dispose ();
}
else if (!IsMenuOpen)
diff --git a/Terminal.Gui/Views/Menu/MenuItem.cs b/Terminal.Gui/Views/Menu/MenuItem.cs
index 0c920da529..016cf28750 100644
--- a/Terminal.Gui/Views/Menu/MenuItem.cs
+++ b/Terminal.Gui/Views/Menu/MenuItem.cs
@@ -325,7 +325,7 @@ private void UpdateHotKeyBinding (Key oldKey)
if (index > -1)
{
_menuBar.KeyBindings.Remove (HotKey!.WithAlt);
- KeyBinding keyBinding = new ([Command.ToggleExpandCollapse], KeyBindingScope.HotKey, this);
+ KeyBinding keyBinding = new ([Command.Toggle], KeyBindingScope.HotKey, this);
_menuBar.KeyBindings.Add (HotKey.WithAlt, keyBinding);
}
}
diff --git a/Terminal.Gui/Views/Menuv2.cs b/Terminal.Gui/Views/Menuv2.cs
index f0024ae28f..e8d371e22d 100644
--- a/Terminal.Gui/Views/Menuv2.cs
+++ b/Terminal.Gui/Views/Menuv2.cs
@@ -1,4 +1,5 @@
using System;
+using System.ComponentModel;
using System.Reflection;
namespace Terminal.Gui;
@@ -16,13 +17,30 @@ public Menuv2 (IEnumerable shortcuts) : base (shortcuts)
Orientation = Orientation.Vertical;
Width = Dim.Auto ();
Height = Dim.Auto (DimAutoStyle.Content, 1);
- ColorScheme = Colors.ColorSchemes ["Menu"];
Initialized += Menuv2_Initialized;
+ VisibleChanged += OnVisibleChanged;
+ }
+
+ private void OnVisibleChanged (object sender, EventArgs e)
+ {
+ if (Visible)
+ {
+ //Application.GrabMouse(this);
+ }
+ else
+ {
+ if (Application.MouseGrabView == this)
+ {
+ //Application.UngrabMouse ();
+ }
+ }
}
private void Menuv2_Initialized (object sender, EventArgs e)
{
Border.Thickness = new Thickness (1, 1, 1, 1);
+ Border.LineStyle = LineStyle.Single;
+ ColorScheme = Colors.ColorSchemes ["Menu"];
}
// Menuv2 arranges the items horizontally.
@@ -51,12 +69,30 @@ public override View Add (View view)
if (view is Shortcut shortcut)
{
shortcut.CanFocus = true;
- shortcut.KeyBindingScope = KeyBindingScope.Application;
shortcut.Orientation = Orientation.Vertical;
+ shortcut.HighlightStyle |= HighlightStyle.Hover;
// TODO: not happy about using AlignmentModes for this. Too implied.
// TODO: instead, add a property (a style enum?) to Shortcut to control this
//shortcut.AlignmentModes = AlignmentModes.EndToStart;
+
+ shortcut.Accepting += ShortcutOnAccept;
+
+ void ShortcutOnAccept (object sender, CommandEventArgs e)
+ {
+ if (Arrangement.HasFlag (ViewArrangement.Overlapped) && Visible)
+ {
+ Visible = false;
+ e.Cancel = true;
+
+ return;
+ }
+
+ //if (!e.Handled)
+ //{
+ // RaiseAcceptEvent ();
+ //}
+ }
}
return view;
diff --git a/Terminal.Gui/Views/MessageBox.cs b/Terminal.Gui/Views/MessageBox.cs
index 0fbb09e7a2..f36e41ace1 100644
--- a/Terminal.Gui/Views/MessageBox.cs
+++ b/Terminal.Gui/Views/MessageBox.cs
@@ -338,6 +338,7 @@ params string [] buttons
// Create button array for Dialog
var count = 0;
List
///
- ///
+ /// The text to display for the command.
///
- ///
- public Shortcut (Key key, string commandText, Action action, string helpText = null)
+ /// The help text to display.
+ public Shortcut (Key key, string? commandText, Action? action, string? helpText = null)
{
Id = "_shortcut";
- HighlightStyle = HighlightStyle.Pressed;
- Highlight += Shortcut_Highlight;
+
+ HighlightStyle = HighlightStyle.None;
CanFocus = true;
Width = GetWidthDimAuto ();
Height = Dim.Auto (DimAutoStyle.Content, 1);
@@ -63,48 +103,36 @@ public Shortcut (Key key, string commandText, Action action, string helpText = n
_orientationHelper.OrientationChanging += (sender, e) => OrientationChanging?.Invoke (this, e);
_orientationHelper.OrientationChanged += (sender, e) => OrientationChanged?.Invoke (this, e);
- AddCommand (Command.HotKey, ctx => OnAccept (ctx));
- AddCommand (Command.Accept, ctx => OnAccept (ctx));
- AddCommand (Command.Select, ctx => OnSelect (ctx));
- KeyBindings.Add (KeyCode.Enter, Command.Accept);
- KeyBindings.Add (KeyCode.Space, Command.Select);
+ AddCommands ();
TitleChanged += Shortcut_TitleChanged; // This needs to be set before CommandView is set
CommandView = new ()
{
Width = Dim.Auto (),
- Height = Dim.Auto (DimAutoStyle.Auto, minimumContentDim: 1)
+ Height = Dim.Auto (DimAutoStyle.Auto, 1)
};
HelpView.Id = "_helpView";
HelpView.CanFocus = false;
- HelpView.Text = helpText;
+ HelpView.Text = helpText ?? string.Empty;
Add (HelpView);
KeyView.Id = "_keyView";
KeyView.CanFocus = false;
Add (KeyView);
- // If the user clicks anywhere on the Shortcut, other than the CommandView, invoke the Command
- MouseClick += Shortcut_MouseClick;
- HelpView.MouseClick += Subview_MouseClick;
- KeyView.MouseClick += Subview_MouseClick;
LayoutStarted += OnLayoutStarted;
Initialized += OnInitialized;
- if (key is null)
- {
- key = Key.Empty;
- }
-
+ key ??= Key.Empty;
Key = key;
- Title = commandText;
+ Title = commandText ?? string.Empty;
Action = action;
return;
- void OnInitialized (object sender, EventArgs e)
+ void OnInitialized (object? sender, EventArgs e)
{
SuperViewRendersLineCanvas = true;
Border.Settings &= ~BorderSettings.Title;
@@ -131,30 +159,22 @@ Dim GetWidthDimAuto ()
return Dim.Auto (
DimAutoStyle.Content,
Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)),
- Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)));
+ Dim.Func (() => PosAlign.CalculateMinDimension (0, Subviews, Dimension.Width)))!;
}
}
- ///
- /// Creates a new instance of .
- ///
- public Shortcut () : this (Key.Empty, string.Empty, null) { }
-
- private readonly OrientationHelper _orientationHelper;
-
private AlignmentModes _alignmentModes = AlignmentModes.StartToEnd | AlignmentModes.IgnoreFirstOrLast;
// This is used to calculate the minimum width of the Shortcut when the width is NOT Dim.Auto
private int? _minimumDimAutoWidth;
- private Color? _savedForeColor;
-
///
- public bool EnableForDesign ()
+ protected override bool OnHighlight (CancelEventArgs args)
{
- Title = "_Shortcut";
- HelpText = "Shortcut help";
- Key = Key.F1;
+ if (args.NewValue.HasFlag (HighlightStyle.Hover))
+ {
+ HasFocus = true;
+ }
return true;
}
@@ -180,30 +200,6 @@ public AlignmentModes AlignmentModes
}
}
- ///
- protected override void Dispose (bool disposing)
- {
- if (disposing)
- {
- if (CommandView?.IsAdded == false)
- {
- CommandView.Dispose ();
- }
-
- if (HelpView?.IsAdded == false)
- {
- HelpView.Dispose ();
- }
-
- if (KeyView?.IsAdded == false)
- {
- KeyView.Dispose ();
- }
- }
-
- base.Dispose (disposing);
- }
-
// When one of the subviews is "empty" we don't want to show it. So we
// Use Add/Remove. We need to be careful to add them in the right order
// so Pos.Align works correctly.
@@ -238,7 +234,7 @@ private Thickness GetMarginThickness ()
}
// When layout starts, we need to adjust the layout of the HelpView and KeyView
- private void OnLayoutStarted (object sender, LayoutEventArgs e)
+ private void OnLayoutStarted (object? sender, LayoutEventArgs e)
{
if (Width is DimAuto widthAuto)
{
@@ -306,77 +302,86 @@ private void OnLayoutStarted (object sender, LayoutEventArgs e)
else
{
// Reset to default
- //SetCommandViewDefaultLayout();
SetHelpViewDefaultLayout ();
-
- //SetKeyViewDefaultLayout ();
}
}
}
- private bool? OnSelect (CommandContext ctx)
- {
- if (CommandView.GetSupportedCommands ().Contains (Command.Select))
- {
- return CommandView.InvokeCommand (Command.Select, ctx.Key, ctx.KeyBinding);
- }
- return false;
+ #region Accept/Select/HotKey Command Handling
+
+ private readonly View? _targetView; // If set, _command will be invoked
+
+ private readonly Command _command; // Used when _targetView is set
+
+ private void AddCommands ()
+ {
+ // Accept (Enter key) -
+ AddCommand (Command.Accept, DispatchCommand);
+ // Hotkey -
+ AddCommand (Command.HotKey, DispatchCommand);
+ // Select (Space key or click) -
+ AddCommand (Command.Select, DispatchCommand);
}
- private void Shortcut_Highlight (object sender, CancelEventArgs e)
+ private bool? DispatchCommand (CommandContext ctx)
{
- if (e.CurrentValue.HasFlag (HighlightStyle.Pressed))
+ if (ctx.Data != this)
{
- if (!_savedForeColor.HasValue)
- {
- _savedForeColor = base.ColorScheme.Normal.Foreground;
- }
-
- var cs = new ColorScheme (base.ColorScheme)
- {
- Normal = new (ColorScheme.Normal.Foreground.GetHighlightColor (), base.ColorScheme.Normal.Background)
- };
- base.ColorScheme = cs;
+ // Invoke Select on the command view to cause it to change state if it wants to
+ // If this causes CommandView to raise Accept, we eat it
+ ctx.Data = this;
+ CommandView.InvokeCommand (Command.Select, ctx);
}
- if (e.CurrentValue == HighlightStyle.None && _savedForeColor.HasValue)
+ if (RaiseSelecting (ctx) is true)
{
- var cs = new ColorScheme (base.ColorScheme)
- {
- Normal = new (_savedForeColor.Value, base.ColorScheme.Normal.Background)
- };
- base.ColorScheme = cs;
+ return true;
}
- SuperView?.SetNeedsDisplay ();
- e.Cancel = true;
- }
+ // The default HotKey handler sets Focus
+ SetFocus ();
- private void Shortcut_MouseClick (object sender, MouseEventEventArgs e)
- {
- // When the Shortcut is clicked, we want to invoke the Command and Set focus
- var view = sender as View;
+ var cancel = false;
+
+ cancel = RaiseAccepting (ctx) is true;
- if (!e.Handled)
+ if (cancel)
{
- // If the subview (likely CommandView) didn't handle the mouse click, invoke the command.
- e.Handled = InvokeCommand (Command.Accept) == true;
+ return true;
}
- if (CanFocus)
+ if (Action is { })
{
- SetFocus ();
+ Action.Invoke ();
+
+ // Assume if there's a subscriber to Action, it's handled.
+ cancel = true;
}
- }
- private void Subview_MouseClick (object sender, MouseEventEventArgs e)
- {
- // TODO: Remove. This does nothing.
+ if (_targetView is { })
+ {
+ _targetView.InvokeCommand (_command);
+ }
+
+ return cancel;
}
+ ///
+ /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
+ /// mouse.
+ ///
+ ///
+ /// Note, the event is fired first, and if cancelled, the event will not be invoked.
+ ///
+ public Action? Action { get; set; }
+
+ #endregion Accept/Select/HotKey Command Handling
+
#region IOrientation members
+ private readonly OrientationHelper _orientationHelper;
+
///
/// Gets or sets the for this . The default is
/// .
@@ -397,10 +402,10 @@ public Orientation Orientation
}
///
- public event EventHandler> OrientationChanging;
+ public event EventHandler>? OrientationChanging;
///
- public event EventHandler> OrientationChanged;
+ public event EventHandler>? OrientationChanged;
/// Called when has changed.
///
@@ -419,6 +424,7 @@ public void OnOrientationChanged (Orientation newOrientation)
///
/// Gets or sets the View that displays the command text and hotkey.
///
+ ///
///
///
/// By default, the of the is displayed as the Shortcut's
@@ -463,17 +469,20 @@ public View CommandView
get => _commandView;
set
{
+ ArgumentNullException.ThrowIfNull (value);
+
if (value == null)
{
throw new ArgumentNullException ();
}
- if (_commandView is { })
- {
- Remove (_commandView);
- _commandView?.Dispose ();
- }
+ // Clean up old
+ _commandView.Selecting -= CommandViewOnSelecting;
+ _commandView.Accepting -= CommandViewOnAccepted;
+ Remove (_commandView);
+ _commandView?.Dispose ();
+ // Set new
_commandView = value;
_commandView.Id = "_commandView";
@@ -485,7 +494,7 @@ public View CommandView
{
if (e.NewKey != Key.Empty)
{
- // Add it
+ // Add it
AddKeyBindingsForHotKey (e.OldKey, e.NewKey);
}
};
@@ -494,11 +503,34 @@ public View CommandView
Title = _commandView.Text;
+ _commandView.Selecting += CommandViewOnSelecting;
+
+ _commandView.Accepting += CommandViewOnAccepted;
+
SetCommandViewDefaultLayout ();
SetHelpViewDefaultLayout ();
SetKeyViewDefaultLayout ();
ShowHide ();
- UpdateKeyBinding (Key.Empty);
+ UpdateKeyBindings (Key.Empty);
+
+ return;
+
+ void CommandViewOnAccepted (object? sender, CommandEventArgs e)
+ {
+ // Always eat CommandView.Accept
+ e.Cancel = true;
+ }
+
+ void CommandViewOnSelecting (object? sender, CommandEventArgs e)
+ {
+ if (e.Context.Data != this)
+ {
+ // Forward command to ourselves
+ InvokeCommand (Command.Select, new (Command.Select, null, null, this));
+ }
+
+ e.Cancel = true;
+ }
}
}
@@ -507,9 +539,10 @@ private void SetCommandViewDefaultLayout ()
CommandView.Margin.Thickness = GetMarginThickness ();
CommandView.X = Pos.Align (Alignment.End, AlignmentModes);
CommandView.Y = 0; //Pos.Center ();
+ HelpView.HighlightStyle = HighlightStyle.None;
}
- private void Shortcut_TitleChanged (object sender, EventArgs e)
+ private void Shortcut_TitleChanged (object? sender, EventArgs e)
{
// If the Title changes, update the CommandView text.
// This is a helper to make it easier to set the CommandView text.
@@ -536,6 +569,7 @@ private void SetHelpViewDefaultLayout ()
HelpView.Visible = true;
HelpView.VerticalTextAlignment = Alignment.Center;
+ HelpView.HighlightStyle = HighlightStyle.None;
}
///
@@ -544,14 +578,11 @@ private void SetHelpViewDefaultLayout ()
///
public override string Text
{
- get => HelpView?.Text;
+ get => HelpView.Text;
set
{
- if (HelpView is { })
- {
- HelpView.Text = value;
- ShowHide ();
- }
+ HelpView.Text = value;
+ ShowHide ();
}
}
@@ -560,14 +591,11 @@ public override string Text
///
public string HelpText
{
- get => HelpView?.Text;
+ get => HelpView.Text;
set
{
- if (HelpView is { })
- {
- HelpView.Text = value;
- ShowHide ();
- }
+ HelpView.Text = value;
+ ShowHide ();
}
}
@@ -585,15 +613,12 @@ public Key Key
get => _key;
set
{
- if (value == null)
- {
- throw new ArgumentNullException ();
- }
+ ArgumentNullException.ThrowIfNull (value);
Key oldKey = _key;
_key = value;
- UpdateKeyBinding (oldKey);
+ UpdateKeyBindings (oldKey);
KeyView.Text = Key == Key.Empty ? string.Empty : $"{Key}";
ShowHide ();
@@ -627,7 +652,7 @@ public KeyBindingScope KeyBindingScope
_keyBindingScope = value;
- UpdateKeyBinding (Key.Empty);
+ UpdateKeyBindings (Key.Empty);
}
}
@@ -667,7 +692,7 @@ private void SetKeyViewDefaultLayout ()
{
KeyView.Margin.Thickness = GetMarginThickness ();
KeyView.X = Pos.Align (Alignment.End, AlignmentModes);
- KeyView.Y = 0; //Pos.Center ();
+ KeyView.Y = 0;
KeyView.Width = Dim.Auto (DimAutoStyle.Text, Dim.Func (GetMinimumKeyViewSize));
KeyView.Height = CommandView?.Visible == true ? Dim.Height (CommandView) : 1;
@@ -677,16 +702,13 @@ private void SetKeyViewDefaultLayout ()
KeyView.TextAlignment = Alignment.End;
KeyView.VerticalTextAlignment = Alignment.Center;
KeyView.KeyBindings.Clear ();
+ HelpView.HighlightStyle = HighlightStyle.None;
}
- private void UpdateKeyBinding (Key oldKey)
+ private void UpdateKeyBindings (Key oldKey)
{
- if (Key != null && Key.IsValid)
+ if (Key.IsValid)
{
- // Disable the command view key bindings
- CommandView.KeyBindings.Remove (Key);
- CommandView.KeyBindings.Remove (CommandView.HotKey);
-
if (KeyBindingScope.FastHasFlags (KeyBindingScope.Application))
{
if (oldKey != Key.Empty)
@@ -695,7 +717,7 @@ private void UpdateKeyBinding (Key oldKey)
}
Application.KeyBindings.Remove (Key);
- Application.KeyBindings.Add (Key, this, Command.Accept);
+ Application.KeyBindings.Add (Key, this, Command.HotKey);
}
else
{
@@ -705,93 +727,17 @@ private void UpdateKeyBinding (Key oldKey)
}
KeyBindings.Remove (Key);
- KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.Accept);
+ KeyBindings.Add (Key, KeyBindingScope | KeyBindingScope.HotKey, Command.HotKey);
}
}
}
#endregion Key
- #region Accept Handling
-
- ///
- /// Called when the command is received. This
- /// occurs
- /// - if the user clicks anywhere on the shortcut with the mouse
- /// - if the user presses Key
- /// - if the user presses the HotKey specified by CommandView
- /// - if HasFocus and the user presses Space or Enter (or any other key bound to Command.Accept).
- ///
- protected bool? OnAccept (CommandContext ctx)
- {
- var cancel = false;
-
- switch (ctx.KeyBinding?.Scope)
- {
- case KeyBindingScope.Application:
- cancel = base.OnAccept () == true;
-
- break;
-
- case KeyBindingScope.Focused:
- base.OnAccept ();
-
- // cancel if we're focused
- cancel = true;
-
- break;
-
- case KeyBindingScope.HotKey:
- //if (!CanBeVisible(this))
- //{
- // return true;
- //}
- cancel = base.OnAccept () == true;
-
- if (CanFocus)
- {
- SetFocus ();
- cancel = true;
- }
-
- break;
-
- default:
- // Mouse
- cancel = base.OnAccept () == true;
-
- break;
- }
-
- CommandView.InvokeCommand (Command.Accept, ctx.Key, ctx.KeyBinding);
-
- if (Action is { })
- {
- Action.Invoke ();
-
- // Assume if there's a subscriber to Action, it's handled.
- cancel = true;
- }
-
- return cancel;
- }
-
- ///
- /// Gets or sets the action to be invoked when the shortcut key is pressed or the shortcut is clicked on with the
- /// mouse.
- ///
- ///
- /// Note, the event is fired first, and if cancelled, the event will not be invoked.
- ///
- [CanBeNull]
- public Action Action { get; set; }
-
- #endregion Accept Handling
-
#region Focus
///
- public override ColorScheme ColorScheme
+ public override ColorScheme? ColorScheme
{
get => base.ColorScheme;
set
@@ -803,12 +749,15 @@ public override ColorScheme ColorScheme
///
///
- internal void SetColors ()
+ internal void SetColors (bool highlight = false)
{
// Border should match superview.
- Border.ColorScheme = SuperView?.ColorScheme;
+ if (Border is { })
+ {
+ Border.ColorScheme = SuperView?.ColorScheme;
+ }
- if (HasFocus)
+ if (HasFocus || highlight)
{
base.ColorScheme ??= new (Attribute.Default);
@@ -839,10 +788,44 @@ internal void SetColors ()
}
///
- protected override void OnHasFocusChanged (bool newHasFocus, View previousFocusedView, View view)
+ protected override void OnHasFocusChanged (bool newHasFocus, View? previousFocusedView, View? view) { SetColors (); }
+
+ #endregion Focus
+
+ ///
+ public bool EnableForDesign ()
{
- SetColors ();
+ Title = "_Shortcut";
+ HelpText = "Shortcut help";
+ Key = Key.F1;
+
+ return true;
}
- #endregion Focus
+
+ ///
+ protected override void Dispose (bool disposing)
+ {
+ if (disposing)
+ {
+ TitleChanged -= Shortcut_TitleChanged;
+
+ if (CommandView?.IsAdded == false)
+ {
+ CommandView.Dispose ();
+ }
+
+ if (HelpView?.IsAdded == false)
+ {
+ HelpView.Dispose ();
+ }
+
+ if (KeyView?.IsAdded == false)
+ {
+ KeyView.Dispose ();
+ }
+ }
+
+ base.Dispose (disposing);
+ }
}
diff --git a/Terminal.Gui/Views/Slider.cs b/Terminal.Gui/Views/Slider.cs
index 29c4692b7c..4c331251b6 100644
--- a/Terminal.Gui/Views/Slider.cs
+++ b/Terminal.Gui/Views/Slider.cs
@@ -848,7 +848,7 @@ private void DrawSlider ()
if (IsInitialized)
{
- normalAttr = ColorScheme?.Normal ?? Application.Current.ColorScheme.Normal;
+ normalAttr = ColorScheme?.Normal ?? Application.Top.ColorScheme.Normal;
setAttr = Style.SetChar.Attribute ?? ColorScheme!.HotNormal;
}
@@ -1380,6 +1380,8 @@ protected internal override bool OnMouseEvent (MouseEvent mouseEvent)
SetNeedsDisplay ();
mouseEvent.Handled = true;
+
+ // BUGBUG: OnMouseClick is/should be internal.
return OnMouseClick (new (mouseEvent));
}
@@ -1413,15 +1415,15 @@ Point ClampMovePosition (Point position)
private void SetCommands ()
{
AddCommand (Command.Right, () => MovePlus ());
- AddCommand (Command.LineDown, () => MovePlus ());
+ AddCommand (Command.Down, () => MovePlus ());
AddCommand (Command.Left, () => MoveMinus ());
- AddCommand (Command.LineUp, () => MoveMinus ());
- AddCommand (Command.LeftHome, () => MoveStart ());
+ AddCommand (Command.Up, () => MoveMinus ());
+ AddCommand (Command.LeftStart, () => MoveStart ());
AddCommand (Command.RightEnd, () => MoveEnd ());
AddCommand (Command.RightExtend, () => ExtendPlus ());
AddCommand (Command.LeftExtend, () => ExtendMinus ());
AddCommand (Command.Select, () => Select ());
- AddCommand (Command.Accept, () => Accept ());
+ AddCommand (Command.Accept, (ctx) => Accept (ctx));
SetKeyBindings ();
}
@@ -1444,9 +1446,9 @@ private void SetKeyBindings ()
else
{
KeyBindings.Remove (Key.CursorRight);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
KeyBindings.Remove (Key.CursorLeft);
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
KeyBindings.Remove (Key.CursorRight.WithCtrl);
KeyBindings.Add (Key.CursorDown.WithCtrl, Command.RightExtend);
@@ -1455,7 +1457,7 @@ private void SetKeyBindings ()
}
KeyBindings.Remove (Key.Home);
- KeyBindings.Add (Key.Home, Command.LeftHome);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
KeyBindings.Remove (Key.End);
KeyBindings.Add (Key.End, Command.RightEnd);
KeyBindings.Remove (Key.Enter);
@@ -1466,8 +1468,41 @@ private void SetKeyBindings ()
private Dictionary> GetSetOptionDictionary () { return _setOptions.ToDictionary (e => e, e => _options [e]); }
- private void SetFocusedOption ()
+ ///
+ /// Sets or unsets based on .
+ ///
+ /// The option to change.
+ /// If , sets the option. Unsets it otherwise.
+ public void ChangeOption (int optionIndex, bool set)
+ {
+ if (set)
+ {
+ if (!_setOptions.Contains (optionIndex))
+ {
+ _setOptions.Add (optionIndex);
+ _options [optionIndex].OnSet ();
+ }
+ }
+ else
+ {
+ if (_setOptions.Contains (optionIndex))
+ {
+ _setOptions.Remove (optionIndex);
+ _options [optionIndex].OnUnSet ();
+ }
+ }
+
+ // Raise slider changed event.
+ OnOptionsChanged ();
+ }
+
+ private bool SetFocusedOption ()
{
+ if (_options.Count == 0)
+ {
+ return false;
+ }
+ bool changed = false;
switch (_config._type)
{
case SliderType.Single:
@@ -1500,6 +1535,7 @@ private void SetFocusedOption ()
// Raise slider changed event.
OnOptionsChanged ();
+ changed = true;
break;
case SliderType.Multiple:
@@ -1520,6 +1556,7 @@ private void SetFocusedOption ()
}
OnOptionsChanged ();
+ changed = true;
break;
@@ -1653,11 +1690,14 @@ private void SetFocusedOption ()
// Raise Slider Option Changed Event.
OnOptionsChanged ();
+ changed = true;
break;
default:
throw new ArgumentOutOfRangeException (_config._type.ToString ());
}
+
+ return changed;
}
internal bool ExtendPlus ()
@@ -1742,16 +1782,14 @@ internal bool ExtendMinus ()
internal bool Select ()
{
- SetFocusedOption ();
-
- return true;
+ return SetFocusedOption ();
}
- internal new bool Accept ()
+ internal bool Accept (CommandContext ctx)
{
SetFocusedOption ();
- return OnAccept () == true;
+ return RaiseAccepting (ctx) == true;
}
internal bool MovePlus ()
diff --git a/Terminal.Gui/Views/StatusBar.cs b/Terminal.Gui/Views/StatusBar.cs
index 83675077ea..975d2c7ad4 100644
--- a/Terminal.Gui/Views/StatusBar.cs
+++ b/Terminal.Gui/Views/StatusBar.cs
@@ -113,14 +113,14 @@ bool IDesignable.EnableForDesign ()
Text = "I'll Hide",
// Visible = false
};
- button1.Accept += Button_Clicked;
+ button1.Accepting += Button_Clicked;
Add (button1);
- shortcut.Accept += (s, e) =>
+ shortcut.Accepting += (s, e) =>
{
button1.Visible = !button1.Visible;
button1.Enabled = button1.Visible;
- e.Handled = false;
+ e.Cancel = false;
};
Add (new Label
@@ -134,7 +134,7 @@ bool IDesignable.EnableForDesign ()
{
Text = "Or me!",
};
- button2.Accept += (s, e) => Application.RequestStop ();
+ button2.Accepting += (s, e) => Application.RequestStop ();
Add (button2);
diff --git a/Terminal.Gui/Views/Tab.cs b/Terminal.Gui/Views/Tab.cs
index 96b8fa50ba..eb42a59b99 100644
--- a/Terminal.Gui/Views/Tab.cs
+++ b/Terminal.Gui/Views/Tab.cs
@@ -18,7 +18,11 @@ public Tab ()
public string DisplayText
{
get => _displayText ?? "Unnamed";
- set => _displayText = value;
+ set
+ {
+ _displayText = value;
+ SetNeedsDisplay ();
+ }
}
/// The control to display when the tab is selected.
diff --git a/Terminal.Gui/Views/TabView.cs b/Terminal.Gui/Views/TabView.cs
index e88ec14bd3..3e63dde536 100644
--- a/Terminal.Gui/Views/TabView.cs
+++ b/Terminal.Gui/Views/TabView.cs
@@ -44,7 +44,7 @@ public TabView ()
AddCommand (Command.Right, () => SwitchTabBy (1));
AddCommand (
- Command.LeftHome,
+ Command.LeftStart,
() =>
{
TabScrollOffset = 0;
@@ -90,7 +90,7 @@ public TabView ()
// Default keybindings for this view
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.CursorRight, Command.Right);
- KeyBindings.Add (Key.Home, Command.LeftHome);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
KeyBindings.Add (Key.End, Command.RightEnd);
KeyBindings.Add (Key.PageDown, Command.PageDown);
KeyBindings.Add (Key.PageUp, Command.PageUp);
diff --git a/Terminal.Gui/Views/TableView/TableView.cs b/Terminal.Gui/Views/TableView/TableView.cs
index ac9ea11134..1717b35d68 100644
--- a/Terminal.Gui/Views/TableView/TableView.cs
+++ b/Terminal.Gui/Views/TableView/TableView.cs
@@ -61,11 +61,11 @@ public TableView ()
() => ChangeSelectionByOffsetWithReturn (-1, 0));
AddCommand (
- Command.LineUp,
+ Command.Up,
() => ChangeSelectionByOffsetWithReturn (0, -1));
AddCommand (
- Command.LineDown,
+ Command.Down,
() => ChangeSelectionByOffsetWithReturn (0, 1));
AddCommand (
@@ -89,7 +89,7 @@ public TableView ()
);
AddCommand (
- Command.LeftHome,
+ Command.LeftStart,
() =>
{
ChangeSelectionToStartOfRow (false);
@@ -109,7 +109,7 @@ public TableView ()
);
AddCommand (
- Command.TopHome,
+ Command.Start,
() =>
{
ChangeSelectionToStartOfTable (false);
@@ -119,7 +119,7 @@ public TableView ()
);
AddCommand (
- Command.BottomEnd,
+ Command.End,
() =>
{
ChangeSelectionToEndOfTable (false);
@@ -149,7 +149,7 @@ public TableView ()
);
AddCommand (
- Command.LineUpExtend,
+ Command.UpExtend,
() =>
{
ChangeSelectionByOffset (0, -1, true);
@@ -159,7 +159,7 @@ public TableView ()
);
AddCommand (
- Command.LineDownExtend,
+ Command.DownExtend,
() =>
{
ChangeSelectionByOffset (0, 1, true);
@@ -189,7 +189,7 @@ public TableView ()
);
AddCommand (
- Command.LeftHomeExtend,
+ Command.LeftStartExtend,
() =>
{
ChangeSelectionToStartOfRow (true);
@@ -209,7 +209,7 @@ public TableView ()
);
AddCommand (
- Command.TopHomeExtend,
+ Command.StartExtend,
() =>
{
ChangeSelectionToStartOfTable (true);
@@ -219,7 +219,7 @@ public TableView ()
);
AddCommand (
- Command.BottomEndExtend,
+ Command.EndExtend,
() =>
{
ChangeSelectionToEndOfTable (true);
@@ -238,53 +238,47 @@ public TableView ()
}
);
- AddCommand (
- Command.Accept,
- () =>
- {
- // BUGBUG: This should return false if the event is not handled
- OnCellActivated (new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow));
-
- return true;
- }
- );
+ AddCommand (Command.Accept, () => OnCellActivated (new CellActivatedEventArgs (Table, SelectedColumn, SelectedRow)));
AddCommand (
Command.Select, // was Command.ToggleChecked
- () =>
+ (ctx) =>
{
- ToggleCurrentCellSelection ();
+ if (ToggleCurrentCellSelection () is true)
+ {
+ return RaiseSelecting (ctx) is true;
+ }
- return true;
+ return false;
}
);
// Default keybindings for this view
KeyBindings.Add (Key.CursorLeft, Command.Left);
KeyBindings.Add (Key.CursorRight, Command.Right);
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
KeyBindings.Add (Key.PageUp, Command.PageUp);
KeyBindings.Add (Key.PageDown, Command.PageDown);
- KeyBindings.Add (Key.Home, Command.LeftHome);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
KeyBindings.Add (Key.End, Command.RightEnd);
- KeyBindings.Add (Key.Home.WithCtrl, Command.TopHome);
- KeyBindings.Add (Key.End.WithCtrl, Command.BottomEnd);
+ KeyBindings.Add (Key.Home.WithCtrl, Command.Start);
+ KeyBindings.Add (Key.End.WithCtrl, Command.End);
KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
KeyBindings.Add (Key.CursorRight.WithShift, Command.RightExtend);
- KeyBindings.Add (Key.CursorUp.WithShift, Command.LineUpExtend);
- KeyBindings.Add (Key.CursorDown.WithShift, Command.LineDownExtend);
+ KeyBindings.Add (Key.CursorUp.WithShift, Command.UpExtend);
+ KeyBindings.Add (Key.CursorDown.WithShift, Command.DownExtend);
KeyBindings.Add (Key.PageUp.WithShift, Command.PageUpExtend);
KeyBindings.Add (Key.PageDown.WithShift, Command.PageDownExtend);
- KeyBindings.Add (Key.Home.WithShift, Command.LeftHomeExtend);
+ KeyBindings.Add (Key.Home.WithShift, Command.LeftStartExtend);
KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
- KeyBindings.Add (Key.Home.WithCtrl.WithShift, Command.TopHomeExtend);
- KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.BottomEndExtend);
+ KeyBindings.Add (Key.Home.WithCtrl.WithShift, Command.StartExtend);
+ KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.EndExtend);
KeyBindings.Add (Key.A.WithCtrl, Command.SelectAll);
+ KeyBindings.Remove (CellActivationKey);
KeyBindings.Add (CellActivationKey, Command.Accept);
- KeyBindings.Add (Key.Space, Command.Select);
}
// TODO: Update to use Key instead of KeyCode
@@ -323,7 +317,11 @@ public int ColumnOffset
get => columnOffset;
//try to prevent this being set to an out of bounds column
- set => columnOffset = TableIsNullOrInvisible () ? 0 : Math.Max (0, Math.Min (Table.Columns - 1, value));
+ set
+ {
+ columnOffset = TableIsNullOrInvisible () ? 0 : Math.Max (0, Math.Min (Table.Columns - 1, value));
+ SetNeedsDisplay ();
+ }
}
/// True to select the entire row at once. False to select individual cells. Defaults to false
@@ -1246,7 +1244,12 @@ public void Update ()
/// Invokes the event
///
- protected virtual void OnCellActivated (CellActivatedEventArgs args) { CellActivated?.Invoke (this, args); }
+ /// if the CellActivated event was raised.
+ protected virtual bool OnCellActivated (CellActivatedEventArgs args)
+ {
+ CellActivated?.Invoke (this, args);
+ return CellActivated is { };
+ }
/// Invokes the event
///
@@ -2043,19 +2046,19 @@ private bool TableIsNullOrInvisible ()
);
}
- private void ToggleCurrentCellSelection ()
+ private bool? ToggleCurrentCellSelection ()
{
var e = new CellToggledEventArgs (Table, selectedColumn, selectedRow);
OnCellToggled (e);
if (e.Cancel)
{
- return;
+ return false;
}
if (!MultiSelect)
{
- return;
+ return null;
}
TableSelection [] regions = GetMultiSelectedRegionsContaining (selectedColumn, selectedRow).ToArray ();
@@ -2100,6 +2103,8 @@ private void ToggleCurrentCellSelection ()
);
}
}
+
+ return true;
}
///
diff --git a/Terminal.Gui/Views/TextField.cs b/Terminal.Gui/Views/TextField.cs
index da8f423472..7d9785c22c 100644
--- a/Terminal.Gui/Views/TextField.cs
+++ b/Terminal.Gui/Views/TextField.cs
@@ -1,4 +1,3 @@
-using System.Data;
using System.Globalization;
using Terminal.Gui.Resources;
@@ -25,14 +24,14 @@ public class TextField : View
///
public TextField ()
{
- _historyText = new HistoryText ();
+ _historyText = new ();
_isButtonReleased = true;
_selectedStart = -1;
- _text = new List ();
- CaptionColor = new Color (Color.DarkGray);
+ _text = new ();
+ CaptionColor = new (Color.DarkGray);
ReadOnly = false;
Autocomplete = new TextFieldAutocomplete ();
- Height = Dim.Auto (DimAutoStyle.Text, minimumContentDim: 1);
+ Height = Dim.Auto (DimAutoStyle.Text, 1);
CanFocus = true;
CursorVisibility = CursorVisibility.Default;
@@ -72,7 +71,7 @@ public TextField ()
);
AddCommand (
- Command.LeftHomeExtend,
+ Command.LeftStartExtend,
() =>
{
MoveHomeExtend ();
@@ -92,7 +91,7 @@ public TextField ()
);
AddCommand (
- Command.LeftHome,
+ Command.LeftStart,
() =>
{
MoveHome ();
@@ -316,7 +315,7 @@ public TextField ()
);
AddCommand (
- Command.ShowContextMenu,
+ Command.Context,
() =>
{
ShowContextMenu ();
@@ -325,10 +324,6 @@ public TextField ()
}
);
- // By Default pressing ENTER should be ignored (OnAccept will return false or null). Only cancel if the
- // event was fired and set Cancel = true.
- AddCommand (Command.Accept, () => OnAccept () == false);
-
// Default keybindings for this view
// We follow this as closely as possible: https://en.wikipedia.org/wiki/Table_of_keyboard_shortcuts
KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
@@ -336,17 +331,17 @@ public TextField ()
KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
- KeyBindings.Add (Key.Home.WithShift, Command.LeftHomeExtend);
- KeyBindings.Add (Key.Home.WithShift.WithCtrl, Command.LeftHomeExtend);
- KeyBindings.Add (Key.A.WithShift.WithCtrl, Command.LeftHomeExtend);
+ KeyBindings.Add (Key.Home.WithShift, Command.LeftStartExtend);
+ KeyBindings.Add (Key.Home.WithShift.WithCtrl, Command.LeftStartExtend);
+ KeyBindings.Add (Key.A.WithShift.WithCtrl, Command.LeftStartExtend);
KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
KeyBindings.Add (Key.End.WithShift.WithCtrl, Command.RightEndExtend);
KeyBindings.Add (Key.E.WithShift.WithCtrl, Command.RightEndExtend);
- KeyBindings.Add (Key.Home, Command.LeftHome);
- KeyBindings.Add (Key.Home.WithCtrl, Command.LeftHome);
- KeyBindings.Add (Key.A.WithCtrl, Command.LeftHome);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
+ KeyBindings.Add (Key.Home.WithCtrl, Command.LeftStart);
+ KeyBindings.Add (Key.A.WithCtrl, Command.LeftStart);
KeyBindings.Add (Key.CursorLeft.WithShift, Command.LeftExtend);
KeyBindings.Add (Key.CursorUp.WithShift, Command.LeftExtend);
@@ -405,13 +400,13 @@ public TextField ()
_currentCulture = Thread.CurrentThread.CurrentUICulture;
- ContextMenu = new ContextMenu { Host = this };
+ ContextMenu = new() { Host = this };
ContextMenu.KeyChanged += ContextMenu_KeyChanged;
- KeyBindings.Add (ContextMenu.Key, KeyBindingScope.HotKey, Command.ShowContextMenu);
- KeyBindings.Add (Key.Enter, Command.Accept);
- }
+ KeyBindings.Add (ContextMenu.Key, KeyBindingScope.HotKey, Command.Context);
+ KeyBindings.Remove (Key.Space);
+ }
///
/// Provides autocomplete context menu based on suggestions at the current cursor position. Configure
@@ -464,7 +459,7 @@ public virtual int CursorPosition
/// Indicates whatever the text was changed or not. if the text was changed
/// otherwise.
///
- public bool IsDirty => _historyText.IsDirty (Text);
+ public bool IsDirty => _historyText.IsDirty ([Cell.StringToCells (Text)]);
/// If set to true its not allow any changes in the text.
public bool ReadOnly { get; set; }
@@ -546,13 +541,13 @@ public string SelectedText
if (!Secret && !_historyText.IsFromHistory)
{
_historyText.Add (
- new List> { TextModel.ToRuneCellList (oldText) },
- new Point (_cursorPosition, 0)
+ new () { Cell.ToCellList (oldText) },
+ new (_cursorPosition, 0)
);
_historyText.Add (
- new List> { TextModel.ToRuneCells (_text) },
- new Point (_cursorPosition, 0),
+ new () { Cell.ToCells (_text) },
+ new (_cursorPosition, 0),
HistoryText.LineStatus.Replaced
);
}
@@ -594,7 +589,7 @@ public void ClearAllSelection ()
}
/// Allows clearing the items updating the original text.
- public void ClearHistoryChanges () { _historyText.Clear (Text); }
+ public void ClearHistoryChanges () { _historyText.Clear ([Cell.StringToCells (Text)]); }
/// Copy the selected text to the clipboard.
public virtual void Copy ()
@@ -648,8 +643,8 @@ public virtual void DeleteCharLeft (bool usePreTextChangedCursorPos)
}
_historyText.Add (
- new List> { TextModel.ToRuneCells (_text) },
- new Point (_cursorPosition, 0)
+ new () { Cell.ToCells (_text) },
+ new (_cursorPosition, 0)
);
if (SelectedLength == 0)
@@ -702,8 +697,8 @@ public virtual void DeleteCharRight ()
}
_historyText.Add (
- new List> { TextModel.ToRuneCells (_text) },
- new Point (_cursorPosition, 0)
+ new () { Cell.ToCells (_text) },
+ new (_cursorPosition, 0)
);
if (SelectedLength == 0)
@@ -728,17 +723,7 @@ public virtual void DeleteCharRight ()
}
///
- public override Attribute GetNormalColor ()
- {
- ColorScheme cs = ColorScheme;
-
- if (ColorScheme is null)
- {
- cs = new ColorScheme ();
- }
-
- return Enabled ? cs.Focus : cs.Disabled;
- }
+ public override Attribute GetNormalColor () { return GetFocusColor (); }
///
/// Inserts the given text at the current cursor position exactly as if the user had just
@@ -959,7 +944,7 @@ public override void OnDrawContent (Rectangle viewport)
int p = ScrollOffset;
var col = 0;
- int width = Frame.Width + OffSetBackground ();
+ int width = Viewport.Width + OffSetBackground ();
int tcount = _text.Count;
Attribute roc = GetReadOnlyColor ();
@@ -1050,8 +1035,6 @@ protected override void OnHasFocusChanged (bool newHasFocus, View previousFocuse
//if (SelectedLength != 0 && !(Application.MouseGrabView is MenuBar))
// ClearAllSelection ();
-
- return;
}
/// TODO: Flush out these docs
@@ -1147,11 +1130,12 @@ public virtual void Paste ()
}
int cols = _text [idx].GetColumns ();
- TextModel.SetCol (ref col, Frame.Width - 1, cols);
+ TextModel.SetCol (ref col, Viewport.Width - 1, cols);
}
- int pos = _cursorPosition - ScrollOffset + Math.Min (Frame.X, 0);
+ int pos = _cursorPosition - ScrollOffset + Math.Min (Viewport.X, 0);
Move (pos, 0);
+
return new Point (pos, 0);
}
@@ -1232,16 +1216,16 @@ private void Adjust ()
ScrollOffset = _cursorPosition;
need = true;
}
- else if (Frame.Width > 0
- && (ScrollOffset + _cursorPosition - (Frame.Width + offB) == 0
- || TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Frame.Width + offB))
+ else if (Viewport.Width > 0
+ && (ScrollOffset + _cursorPosition - (Viewport.Width + offB) == 0
+ || TextModel.DisplaySize (_text, ScrollOffset, _cursorPosition).size >= Viewport.Width + offB))
{
ScrollOffset = Math.Max (
TextModel.CalculateLeftColumn (
_text,
ScrollOffset,
_cursorPosition,
- Frame.Width + offB
+ Viewport.Width + offB
),
0
);
@@ -1260,73 +1244,70 @@ private void Adjust ()
private MenuBarItem BuildContextMenuBarItem ()
{
- return new MenuBarItem (
- new MenuItem []
- {
- new (
- Strings.ctxSelectAll,
- "",
- () => SelectAll (),
- null,
- null,
- (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)
- ),
- new (
- Strings.ctxDeleteAll,
- "",
- () => DeleteAll (),
- null,
- null,
- (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)
- ),
- new (
- Strings.ctxCopy,
- "",
- () => Copy (),
- null,
- null,
- (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)
- ),
- new (
- Strings.ctxCut,
- "",
- () => Cut (),
- null,
- null,
- (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)
- ),
- new (
- Strings.ctxPaste,
- "",
- () => Paste (),
- null,
- null,
- (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)
- ),
- new (
- Strings.ctxUndo,
- "",
- () => Undo (),
- null,
- null,
- (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)
- ),
- new (
- Strings.ctxRedo,
- "",
- () => Redo (),
- null,
- null,
- (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)
- )
- }
- );
+ return new (
+ new MenuItem []
+ {
+ new (
+ Strings.ctxSelectAll,
+ "",
+ () => SelectAll (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.SelectAll)
+ ),
+ new (
+ Strings.ctxDeleteAll,
+ "",
+ () => DeleteAll (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.DeleteAll)
+ ),
+ new (
+ Strings.ctxCopy,
+ "",
+ () => Copy (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.Copy)
+ ),
+ new (
+ Strings.ctxCut,
+ "",
+ () => Cut (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.Cut)
+ ),
+ new (
+ Strings.ctxPaste,
+ "",
+ () => Paste (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.Paste)
+ ),
+ new (
+ Strings.ctxUndo,
+ "",
+ () => Undo (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.Undo)
+ ),
+ new (
+ Strings.ctxRedo,
+ "",
+ () => Redo (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)
+ )
+ }
+ );
}
- private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e)
- {
- KeyBindings.ReplaceKey (e.OldKey.KeyCode, e.NewKey.KeyCode);
- }
+ private void ContextMenu_KeyChanged (object sender, KeyChangedEventArgs e) { KeyBindings.ReplaceKey (e.OldKey.KeyCode, e.NewKey.KeyCode); }
private List DeleteSelectedText ()
{
@@ -1349,16 +1330,16 @@ private List DeleteSelectedText ()
private void GenerateSuggestions ()
{
- List currentLine = TextModel.ToRuneCellList (Text);
+ List currentLine = Cell.ToCellList (Text);
int cursorPosition = Math.Min (CursorPosition, currentLine.Count);
- Autocomplete.Context = new AutocompleteContext (
- currentLine,
- cursorPosition,
- Autocomplete.Context != null
- ? Autocomplete.Context.Canceled
- : false
- );
+ Autocomplete.Context = new (
+ currentLine,
+ cursorPosition,
+ Autocomplete.Context != null
+ ? Autocomplete.Context.Canceled
+ : false
+ );
Autocomplete.GenerateSuggestions (
Autocomplete.Context
@@ -1379,15 +1360,15 @@ private Attribute GetReadOnlyColor ()
if (ColorScheme is null)
{
- cs = new ColorScheme ();
+ cs = new ();
}
if (cs.Disabled.Foreground == cs.Focus.Background)
{
- return new Attribute (cs.Focus.Foreground, cs.Focus.Background);
+ return new (cs.Focus.Foreground, cs.Focus.Background);
}
- return new Attribute (cs.Disabled.Foreground, cs.Focus.Background);
+ return new (cs.Disabled.Foreground, cs.Focus.Background);
}
private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemEventArgs obj)
@@ -1397,7 +1378,7 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE
return;
}
- Text = TextModel.ToString (obj?.Lines [obj.CursorPosition.Y]);
+ Text = Cell.ToString (obj?.Lines [obj.CursorPosition.Y]);
CursorPosition = obj.CursorPosition.X;
Adjust ();
}
@@ -1405,8 +1386,8 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE
private void InsertText (Key a, bool usePreTextChangedCursorPos)
{
_historyText.Add (
- new List> { TextModel.ToRuneCells (_text) },
- new Point (_cursorPosition, 0)
+ new () { Cell.ToCells (_text) },
+ new (_cursorPosition, 0)
);
List newText = _text;
@@ -1540,7 +1521,6 @@ private void MoveHomeExtend ()
private bool MoveLeft ()
{
-
if (_cursorPosition > 0)
{
ClearAllSelection ();
@@ -1679,13 +1659,10 @@ private int OffSetBackground ()
offB = SuperView.Frame.Right - Frame.Right - 1;
}
- return 0;//offB;
+ return 0; //offB;
}
- private int PositionCursor (MouseEvent ev)
- {
- return PositionCursor (TextModel.GetColFromX (_text, ScrollOffset, ev.Position.X), false);
- }
+ private int PositionCursor (MouseEvent ev) { return PositionCursor (TextModel.GetColFromX (_text, ScrollOffset, ev.Position.X), false); }
private int PositionCursor (int x, bool getX = true)
{
@@ -1779,7 +1756,6 @@ private void ProcessAutocomplete ()
private void DrawAutocomplete ()
{
-
if (SelectedLength > 0)
{
return;
@@ -1870,10 +1846,7 @@ private void TextField_Added (object sender, SuperViewChangedEventArgs e)
}
}
- private void TextField_Removed (object sender, SuperViewChangedEventArgs e)
- {
- Autocomplete.HostControl = null;
- }
+ private void TextField_Removed (object sender, SuperViewChangedEventArgs e) { Autocomplete.HostControl = null; }
private void TextField_Initialized (object sender, EventArgs e)
{
diff --git a/Terminal.Gui/Views/TextValidateField.cs b/Terminal.Gui/Views/TextValidateField.cs
index 7858dba595..a6c740d1b7 100644
--- a/Terminal.Gui/Views/TextValidateField.cs
+++ b/Terminal.Gui/Views/TextValidateField.cs
@@ -401,7 +401,7 @@ public TextValidateField ()
// Things this view knows how to do
AddCommand (
- Command.LeftHome,
+ Command.LeftStart,
() =>
{
HomeKeyHandler ();
@@ -461,7 +461,7 @@ public TextValidateField ()
);
// Default keybindings for this view
- KeyBindings.Add (Key.Home, Command.LeftHome);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
KeyBindings.Add (Key.End, Command.RightEnd);
KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
diff --git a/Terminal.Gui/Views/TextView.cs b/Terminal.Gui/Views/TextView.cs
index 44bfcdf5fc..c34e9220bd 100644
--- a/Terminal.Gui/Views/TextView.cs
+++ b/Terminal.Gui/Views/TextView.cs
@@ -9,44 +9,9 @@
namespace Terminal.Gui;
-///
-/// Represents a single row/column within the . Includes the glyph and the
-/// foreground/background colors.
-///
-[DebuggerDisplay ("{ColorSchemeDebuggerDisplay}")]
-public class RuneCell : IEquatable
-{
- /// The color sets to draw the glyph with.
- [JsonConverter (typeof (ColorSchemeJsonConverter))]
- public ColorScheme? ColorScheme { get; set; }
-
- /// The glyph to draw.
- [JsonConverter (typeof (RuneJsonConverter))]
- public Rune Rune { get; set; }
-
- private string ColorSchemeDebuggerDisplay => ToString ();
-
- /// Indicates whether the current object is equal to another object of the same type.
- /// An object to compare with this object.
- ///
- /// if the current object is equal to the parameter; otherwise,
- /// .
- ///
- public bool Equals (RuneCell? other) { return other is { } && Rune.Equals (other.Rune) && ColorScheme == other.ColorScheme; }
-
- /// Returns a string that represents the current object.
- /// A string that represents the current object.
- public override string ToString ()
- {
- string colorSchemeStr = ColorScheme?.ToString () ?? "null";
-
- return $"U+{Rune.Value:X4} '{Rune.ToString ()}'; {colorSchemeStr}";
- }
-}
-
internal class TextModel
{
- private List> _lines = new ();
+ private List> _lines = new ();
private (Point startPointToFind, Point currentPointToFind, bool found) _toFind;
/// The number of text lines in the model
@@ -56,8 +21,8 @@ internal class TextModel
/// Adds a line to the model at the specified position.
/// Line number where the line will be inserted.
- /// The line of text and color, as a List of RuneCell.
- public void AddLine (int pos, List cells) { _lines.Insert (pos, cells); }
+ /// The line of text and color, as a List of Cell.
+ public void AddLine (int pos, List cells) { _lines.Insert (pos, cells); }
public bool CloseFile ()
{
@@ -72,12 +37,12 @@ public bool CloseFile ()
return true;
}
- public List> GetAllLines () { return _lines; }
+ public List> GetAllLines () { return _lines; }
/// Returns the specified line as a List of Rune
/// The line.
/// Line number to retrieve.
- public List GetLine (int line)
+ public List GetLine (int line)
{
if (_lines.Count > 0)
{
@@ -105,7 +70,7 @@ public int GetMaxVisibleLine (int first, int last, int tabWidth)
for (int i = first; i < last; i++)
{
- List line = GetLine (i);
+ List line = GetLine (i);
int tabSum = line.Sum (c => c.Rune.Value == '\t' ? Math.Max (tabWidth - 1, 0) : 0);
int l = line.Count + tabSum;
@@ -132,17 +97,17 @@ public bool LoadFile (string file)
}
}
- public void LoadListRuneCells (List> cellsList, ColorScheme? colorScheme)
+ public void LoadListCells (List> cellsList, Attribute? attribute)
{
_lines = cellsList;
- SetColorSchemes (colorScheme);
+ SetAttributes (attribute);
OnLinesLoaded ();
}
- public void LoadRuneCells (List cells, ColorScheme? colorScheme)
+ public void LoadCells (List cells, Attribute? attribute)
{
- _lines = ToRuneCells (cells);
- SetColorSchemes (colorScheme);
+ _lines = Cell.ToCells ((List)cells);
+ SetAttributes (attribute);
OnLinesLoaded ();
}
@@ -191,7 +156,7 @@ public void LoadStream (Stream input)
public void LoadString (string content)
{
- _lines = StringToLinesOfRuneCells (content);
+ _lines = Cell.StringToLinesOfCells (content);
OnLinesLoaded ();
}
@@ -211,11 +176,11 @@ public void RemoveLine (int pos)
}
}
- public void ReplaceLine (int pos, List runes)
+ public void ReplaceLine (int pos, List runes)
{
if (_lines.Count > 0 && pos < _lines.Count)
{
- _lines [pos] = new (runes);
+ _lines [pos] = [..runes];
}
else if (_lines.Count == 0 || (_lines.Count > 0 && pos >= _lines.Count))
{
@@ -223,31 +188,7 @@ public void ReplaceLine (int pos, List runes)
}
}
- // Splits a string into a List that contains a List for each line
- public static List> StringToLinesOfRuneCells (string content, ColorScheme? colorScheme = null)
- {
- List cells = content.EnumerateRunes ()
- .Select (x => new RuneCell { Rune = x, ColorScheme = colorScheme })
- .ToList ();
- return SplitNewLines (cells);
- }
-
- /// Converts the string into a .
- /// The string to convert.
- /// The to use.
- ///
- public static List ToRuneCellList (string str, ColorScheme? colorScheme = null)
- {
- List cells = new ();
-
- foreach (Rune rune in str.EnumerateRunes ())
- {
- cells.Add (new () { Rune = rune, ColorScheme = colorScheme });
- }
-
- return cells;
- }
public override string ToString ()
{
@@ -255,7 +196,7 @@ public override string ToString ()
for (var i = 0; i < _lines.Count; i++)
{
- sb.Append (ToString (_lines [i]));
+ sb.Append (Cell.ToString (_lines [i]));
if (i + 1 < _lines.Count)
{
@@ -266,21 +207,6 @@ public override string ToString ()
return sb.ToString ();
}
- /// Converts a generic collection into a string.
- /// The enumerable cell to convert.
- ///
- public static string ToString (IEnumerable cells)
- {
- var str = string.Empty;
-
- foreach (RuneCell cell in cells)
- {
- str += cell.Rune.ToString ();
- }
-
- return str;
- }
-
public (int col, int row)? WordBackward (int fromCol, int fromRow)
{
if (fromRow == 0 && fromCol == 0)
@@ -293,12 +219,12 @@ public static string ToString (IEnumerable cells)
try
{
- RuneCell? cell = RuneAt (col, row);
+ Cell? cell = RuneAt (col, row);
Rune rune;
if (cell is { })
{
- rune = cell.Rune;
+ rune = cell.Value.Rune;
}
else
{
@@ -310,7 +236,7 @@ public static string ToString (IEnumerable cells)
if (col == 0 && row > 0)
{
row--;
- List line = GetLine (row);
+ List line = GetLine (row);
return (line.Count, row);
}
@@ -384,7 +310,7 @@ void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune)
return;
}
- List line = GetLine (nRow);
+ List line = GetLine (nRow);
if (nCol == 0
&& nRow == fromRow
@@ -443,7 +369,7 @@ void ProcMovePrev (ref int nCol, ref int nRow, Rune nRune)
try
{
- Rune rune = RuneAt (col, row).Rune;
+ Rune rune = RuneAt (col, row)!.Value.Rune;
RuneType runeType = GetRuneType (rune);
int lastValidCol = IsSameRuneType (rune, runeType) && (Rune.IsLetterOrDigit (rune) || Rune.IsPunctuation (rune) || Rune.IsSymbol (rune))
@@ -510,7 +436,7 @@ void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune)
return;
}
- List line = GetLine (nRow);
+ List line = GetLine (nRow);
if (nCol == line.Count
&& nRow == fromRow
@@ -550,11 +476,11 @@ void ProcMoveNext (ref int nCol, ref int nRow, Rune nRune)
}
}
- internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0)
+ internal static int CalculateLeftColumn (List t, int start, int end, int width, int tabWidth = 0)
{
List runes = new ();
- foreach (RuneCell cell in t)
+ foreach (Cell cell in t)
{
runes.Add (cell.Rune);
}
@@ -606,7 +532,7 @@ internal static int CalculateLeftColumn (List t, int start, int end, int w
}
internal static (int size, int length) DisplaySize (
- List t,
+ List t,
int start = -1,
int end = -1,
bool checkNextRune = true,
@@ -615,7 +541,7 @@ internal static (int size, int length) DisplaySize (
{
List runes = new ();
- foreach (RuneCell cell in t)
+ foreach (Cell cell in t)
{
runes.Add (cell.Rune);
}
@@ -776,11 +702,11 @@ internal Size GetDisplaySize ()
return foundPos;
}
- internal static int GetColFromX (List t, int start, int x, int tabWidth = 0)
+ internal static int GetColFromX (List t, int start, int x, int tabWidth = 0)
{
List runes = new ();
- foreach (RuneCell cell in t)
+ foreach (Cell cell in t)
{
runes.Add (cell.Rune);
}
@@ -829,7 +755,7 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth =
for (var i = 0; i < _lines.Count; i++)
{
- List x = _lines [i];
+ List x = _lines [i];
string txt = GetText (x);
string matchText = !matchCase ? text.ToUpper () : text;
int col = txt.IndexOf (matchText);
@@ -855,7 +781,7 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth =
found = true;
}
- _lines [i] = ToRuneCellList (ReplaceText (x, textToReplace!, matchText, col));
+ _lines [i] = Cell.ToCellList (ReplaceText (x, textToReplace!, matchText, col));
x = _lines [i];
txt = GetText (x);
pos = new (col, i);
@@ -871,9 +797,9 @@ internal static int GetColFromX (List t, int start, int x, int tabWidth =
}
}
- string GetText (List x)
+ string GetText (List x)
{
- string txt = ToString (x);
+ string txt = Cell.ToString (x);
if (!matchCase)
{
@@ -906,36 +832,10 @@ internal static bool SetCol (ref int col, int width, int cols)
return false;
}
- // Turns the string into cells, this does not split the
- // contents on a newline if it is present.
- internal static List StringToRuneCells (string str, ColorScheme? colorScheme = null)
- {
- List cells = new ();
-
- foreach (Rune rune in str.ToRunes ())
- {
- cells.Add (new () { Rune = rune, ColorScheme = colorScheme });
- }
-
- return cells;
- }
-
- internal static List ToRuneCells (IEnumerable runes, ColorScheme? colorScheme = null)
- {
- List cells = new ();
-
- foreach (Rune rune in runes)
- {
- cells.Add (new () { Rune = rune, ColorScheme = colorScheme });
- }
-
- return cells;
- }
-
private void Append (List line)
{
var str = StringExtensions.ToString (line.ToArray ());
- _lines.Add (StringToRuneCells (str));
+ _lines.Add (Cell.StringToCells (str));
}
private bool ApplyToFind ((Point current, bool found) foundPos)
@@ -971,8 +871,8 @@ Point start
{
for (int i = start.Y; i < linesCount; i++)
{
- List x = _lines [i];
- string txt = ToString (x);
+ List x = _lines [i];
+ string txt = Cell.ToString (x);
if (!matchCase)
{
@@ -1011,8 +911,8 @@ Point start
{
for (int i = linesCount; i >= 0; i--)
{
- List x = _lines [i];
- string txt = ToString (x);
+ List x = _lines [i];
+ string txt = Cell.ToString (x);
if (!matchCase)
{
@@ -1094,7 +994,7 @@ private bool MatchWholeWord (string source, string matchText, int index = 0)
private bool MoveNext (ref int col, ref int row, out Rune rune)
{
- List line = GetLine (row);
+ List line = GetLine (row);
if (col + 1 < line.Count)
{
@@ -1135,7 +1035,7 @@ private bool MoveNext (ref int col, ref int row, out Rune rune)
private bool MovePrev (ref int col, ref int row, out Rune rune)
{
- List line = GetLine (row);
+ List line = GetLine (row);
if (col > 0)
{
@@ -1173,9 +1073,9 @@ private bool MovePrev (ref int col, ref int row, out Rune rune)
private void OnLinesLoaded () { LinesLoaded?.Invoke (this, EventArgs.Empty); }
- private string ReplaceText (List source, string textToReplace, string matchText, int col)
+ private string ReplaceText (List source, string textToReplace, string matchText, int col)
{
- string origTxt = ToString (source);
+ string origTxt = Cell.ToString (source);
(_, int len) = DisplaySize (source, 0, col, false);
(_, int len2) = DisplaySize (source, col, col + matchText.Length, false);
(_, int len3) = DisplaySize (source, col + matchText.Length, origTxt.GetRuneCount (), false);
@@ -1183,72 +1083,31 @@ private string ReplaceText (List source, string textToReplace, string
return origTxt [..len] + textToReplace + origTxt.Substring (len + len2, len3);
}
- private RuneCell RuneAt (int col, int row)
+ private Cell? RuneAt (int col, int row)
{
- List line = GetLine (row);
+ List line = GetLine (row);
if (line.Count > 0)
{
return line [col > line.Count - 1 ? line.Count - 1 : col];
}
- return default (RuneCell)!;
+ return null;
}
- private void SetColorSchemes (ColorScheme? colorScheme)
+ private void SetAttributes (Attribute? attribute)
{
- foreach (List line in _lines)
+ foreach (List line in _lines)
{
- foreach (RuneCell cell in line)
- {
- cell.ColorScheme ??= colorScheme;
- }
- }
- }
-
- private static List> SplitNewLines (List cells)
- {
- List> lines = new ();
- int start = 0, i = 0;
- var hasCR = false;
-
- // ASCII code 13 = Carriage Return.
- // ASCII code 10 = Line Feed.
- for (; i < cells.Count; i++)
- {
- if (cells [i].Rune.Value == 13)
- {
- hasCR = true;
-
- continue;
- }
-
- if (cells [i].Rune.Value == 10)
+ for (var i = 0; i < line.Count; i++)
{
- if (i - start > 0)
- {
- lines.Add (cells.GetRange (start, hasCR ? i - 1 - start : i - start));
- }
- else
- {
- lines.Add (StringToRuneCells (string.Empty));
- }
-
- start = i + 1;
- hasCR = false;
+ Cell cell = line [i];
+ cell.Attribute ??= attribute;
+ line [i] = cell;
}
}
-
- if (i - start >= 0)
- {
- lines.Add (cells.GetRange (start, i - start));
- }
-
- return lines;
}
- private static List> ToRuneCells (List cells) { return SplitNewLines (cells); }
-
private enum RuneType
{
IsSymbol,
@@ -1266,16 +1125,17 @@ public enum LineStatus
Original,
Replaced,
Removed,
- Added
+ Added,
+ Attribute
}
- private readonly List _historyTextItems = new ();
+ private readonly List _historyTextItems = [];
private int _idxHistoryText = -1;
- private string? _originalText;
+ private readonly List> _originalCellsList = [];
public bool HasHistoryChanges => _idxHistoryText > -1;
public bool IsFromHistory { get; private set; }
- public void Add (List> lines, Point curPos, LineStatus lineStatus = LineStatus.Original)
+ public void Add (List> lines, Point curPos, LineStatus lineStatus = LineStatus.Original)
{
if (lineStatus == LineStatus.Original && _historyTextItems.Count > 0 && _historyTextItems.Last ().LineStatus == LineStatus.Original)
{
@@ -1306,15 +1166,51 @@ public void Add (List> lines, Point curPos, LineStatus lineStatus
public event EventHandler? ChangeText;
- public void Clear (string text)
+ public void Clear (List> cellsList)
{
_historyTextItems.Clear ();
_idxHistoryText = -1;
- _originalText = text;
+ _originalCellsList.Clear ();
+ // Save a copy of the original, not the reference
+ foreach (List cells in cellsList)
+ {
+ _originalCellsList.Add ([..cells]);
+ }
+
OnChangeText (null);
}
- public bool IsDirty (string text) { return _originalText != text; }
+ public bool IsDirty (List> cellsList)
+ {
+ if (cellsList.Count != _originalCellsList.Count)
+ {
+ return true;
+ }
+
+ for (var r = 0; r < cellsList.Count; r++)
+ {
+ List cells = cellsList [r];
+ List originalCells = _originalCellsList [r];
+
+ if (cells.Count != originalCells.Count)
+ {
+ return true;
+ }
+
+ for (var c = 0; c < cells.Count; c++)
+ {
+ Cell cell = cells [c];
+ Cell originalCell = originalCells [c];
+
+ if (!cell.Equals (originalCell))
+ {
+ return true;
+ }
+ }
+ }
+
+ return false;
+ }
public void Redo ()
{
@@ -1332,7 +1228,7 @@ public void Redo ()
}
}
- public void ReplaceLast (List> lines, Point curPos, LineStatus lineStatus)
+ public void ReplaceLast (List> lines, Point curPos, LineStatus lineStatus)
{
HistoryTextItemEventArgs? found = _historyTextItems.FindLast (x => x.LineStatus == lineStatus);
@@ -1368,7 +1264,8 @@ private void ProcessChanges (ref HistoryTextItemEventArgs historyTextItem)
if (_idxHistoryText - 1 > -1
&& (_historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Added
|| _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Removed
- || (historyTextItem.LineStatus == LineStatus.Replaced && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original)))
+ || (historyTextItem.LineStatus == LineStatus.Replaced && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original)
+ || (historyTextItem.LineStatus == LineStatus.Attribute && _historyTextItems [_idxHistoryText - 1].LineStatus == LineStatus.Original)))
{
_idxHistoryText--;
@@ -1487,9 +1384,9 @@ public void AddLine (int row, int col)
{
int modelRow = GetModelLineFromWrappedLines (row);
int modelCol = GetModelColFromWrappedLines (row, col);
- List line = GetCurrentLine (modelRow);
+ List line = GetCurrentLine (modelRow);
int restCount = line.Count - modelCol;
- List rest = line.GetRange (modelCol, restCount);
+ List rest = line.GetRange (modelCol, restCount);
line.RemoveRange (modelCol, restCount);
Model.AddLine (modelRow + 1, rest);
_isWrapModelRefreshing = true;
@@ -1573,9 +1470,9 @@ public int GetWrappedLineColWidth (int line, int col, WordWrapManager wrapManage
return modelCol - colWidthOffset;
}
- public bool Insert (int row, int col, RuneCell cell)
+ public bool Insert (int row, int col, Cell cell)
{
- List line = GetCurrentLine (GetModelLineFromWrappedLines (row));
+ List line = GetCurrentLine (GetModelLineFromWrappedLines (row));
line.Insert (GetModelColFromWrappedLines (row, col), cell);
if (line.Count > _frameWidth)
@@ -1589,7 +1486,7 @@ public bool Insert (int row, int col, RuneCell cell)
public bool RemoveAt (int row, int col)
{
int modelRow = GetModelLineFromWrappedLines (row);
- List line = GetCurrentLine (modelRow);
+ List line = GetCurrentLine (modelRow);
int modelCol = GetModelColFromWrappedLines (row, col);
if (modelCol > line.Count)
@@ -1617,7 +1514,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t
{
lineRemoved = false;
int modelRow = GetModelLineFromWrappedLines (row);
- List line = GetCurrentLine (modelRow);
+ List line = GetCurrentLine (modelRow);
int modelCol = GetModelColFromWrappedLines (row, col);
if (modelCol == 0 && line.Count == 0)
@@ -1653,7 +1550,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t
return false;
}
- List nextLine = Model.GetLine (modelRow + 1);
+ List nextLine = Model.GetLine (modelRow + 1);
line.AddRange (nextLine);
Model.RemoveLine (modelRow + 1);
@@ -1669,7 +1566,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t
return false;
}
- List prevLine = Model.GetLine (modelRow - 1);
+ List prevLine = Model.GetLine (modelRow - 1);
prevLine.AddRange (line);
Model.RemoveLine (modelRow);
@@ -1685,7 +1582,7 @@ public bool RemoveLine (int row, int col, out bool lineRemoved, bool forward = t
public bool RemoveRange (int row, int index, int count)
{
int modelRow = GetModelLineFromWrappedLines (row);
- List line = GetCurrentLine (modelRow);
+ List line = GetCurrentLine (modelRow);
int modelCol = GetModelColFromWrappedLines (row, index);
try
@@ -1700,13 +1597,13 @@ public bool RemoveRange (int row, int index, int count)
return true;
}
- public List> ToListRune (List textList)
+ public List> ToListRune (List textList)
{
- List> runesList = new ();
+ List> runesList = new ();
foreach (string text in textList)
{
- runesList.Add (TextModel.ToRuneCellList (text));
+ runesList.Add (Cell.ToCellList (text));
}
return runesList;
@@ -1778,11 +1675,11 @@ public TextModel WrapModel (
for (var i = 0; i < Model.Count; i++)
{
- List line = Model.GetLine (i);
+ List line = Model.GetLine (i);
- List> wrappedLines = ToListRune (
+ List> wrappedLines = ToListRune (
TextFormatter.Format (
- TextModel.ToString (line),
+ Cell.ToString (line),
width,
Alignment.Start,
true,
@@ -1794,7 +1691,7 @@ public TextModel WrapModel (
for (var j = 0; j < wrappedLines.Count; j++)
{
- List wrapLine = wrappedLines [j];
+ List wrapLine = wrappedLines [j];
if (!isRowAndColSet && modelRow == i)
{
@@ -1852,7 +1749,9 @@ public TextModel WrapModel (
for (int k = j; k < wrapLine.Count; k++)
{
- wrapLine [k].ColorScheme = line [k].ColorScheme;
+ Cell cell = wrapLine [k];
+ cell.Attribute = line [k].Attribute;
+ wrapLine [k] = cell;
}
wrappedModel.AddLine (lines, wrapLine);
@@ -1872,7 +1771,7 @@ public TextModel WrapModel (
return wrappedModel;
}
- private List GetCurrentLine (int row) { return Model.GetLine (row); }
+ private List GetCurrentLine (int row) { return Model.GetLine (row); }
private class WrappedLine
{
@@ -1997,7 +1896,7 @@ public TextView ()
CursorVisibility = CursorVisibility.Default;
Used = true;
- // By default, disable hotkeys (in case someome sets Title)
+ // By default, disable hotkeys (in case someone sets Title)
HotKeySpecifier = new ('\xffff');
_model.LinesLoaded += Model_LinesLoaded!;
@@ -2010,6 +1909,10 @@ public TextView ()
LayoutComplete += TextView_LayoutComplete;
// Things this view knows how to do
+
+ // Note - NewLine is only bound to Enter if Multiline is true
+ AddCommand (Command.NewLine, (ctx) => ProcessEnterKey (ctx));
+
AddCommand (
Command.PageDown,
() =>
@@ -2050,10 +1953,10 @@ public TextView ()
}
);
- AddCommand (Command.LineDown, () => ProcessMoveDown ());
+ AddCommand (Command.Down, () => ProcessMoveDown ());
AddCommand (
- Command.LineDownExtend,
+ Command.DownExtend,
() =>
{
ProcessMoveDownExtend ();
@@ -2062,10 +1965,10 @@ public TextView ()
}
);
- AddCommand (Command.LineUp, () => ProcessMoveUp ());
+ AddCommand (Command.Up, () => ProcessMoveUp ());
AddCommand (
- Command.LineUpExtend,
+ Command.UpExtend,
() =>
{
ProcessMoveUpExtend ();
@@ -2107,20 +2010,20 @@ public TextView ()
);
AddCommand (
- Command.StartOfLine,
+ Command.LeftStart,
() =>
{
- ProcessMoveStartOfLine ();
+ ProcessMoveLeftStart ();
return true;
}
);
AddCommand (
- Command.StartOfLineExtend,
+ Command.LeftStartExtend,
() =>
{
- ProcessMoveStartOfLineExtend ();
+ ProcessMoveLeftStartExtend ();
return true;
}
@@ -2137,7 +2040,7 @@ public TextView ()
);
AddCommand (
- Command.EndOfLine,
+ Command.RightEnd,
() =>
{
ProcessMoveEndOfLine ();
@@ -2147,10 +2050,10 @@ public TextView ()
);
AddCommand (
- Command.EndOfLineExtend,
+ Command.RightEndExtend,
() =>
{
- ProcessMoveEndOfLineExtend ();
+ ProcessMoveRightEndExtend ();
return true;
}
@@ -2170,7 +2073,7 @@ public TextView ()
Command.CutToStartLine,
() =>
{
- KillToStartOfLine ();
+ KillToLeftStart ();
return true;
}
@@ -2275,10 +2178,9 @@ public TextView ()
return true;
}
);
- AddCommand (Command.NewLine, () => ProcessReturn ());
AddCommand (
- Command.BottomEnd,
+ Command.End,
() =>
{
MoveBottomEnd ();
@@ -2288,7 +2190,7 @@ public TextView ()
);
AddCommand (
- Command.BottomEndExtend,
+ Command.EndExtend,
() =>
{
MoveBottomEndExtend ();
@@ -2298,7 +2200,7 @@ public TextView ()
);
AddCommand (
- Command.TopHome,
+ Command.Start,
() =>
{
MoveTopHome ();
@@ -2308,7 +2210,7 @@ public TextView ()
);
AddCommand (
- Command.TopHomeExtend,
+ Command.StartExtend,
() =>
{
MoveTopHomeExtend ();
@@ -2390,7 +2292,7 @@ public TextView ()
);
AddCommand (
- Command.ShowContextMenu,
+ Command.Context,
() =>
{
ContextMenu!.Position = new (
@@ -2403,26 +2305,39 @@ public TextView ()
}
);
+ AddCommand (
+ Command.Open,
+ () =>
+ {
+ PromptForColors ();
+
+ return true;
+ });
+
// Default keybindings for this view
+ KeyBindings.Remove (Key.Space);
+
+ KeyBindings.Remove (Key.Enter);
+ KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
+
KeyBindings.Add (Key.PageDown, Command.PageDown);
KeyBindings.Add (Key.V.WithCtrl, Command.PageDown);
KeyBindings.Add (Key.PageDown.WithShift, Command.PageDownExtend);
KeyBindings.Add (Key.PageUp, Command.PageUp);
- KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
KeyBindings.Add (Key.PageUp.WithShift, Command.PageUpExtend);
- KeyBindings.Add (Key.N.WithCtrl, Command.LineDown);
- KeyBindings.Add (Key.CursorDown, Command.LineDown);
+ KeyBindings.Add (Key.N.WithCtrl, Command.Down);
+ KeyBindings.Add (Key.CursorDown, Command.Down);
- KeyBindings.Add (Key.CursorDown.WithShift, Command.LineDownExtend);
+ KeyBindings.Add (Key.CursorDown.WithShift, Command.DownExtend);
- KeyBindings.Add (Key.P.WithCtrl, Command.LineUp);
- KeyBindings.Add (Key.CursorUp, Command.LineUp);
+ KeyBindings.Add (Key.P.WithCtrl, Command.Up);
+ KeyBindings.Add (Key.CursorUp, Command.Up);
- KeyBindings.Add (Key.CursorUp.WithShift, Command.LineUpExtend);
+ KeyBindings.Add (Key.CursorUp.WithShift, Command.UpExtend);
KeyBindings.Add (Key.F.WithCtrl, Command.Right);
KeyBindings.Add (Key.CursorRight, Command.Right);
@@ -2436,55 +2351,47 @@ public TextView ()
KeyBindings.Add (Key.Backspace, Command.DeleteCharLeft);
- KeyBindings.Add (Key.Home, Command.StartOfLine);
- KeyBindings.Add (Key.A.WithCtrl, Command.StartOfLine);
+ KeyBindings.Add (Key.Home, Command.LeftStart);
+ KeyBindings.Add (Key.A.WithCtrl, Command.LeftStart);
- KeyBindings.Add (Key.Home.WithShift, Command.StartOfLineExtend);
+ KeyBindings.Add (Key.Home.WithShift, Command.LeftStartExtend);
KeyBindings.Add (Key.Delete, Command.DeleteCharRight);
KeyBindings.Add (Key.D.WithCtrl, Command.DeleteCharRight);
- KeyBindings.Add (Key.End, Command.EndOfLine);
- KeyBindings.Add (Key.E.WithCtrl, Command.EndOfLine);
+ KeyBindings.Add (Key.End, Command.RightEnd);
+ KeyBindings.Add (Key.E.WithCtrl, Command.RightEnd);
- KeyBindings.Add (Key.End.WithShift, Command.EndOfLineExtend);
+ KeyBindings.Add (Key.End.WithShift, Command.RightEndExtend);
KeyBindings.Add (Key.K.WithCtrl, Command.CutToEndLine); // kill-to-end
KeyBindings.Add (Key.Delete.WithCtrl.WithShift, Command.CutToEndLine); // kill-to-end
- KeyBindings.Add (Key.K.WithAlt, Command.CutToStartLine); // kill-to-start
-
KeyBindings.Add (Key.Backspace.WithCtrl.WithShift, Command.CutToStartLine); // kill-to-start
KeyBindings.Add (Key.Y.WithCtrl, Command.Paste); // Control-y, yank
KeyBindings.Add (Key.Space.WithCtrl, Command.ToggleExtend);
- KeyBindings.Add (Key.C.WithAlt, Command.Copy);
KeyBindings.Add (Key.C.WithCtrl, Command.Copy);
- KeyBindings.Add (Key.W.WithAlt, Command.Cut);
- KeyBindings.Add (Key.W.WithCtrl, Command.Cut);
- KeyBindings.Add (Key.X.WithCtrl, Command.Cut);
+ KeyBindings.Add (Key.W.WithCtrl, Command.Cut); // Move to Unix?
+ KeyBindings.Add (Key.X.WithCtrl, Command.Cut);
KeyBindings.Add (Key.CursorLeft.WithCtrl, Command.WordLeft);
- KeyBindings.Add (Key.B.WithAlt, Command.WordLeft);
KeyBindings.Add (Key.CursorLeft.WithCtrl.WithShift, Command.WordLeftExtend);
KeyBindings.Add (Key.CursorRight.WithCtrl, Command.WordRight);
- KeyBindings.Add (Key.F.WithAlt, Command.WordRight);
KeyBindings.Add (Key.CursorRight.WithCtrl.WithShift, Command.WordRightExtend);
KeyBindings.Add (Key.Delete.WithCtrl, Command.KillWordForwards); // kill-word-forwards
KeyBindings.Add (Key.Backspace.WithCtrl, Command.KillWordBackwards); // kill-word-backwards
- // BUGBUG: If AllowsReturn is false, Key.Enter should not be bound (so that Toplevel can cause Command.Accept).
- KeyBindings.Add (Key.Enter, Command.NewLine);
- KeyBindings.Add (Key.End.WithCtrl, Command.BottomEnd);
- KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.BottomEndExtend);
- KeyBindings.Add (Key.Home.WithCtrl, Command.TopHome);
- KeyBindings.Add (Key.Home.WithCtrl.WithShift, Command.TopHomeExtend);
+ KeyBindings.Add (Key.End.WithCtrl, Command.End);
+ KeyBindings.Add (Key.End.WithCtrl.WithShift, Command.EndExtend);
+ KeyBindings.Add (Key.Home.WithCtrl, Command.Start);
+ KeyBindings.Add (Key.Home.WithCtrl.WithShift, Command.StartExtend);
KeyBindings.Add (Key.T.WithCtrl, Command.SelectAll);
KeyBindings.Add (Key.InsertChar, Command.ToggleOverwrite);
KeyBindings.Add (Key.Tab, Command.Tab);
@@ -2496,12 +2403,23 @@ public TextView ()
KeyBindings.Add (Key.G.WithCtrl, Command.DeleteAll);
KeyBindings.Add (Key.D.WithCtrl.WithShift, Command.DeleteAll);
+ KeyBindings.Add (Key.L.WithCtrl, Command.Open);
+
+#if UNIX_KEY_BINDINGS
+ KeyBindings.Add (Key.C.WithAlt, Command.Copy);
+ KeyBindings.Add (Key.B.WithAlt, Command.WordLeft);
+ KeyBindings.Add (Key.W.WithAlt, Command.Cut);
+ KeyBindings.Add (Key.V.WithAlt, Command.PageUp);
+ KeyBindings.Add (Key.F.WithAlt, Command.WordRight);
+ KeyBindings.Add (Key.K.WithAlt, Command.CutToStartLine); // kill-to-start
+#endif
+
_currentCulture = Thread.CurrentThread.CurrentUICulture;
ContextMenu = new ();
ContextMenu.KeyChanged += ContextMenu_KeyChanged!;
- KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.ShowContextMenu);
+ KeyBindings.Add ((KeyCode)ContextMenu.Key, KeyBindingScope.HotKey, Command.Context);
}
private void TextView_Added1 (object? sender, SuperViewChangedEventArgs e)
@@ -2512,7 +2430,7 @@ private void TextView_Added1 (object? sender, SuperViewChangedEventArgs e)
// BUGBUG: AllowsReturn is mis-named. It should be EnterKeyAccepts.
///
/// Gets or sets whether pressing ENTER in a creates a new line of text
- /// in the view or invokes the event.
+ /// in the view or invokes the event.
///
///
///
@@ -2602,7 +2520,7 @@ public Point CursorPosition
get => new (CurrentColumn, CurrentRow);
set
{
- List line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0));
+ List line = _model.GetLine (Math.Max (Math.Min (value.Y, _model.Count - 1), 0));
CurrentColumn = value.X < 0 ? 0 :
value.X > line.Count ? line.Count : value.X;
@@ -2622,11 +2540,11 @@ public Point CursorPosition
public bool HasHistoryChanges => _historyText.HasHistoryChanges;
///
- /// If and the current is null will inherit from the
+ /// If and the current is null will inherit from the
/// previous, otherwise if (default) do nothing. If the text is load with
- /// this property is automatically sets to .
+ /// this property is automatically sets to .
///
- public bool InheritsPreviousColorScheme { get; set; }
+ public bool InheritsPreviousAttribute { get; set; }
///
/// Indicates whatever the text was changed or not. if the text was changed
@@ -2634,8 +2552,8 @@ public Point CursorPosition
///
public bool IsDirty
{
- get => _historyText.IsDirty (Text);
- set => _historyText.Clear (Text);
+ get => _historyText.IsDirty (_model.GetAllLines ());
+ set => _historyText.Clear (_model.GetAllLines ());
}
/// Gets or sets the left column.
@@ -2700,6 +2618,9 @@ public bool Multiline
Height = _savedHeight;
SetNeedsDisplay ();
}
+
+ KeyBindings.Remove (Key.Enter);
+ KeyBindings.Add (Key.Enter, Multiline ? Command.NewLine : Command.Accept);
}
}
@@ -2724,12 +2645,28 @@ public bool ReadOnly
/// Length of the selected text.
public int SelectedLength => GetSelectedLength ();
+ ///
+ /// Gets the selected text as
+ ///
+ /// List{List{Cell}}
+ ///
+ ///
+ public List> SelectedCellsList
+ {
+ get
+ {
+ GetRegion (out List> selectedCellsList);
+
+ return selectedCellsList;
+ }
+ }
+
/// The selected text.
public string SelectedText
{
get
{
- if (!Selecting || (_model.Count == 1 && _model.GetLine (0).Count == 0))
+ if (!IsSelecting || (_model.Count == 1 && _model.GetLine (0).Count == 0))
{
return string.Empty;
}
@@ -2739,7 +2676,7 @@ public string SelectedText
}
/// Get or sets whether the user is currently selecting text.
- public bool Selecting { get; set; }
+ public bool IsSelecting { get; set; }
/// Start column position of the selected text.
public int SelectionStartColumn
@@ -2747,11 +2684,11 @@ public int SelectionStartColumn
get => _selectionStartColumn;
set
{
- List line = _model.GetLine (_selectionStartRow);
+ List line = _model.GetLine (_selectionStartRow);
_selectionStartColumn = value < 0 ? 0 :
value > line.Count ? line.Count : value;
- Selecting = true;
+ IsSelecting = true;
SetNeedsDisplay ();
Adjust ();
}
@@ -2765,7 +2702,7 @@ public int SelectionStartRow
{
_selectionStartRow = value < 0 ? 0 :
value > _model.Count - 1 ? Math.Max (_model.Count - 1, 0) : value;
- Selecting = true;
+ IsSelecting = true;
SetNeedsDisplay ();
Adjust ();
}
@@ -2818,7 +2755,7 @@ public override string Text
OnTextChanged ();
SetNeedsDisplay ();
- _historyText.Clear (Text);
+ _historyText.Clear (_model.GetAllLines ());
}
}
@@ -2870,7 +2807,7 @@ public bool WordWrap
/// Allows clearing the items updating the original text.
- public void ClearHistoryChanges () { _historyText?.Clear (Text); }
+ public void ClearHistoryChanges () { _historyText?.Clear (_model.GetAllLines ()); }
/// Closes the contents of the stream into the .
/// true, if stream was closed, false otherwise.
@@ -2892,20 +2829,119 @@ public bool CloseFile ()
/// | |
public event EventHandler? ContentsChanged;
+ internal void ApplyCellsAttribute (Attribute attribute)
+ {
+ if (!ReadOnly && SelectedLength > 0)
+ {
+ int startRow = Math.Min (SelectionStartRow, CurrentRow);
+ int endRow = Math.Max (CurrentRow, SelectionStartRow);
+ int startCol = SelectionStartRow <= CurrentRow ? SelectionStartColumn : CurrentColumn;
+ int endCol = CurrentRow >= SelectionStartRow ? CurrentColumn : SelectionStartColumn;
+ List> selectedCellsOriginal = [];
+ List> selectedCellsChanged = [];
+
+ for (int r = startRow; r <= endRow; r++)
+ {
+ List line = GetLine (r);
+
+ selectedCellsOriginal.Add ([.. line]);
+
+ for (int c = r == startRow ? startCol : 0;
+ c < (r == endRow ? endCol : line.Count);
+ c++)
+ {
+ Cell cell = line [c]; // Copy value to a new variable
+ cell.Attribute = attribute; // Modify the copy
+ line [c] = cell; // Assign the modified copy back
+ }
+
+ selectedCellsChanged.Add ([..GetLine (r)]);
+ }
+
+ GetSelectedRegion ();
+ IsSelecting = false;
+
+ _historyText.Add (
+ [.. selectedCellsOriginal],
+ new (startCol, startRow)
+ );
+
+ _historyText.Add (
+ [.. selectedCellsChanged],
+ new (startCol, startRow),
+ HistoryText.LineStatus.Attribute
+ );
+ }
+ }
+
+ private Attribute? GetSelectedCellAttribute ()
+ {
+ List line;
+
+ if (SelectedLength > 0)
+ {
+ line = GetLine (SelectionStartRow);
+
+ if (line [Math.Min (SelectionStartColumn, line.Count - 1)].Attribute is { } attributeSel)
+ {
+ return new (attributeSel);
+ }
+
+ return new (ColorScheme!.Focus);
+ }
+
+ line = GetCurrentLine ();
+
+ if (line [Math.Min (CurrentColumn, line.Count - 1)].Attribute is { } attribute)
+ {
+ return new (attribute);
+ }
+
+ return new (ColorScheme!.Focus);
+ }
+
+ ///
+ /// Open a dialog to set the foreground and background colors.
+ ///
+ public void PromptForColors ()
+ {
+ if (!ColorPicker.Prompt (
+ "Colors",
+ GetSelectedCellAttribute (),
+ out Attribute newAttribute
+ ))
+ {
+ return;
+ }
+
+ var attribute = new Attribute (
+ newAttribute.Foreground,
+ newAttribute.Background
+ );
+
+ ApplyCellsAttribute (attribute);
+ }
+
+ private string? _copiedText;
+ private List> _copiedCellsList = [];
+
/// Copy the selected text to the clipboard contents.
public void Copy ()
{
SetWrapModel ();
- if (Selecting)
+ if (IsSelecting)
{
- SetClipboard (GetRegion ());
+ _copiedText = GetRegion (out _copiedCellsList);
+ SetClipboard (_copiedText);
_copyWithoutSelection = false;
}
else
{
- List currentLine = GetCurrentLine ();
- SetClipboard (TextModel.ToString (currentLine));
+ List currentLine = GetCurrentLine ();
+ _copiedCellsList.Add (currentLine);
+ _copiedText = Cell.ToString (currentLine);
+ SetClipboard (_copiedText);
_copyWithoutSelection = true;
}
@@ -2917,21 +2953,22 @@ public void Copy ()
public void Cut ()
{
SetWrapModel ();
- SetClipboard (GetRegion ());
+ _copiedText = GetRegion (out _copiedCellsList);
+ SetClipboard (_copiedText);
if (!_isReadOnly)
{
ClearRegion ();
_historyText.Add (
- new () { new (GetCurrentLine ()) },
+ [new (GetCurrentLine ())],
CursorPosition,
HistoryText.LineStatus.Replaced
);
}
UpdateWrapModel ();
- Selecting = false;
+ IsSelecting = false;
DoNeededAction ();
OnContentsChanged ();
}
@@ -2961,13 +2998,13 @@ public void DeleteCharLeft ()
SetWrapModel ();
- if (Selecting)
+ if (IsSelecting)
{
_historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
ClearSelectedRegion ();
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
_historyText.Add (
new () { new (currentLine) },
@@ -3005,13 +3042,13 @@ public void DeleteCharRight ()
SetWrapModel ();
- if (Selecting)
+ if (IsSelecting)
{
_historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
ClearSelectedRegion ();
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
_historyText.Add (
new () { new (currentLine) },
@@ -3040,19 +3077,19 @@ public void DeleteCharRight ()
}
/// Invoked when the normal color is drawn.
- public event EventHandler? DrawNormalColor;
+ public event EventHandler? DrawNormalColor;
/// Invoked when the ready only color is drawn.
- public event EventHandler? DrawReadOnlyColor;
+ public event EventHandler? DrawReadOnlyColor;
/// Invoked when the selection color is drawn.
- public event EventHandler? DrawSelectionColor;
+ public event EventHandler? DrawSelectionColor;
///
/// Invoked when the used color is drawn. The Used Color is used to indicate if the
/// was pressed and enabled.
///
- public event EventHandler? DrawUsedColor;
+ public event EventHandler? DrawUsedColor;
/// Find the next text based on the match case with the option to replace it.
/// The text to find.
@@ -3125,31 +3162,24 @@ public bool FindPreviousText (
/// Gets all lines of characters.
///
- public List> GetAllLines () { return _model.GetAllLines (); }
+ public List> GetAllLines () { return _model.GetAllLines (); }
///
/// Returns the characters on the current line (where the cursor is positioned). Use
/// to determine the position of the cursor within that line
///
///
- public List GetCurrentLine () { return _model.GetLine (CurrentRow); }
+ public List GetCurrentLine () { return _model.GetLine (CurrentRow); }
/// Returns the characters on the .
/// The intended line.
///
- public List GetLine (int line) { return _model.GetLine (line); }
+ public List GetLine (int line) { return _model.GetLine (line); }
///
public override Attribute GetNormalColor ()
{
- ColorScheme? cs = ColorScheme;
-
- if (ColorScheme is null)
- {
- cs = new ();
- }
-
- return Enabled ? cs.Focus : cs.Disabled;
+ return GetFocusColor ();
}
///
@@ -3199,7 +3229,7 @@ public bool Load (string path)
{
SetWrapModel ();
res = _model.LoadFile (path);
- _historyText.Clear (Text);
+ _historyText.Clear (_model.GetAllLines ());
ResetPosition ();
}
finally
@@ -3221,33 +3251,33 @@ public void Load (Stream stream)
{
SetWrapModel ();
_model.LoadStream (stream);
- _historyText.Clear (Text);
+ _historyText.Clear (_model.GetAllLines ());
ResetPosition ();
SetNeedsDisplay ();
UpdateWrapModel ();
}
- /// Loads the contents of the list into the .
+ /// Loads the contents of the list into the .
/// Rune cells list to load the contents from.
- public void Load (List cells)
+ public void Load (List cells)
{
SetWrapModel ();
- _model.LoadRuneCells (cells, ColorScheme);
- _historyText.Clear (Text);
+ _model.LoadCells (cells, ColorScheme?.Focus);
+ _historyText.Clear (_model.GetAllLines ());
ResetPosition ();
SetNeedsDisplay ();
UpdateWrapModel ();
- InheritsPreviousColorScheme = true;
+ InheritsPreviousAttribute = true;
}
- /// Loads the contents of the list of list into the .
+ /// Loads the contents of the list of list into the .
/// List of rune cells list to load the contents from.
- public void Load (List> cellsList)
+ public void Load (List> cellsList)
{
SetWrapModel ();
- InheritsPreviousColorScheme = true;
- _model.LoadListRuneCells (cellsList, ColorScheme);
- _historyText.Clear (Text);
+ InheritsPreviousAttribute = true;
+ _model.LoadListCells (cellsList, ColorScheme?.Focus);
+ _historyText.Clear (_model.GetAllLines ());
ResetPosition ();
SetNeedsDisplay ();
UpdateWrapModel ();
@@ -3343,10 +3373,10 @@ protected internal override bool OnMouseEvent (MouseEvent ev)
}
else if (ev.Flags.HasFlag (MouseFlags.Button1Pressed | MouseFlags.ReportMousePosition))
{
- ProcessMouseClick (ev, out List line);
+ ProcessMouseClick (ev, out List line);
PositionCursor ();
- if (_model.Count > 0 && _shiftSelecting && Selecting)
+ if (_model.Count > 0 && _shiftSelecting && IsSelecting)
{
if (CurrentRow - _topRow >= Viewport.Height - 1 && _model.Count > _topRow + CurrentRow)
{
@@ -3410,7 +3440,7 @@ protected internal override bool OnMouseEvent (MouseEvent ev)
ProcessMouseClick (ev, out _);
PositionCursor ();
- if (!Selecting)
+ if (!IsSelecting)
{
StartSelecting ();
}
@@ -3432,17 +3462,17 @@ protected internal override bool OnMouseEvent (MouseEvent ev)
{
if (ev.Flags.HasFlag (MouseFlags.ButtonShift))
{
- if (!Selecting)
+ if (!IsSelecting)
{
StartSelecting ();
}
}
- else if (Selecting)
+ else if (IsSelecting)
{
StopSelecting ();
}
- ProcessMouseClick (ev, out List line);
+ ProcessMouseClick (ev, out List line);
(int col, int row)? newPos;
if (CurrentColumn == line.Count
@@ -3456,7 +3486,7 @@ protected internal override bool OnMouseEvent (MouseEvent ev)
}
}
- if (!Selecting)
+ if (!IsSelecting)
{
StartSelecting ();
}
@@ -3474,15 +3504,15 @@ protected internal override bool OnMouseEvent (MouseEvent ev)
}
else if (ev.Flags.HasFlag (MouseFlags.Button1TripleClicked))
{
- if (Selecting)
+ if (IsSelecting)
{
StopSelecting ();
}
- ProcessMouseClick (ev, out List line);
+ ProcessMouseClick (ev, out List line);
CurrentColumn = 0;
- if (!Selecting)
+ if (!IsSelecting)
{
StartSelecting ();
}
@@ -3505,7 +3535,7 @@ protected internal override bool OnMouseEvent (MouseEvent ev)
public void MoveEnd ()
{
CurrentRow = _model.Count - 1;
- List line = GetCurrentLine ();
+ List line = GetCurrentLine ();
CurrentColumn = line.Count;
TrackColumn ();
PositionCursor ();
@@ -3550,7 +3580,7 @@ public override void OnDrawContent (Rectangle viewport)
for (int idxRow = _topRow; idxRow < _model.Count; idxRow++)
{
- List line = _model.GetLine (idxRow);
+ List line = _model.GetLine (idxRow);
int lineRuneCount = line.Count;
var col = 0;
@@ -3561,11 +3591,11 @@ public override void OnDrawContent (Rectangle viewport)
Rune rune = idxCol >= lineRuneCount ? (Rune)' ' : line [idxCol].Rune;
int cols = rune.GetColumns ();
- if (idxCol < line.Count && Selecting && PointInSelection (idxCol, idxRow))
+ if (idxCol < line.Count && IsSelecting && PointInSelection (idxCol, idxRow))
{
OnDrawSelectionColor (line, idxCol, idxRow);
}
- else if (idxCol == CurrentColumn && idxRow == CurrentRow && !Selecting && !Used && HasFocus && idxCol < lineRuneCount)
+ else if (idxCol == CurrentColumn && idxRow == CurrentRow && !IsSelecting && !Used && HasFocus && idxCol < lineRuneCount)
{
OnDrawUsedColor (line, idxCol, idxRow);
}
@@ -3598,6 +3628,8 @@ public override void OnDrawContent (Rectangle viewport)
else
{
AddRune (col, row, rune);
+ // Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
+ cols = Math.Max (cols, 1);
}
if (!TextModel.SetCol (ref col, viewport.Right, cols))
@@ -3718,17 +3750,17 @@ public void Paste ()
SetWrapModel ();
string? contents = Clipboard.Contents;
- if (_copyWithoutSelection && contents.FirstOrDefault (x => x == '\n' || x == '\r') == 0)
+ if (_copyWithoutSelection && contents.FirstOrDefault (x => x is '\n' or '\r') == 0)
{
- List runeList = contents is null ? new () : TextModel.ToRuneCellList (contents);
- List currentLine = GetCurrentLine ();
+ List runeList = contents is null ? [] : Cell.ToCellList (contents);
+ List currentLine = GetCurrentLine ();
- _historyText.Add (new () { new (currentLine) }, CursorPosition);
+ _historyText.Add ([new (currentLine)], CursorPosition);
- List> addedLine = new () { new (currentLine), runeList };
+ List> addedLine = [new (currentLine), runeList];
_historyText.Add (
- new (addedLine),
+ [..addedLine],
CursorPosition,
HistoryText.LineStatus.Added
);
@@ -3737,7 +3769,7 @@ public void Paste ()
CurrentRow++;
_historyText.Add (
- new () { new (GetCurrentLine ()) },
+ [new (GetCurrentLine ())],
CursorPosition,
HistoryText.LineStatus.Replaced
);
@@ -3747,18 +3779,18 @@ public void Paste ()
}
else
{
- if (Selecting)
+ if (IsSelecting)
{
ClearRegion ();
}
_copyWithoutSelection = false;
- InsertAllText (contents);
+ InsertAllText (contents, true);
- if (Selecting)
+ if (IsSelecting)
{
_historyText.ReplaceLast (
- new () { new (GetCurrentLine ()) },
+ [new (GetCurrentLine ())],
CursorPosition,
HistoryText.LineStatus.Original
);
@@ -3768,7 +3800,7 @@ public void Paste ()
}
UpdateWrapModel ();
- Selecting = false;
+ IsSelecting = false;
DoNeededAction ();
}
@@ -3782,7 +3814,7 @@ public void Paste ()
return null;
}
- if (Application.MouseGrabView == this && Selecting)
+ if (Application.MouseGrabView == this && IsSelecting)
{
// BUGBUG: customized rect aren't supported now because the Redraw isn't using the Intersect method.
//var minRow = Math.Min (Math.Max (Math.Min (selectionStartRow, currentRow) - topRow, 0), Viewport.Height);
@@ -3791,7 +3823,7 @@ public void Paste ()
SetNeedsDisplay ();
}
- List line = _model.GetLine (CurrentRow);
+ List line = _model.GetLine (CurrentRow);
var col = 0;
if (line.Count > 0)
@@ -3809,6 +3841,11 @@ public void Paste ()
{
cols += TabWidth + 1;
}
+ else
+ {
+ // Ensures that cols less than 0 to be 1 because it will be converted to a printable rune
+ cols = Math.Max (cols, 1);
+ }
if (!TextModel.SetCol (ref col, Viewport.Width, cols))
{
@@ -3945,16 +3982,16 @@ public void Undo ()
/// The line.
/// The col index.
/// The row index.
- protected virtual void OnDrawNormalColor (List line, int idxCol, int idxRow)
+ protected virtual void OnDrawNormalColor (List line, int idxCol, int idxRow)
{
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
- var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos);
+ var ev = new CellEventArgs (line, idxCol, unwrappedPos);
DrawNormalColor?.Invoke (this, ev);
- if (line [idxCol].ColorScheme is { })
+ if (line [idxCol].Attribute is { })
{
- ColorScheme? colorScheme = line [idxCol].ColorScheme;
- Driver.SetAttribute (Enabled ? colorScheme!.Focus : colorScheme!.Disabled);
+ Attribute? attribute = line [idxCol].Attribute;
+ Driver.SetAttribute ((Attribute)attribute!);
}
else
{
@@ -3971,22 +4008,22 @@ protected virtual void OnDrawNormalColor (List line, int idxCol, int i
/// The col index.
/// ///
/// The row index.
- protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int idxRow)
+ protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int idxRow)
{
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
- var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos);
+ var ev = new CellEventArgs (line, idxCol, unwrappedPos);
DrawReadOnlyColor?.Invoke (this, ev);
- ColorScheme? colorScheme = line [idxCol].ColorScheme is { } ? line [idxCol].ColorScheme : ColorScheme;
+ Attribute? cellAttribute = line [idxCol].Attribute is { } ? line [idxCol].Attribute : ColorScheme?.Disabled;
Attribute attribute;
- if (colorScheme!.Disabled.Foreground == colorScheme.Focus.Background)
+ if (cellAttribute!.Value.Foreground == cellAttribute.Value.Background)
{
- attribute = new (colorScheme.Focus.Foreground, colorScheme.Focus.Background);
+ attribute = new (cellAttribute.Value.Foreground, cellAttribute.Value.Background);
}
else
{
- attribute = new (colorScheme.Disabled.Foreground, colorScheme.Focus.Background);
+ attribute = new (cellAttribute.Value.Foreground, ColorScheme!.Focus.Background);
}
Driver.SetAttribute (attribute);
@@ -4001,26 +4038,26 @@ protected virtual void OnDrawReadOnlyColor (List line, int idxCol, int
/// The col index.
/// ///
/// The row index.
- protected virtual void OnDrawSelectionColor (List line, int idxCol, int idxRow)
+ protected virtual void OnDrawSelectionColor (List line, int idxCol, int idxRow)
{
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
- var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos);
+ var ev = new CellEventArgs (line, idxCol, unwrappedPos);
DrawSelectionColor?.Invoke (this, ev);
- if (line [idxCol].ColorScheme is { })
+ if (line [idxCol].Attribute is { })
{
- ColorScheme? colorScheme = line [idxCol].ColorScheme;
+ Attribute? attribute = line [idxCol].Attribute;
Driver.SetAttribute (
- new (colorScheme!.Focus.Background, colorScheme.Focus.Foreground)
+ new (attribute!.Value.Background, attribute.Value.Foreground)
);
}
else
{
Driver.SetAttribute (
new (
- ColorScheme.Focus.Background,
- ColorScheme.Focus.Foreground
+ ColorScheme!.Focus.Background,
+ ColorScheme!.Focus.Foreground
)
);
}
@@ -4035,20 +4072,20 @@ protected virtual void OnDrawSelectionColor (List line, int idxCol, in
/// The col index.
/// ///
/// The row index.
- protected virtual void OnDrawUsedColor (List line, int idxCol, int idxRow)
+ protected virtual void OnDrawUsedColor (List line, int idxCol, int idxRow)
{
(int Row, int Col) unwrappedPos = GetUnwrappedPosition (idxRow, idxCol);
- var ev = new RuneCellEventArgs (line, idxCol, unwrappedPos);
+ var ev = new CellEventArgs (line, idxCol, unwrappedPos);
DrawUsedColor?.Invoke (this, ev);
- if (line [idxCol].ColorScheme is { })
+ if (line [idxCol].Attribute is { })
{
- ColorScheme? colorScheme = line [idxCol].ColorScheme;
- SetValidUsedColor (colorScheme!);
+ Attribute? attribute = line [idxCol].Attribute;
+ SetValidUsedColor (attribute!);
}
else
{
- SetValidUsedColor (ColorScheme);
+ SetValidUsedColor (ColorScheme?.Focus);
}
}
@@ -4061,7 +4098,7 @@ protected virtual void OnDrawUsedColor (List line, int idxCol, int idx
private void Adjust ()
{
(int width, int height) offB = OffSetBackground ();
- List line = GetCurrentLine ();
+ List line = GetCurrentLine ();
bool need = NeedsDisplay || _wrapNeeded || !Used;
(int size, int length) tSize = TextModel.DisplaySize (line, -1, -1, false, TabWidth);
(int size, int length) dSize = TextModel.DisplaySize (line, _leftColumn, CurrentColumn, true, TabWidth);
@@ -4191,6 +4228,14 @@ private void Adjust ()
null,
null,
(KeyCode)KeyBindings.GetKeyFromCommands (Command.Redo)
+ ),
+ new (
+ Strings.ctxColors,
+ "",
+ () => PromptForColors (),
+ null,
+ null,
+ (KeyCode)KeyBindings.GetKeyFromCommands (Command.Open)
)
}
);
@@ -4223,11 +4268,11 @@ private void ClearRegion ()
var maxrow = (int)(end >> 32);
var startCol = (int)(start & 0xffffffff);
var endCol = (int)(end & 0xffffffff);
- List line = _model.GetLine (startRow);
+ List line = _model.GetLine (startRow);
_historyText.Add (new () { new (line) }, new (startCol, startRow));
- List> removedLines = new ();
+ List> removedLines = new ();
if (startRow == maxrow)
{
@@ -4262,7 +4307,7 @@ private void ClearRegion ()
removedLines.Add (new (line));
line.RemoveRange (startCol, line.Count - startCol);
- List line2 = _model.GetLine (maxrow);
+ List line2 = _model.GetLine (maxrow);
line.AddRange (line2.Skip (endCol));
for (int row = startRow + 1; row <= maxrow; row++)
@@ -4300,7 +4345,7 @@ private void ClearSelectedRegion ()
}
UpdateWrapModel ();
- Selecting = false;
+ IsSelecting = false;
DoNeededAction ();
}
@@ -4313,7 +4358,7 @@ private bool DeleteTextBackwards ()
if (CurrentColumn > 0)
{
// Delete backwards
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
_historyText.Add (new () { new (currentLine) }, CursorPosition);
@@ -4353,11 +4398,11 @@ private bool DeleteTextBackwards ()
}
int prowIdx = CurrentRow - 1;
- List prevRow = _model.GetLine (prowIdx);
+ List prevRow = _model.GetLine (prowIdx);
_historyText.Add (new () { new (prevRow) }, CursorPosition);
- List> removedLines = new () { new (prevRow) };
+ List> removedLines = new () { new (prevRow) };
removedLines.Add (new (GetCurrentLine ()));
@@ -4397,7 +4442,7 @@ private bool DeleteTextForwards ()
{
SetWrapModel ();
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
if (CurrentColumn == currentLine.Count)
{
@@ -4410,9 +4455,9 @@ private bool DeleteTextForwards ()
_historyText.Add (new () { new (currentLine) }, CursorPosition);
- List> removedLines = new () { new (currentLine) };
+ List> removedLines = new () { new (currentLine) };
- List nextLine = _model.GetLine (CurrentRow + 1);
+ List nextLine = _model.GetLine (CurrentRow + 1);
removedLines.Add (new (nextLine));
@@ -4492,7 +4537,7 @@ private void DoSetNeedsDisplay (Rectangle rect)
}
}
- private IEnumerable<(int col, int row, RuneCell rune)> ForwardIterator (int col, int row)
+ private IEnumerable<(int col, int row, Cell rune)> ForwardIterator (int col, int row)
{
if (col < 0 || row < 0)
{
@@ -4504,7 +4549,7 @@ private void DoSetNeedsDisplay (Rectangle rect)
yield break;
}
- List line = GetCurrentLine ();
+ List line = GetCurrentLine ();
if (col >= line.Count)
{
@@ -4526,7 +4571,7 @@ private void DoSetNeedsDisplay (Rectangle rect)
private void GenerateSuggestions ()
{
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
int cursorPosition = Math.Min (CurrentColumn, currentLine.Count);
Autocomplete.Context = new (
@@ -4582,7 +4627,8 @@ private void GetEncodedRegionBounds (
// Returns a string with the text in the selected
// region.
//
- private string GetRegion (
+ internal string GetRegion (
+ out List> cellsList,
int? sRow = null,
int? sCol = null,
int? cRow = null,
@@ -4590,8 +4636,9 @@ private string GetRegion (
TextModel? model = null
)
{
- long start, end;
- GetEncodedRegionBounds (out start, out end, sRow, sCol, cRow, cCol);
+ GetEncodedRegionBounds (out long start, out long end, sRow, sCol, cRow, cCol);
+
+ cellsList = [];
if (start == end)
{
@@ -4599,31 +4646,38 @@ private string GetRegion (
}
var startRow = (int)(start >> 32);
- var maxrow = (int)(end >> 32);
+ var maxRow = (int)(end >> 32);
var startCol = (int)(start & 0xffffffff);
var endCol = (int)(end & 0xffffffff);
- List line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow);
+ List line = model is null ? _model.GetLine (startRow) : model.GetLine (startRow);
+ List cells;
- if (startRow == maxrow)
+ if (startRow == maxRow)
{
- return StringFromRunes (line.GetRange (startCol, endCol - startCol));
+ cells = line.GetRange (startCol, endCol - startCol);
+ cellsList.Add (cells);
+ return StringFromRunes (cells);
}
- string res = StringFromRunes (line.GetRange (startCol, line.Count - startCol));
+ cells = line.GetRange (startCol, line.Count - startCol);
+ cellsList.Add (cells);
+ string res = StringFromRunes (cells);
- for (int row = startRow + 1; row < maxrow; row++)
+ for (int row = startRow + 1; row < maxRow; row++)
{
+ cellsList.AddRange ([]);
+ cells = model == null ? _model.GetLine (row) : model.GetLine (row);
+ cellsList.Add (cells);
res = res
+ Environment.NewLine
- + StringFromRunes (
- model == null
- ? _model.GetLine (row)
- : model.GetLine (row)
- );
+ + StringFromRunes (cells);
}
- line = model is null ? _model.GetLine (maxrow) : model.GetLine (maxrow);
- res = res + Environment.NewLine + StringFromRunes (line.GetRange (0, endCol));
+ line = model is null ? _model.GetLine (maxRow) : model.GetLine (maxRow);
+ cellsList.AddRange ([]);
+ cells = line.GetRange (0, endCol);
+ cellsList.Add (cells);
+ res = res + Environment.NewLine + StringFromRunes (cells);
return res;
}
@@ -4649,7 +4703,7 @@ private string GetSelectedRegion ()
OnUnwrappedCursorPosition (cRow, cCol);
- return GetRegion (startRow, startCol, cRow, cCol, model);
+ return GetRegion (out _, sRow: startRow, sCol: startCol, cRow: cRow, cCol: cCol, model: model);
}
private (int Row, int Col) GetUnwrappedPosition (int line, int col)
@@ -4701,12 +4755,12 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE
for (var i = 0; i < obj.Lines.Count; i++)
{
- if (i == 0)
+ if (i == 0 || obj.LineStatus == HistoryText.LineStatus.Original || obj.LineStatus == HistoryText.LineStatus.Attribute)
{
_model.ReplaceLine (startLine, obj.Lines [i]);
}
- else if ((obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Removed)
- || (!obj.IsUndoing && obj.LineStatus == HistoryText.LineStatus.Added))
+ else if (obj is { IsUndoing: true, LineStatus: HistoryText.LineStatus.Removed }
+ or { IsUndoing: false, LineStatus: HistoryText.LineStatus.Added })
{
_model.AddLine (startLine, obj.Lines [i]);
}
@@ -4727,9 +4781,9 @@ private void HistoryText_ChangeText (object sender, HistoryText.HistoryTextItemE
OnContentsChanged ();
}
- private void Insert (RuneCell cell)
+ private void Insert (Cell cell)
{
- List line = GetCurrentLine ();
+ List line = GetCurrentLine ();
if (Used)
{
@@ -4755,14 +4809,25 @@ private void Insert (RuneCell cell)
}
}
- private void InsertAllText (string text)
+ private void InsertAllText (string text, bool fromClipboard = false)
{
if (string.IsNullOrEmpty (text))
{
return;
}
- List> lines = TextModel.StringToLinesOfRuneCells (text);
+ List> lines;
+
+ if (fromClipboard && text == _copiedText)
+ {
+ lines = _copiedCellsList;
+ }
+ else
+ {
+ // Get selected attribute
+ Attribute? attribute = GetSelectedAttribute (CurrentRow, CurrentColumn);
+ lines = Cell.StringToLinesOfCells (text, attribute);
+ }
if (lines.Count == 0)
{
@@ -4771,9 +4836,9 @@ private void InsertAllText (string text)
SetWrapModel ();
- List line = GetCurrentLine ();
+ List line = GetCurrentLine ();
- _historyText.Add (new () { new (line) }, CursorPosition);
+ _historyText.Add ([new (line)], CursorPosition);
// Optimize single line
if (lines.Count == 1)
@@ -4782,7 +4847,7 @@ private void InsertAllText (string text)
CurrentColumn += lines [0].Count;
_historyText.Add (
- new () { new (line) },
+ [new (line)],
CursorPosition,
HistoryText.LineStatus.Replaced
);
@@ -4810,8 +4875,8 @@ private void InsertAllText (string text)
return;
}
- List? rest = null;
- var lastp = 0;
+ List? rest = null;
+ var lastPosition = 0;
if (_model.Count > 0 && line.Count > 0 && !_copyWithoutSelection)
{
@@ -4826,19 +4891,19 @@ private void InsertAllText (string text)
//model.AddLine (currentRow, lines [0]);
- List> addedLines = new () { new (line) };
+ List> addedLines = [new (line)];
for (var i = 1; i < lines.Count; i++)
{
_model.AddLine (CurrentRow + i, lines [i]);
- addedLines.Add (new (lines [i]));
+ addedLines.Add ([..lines [i]]);
}
if (rest is { })
{
- List last = _model.GetLine (CurrentRow + lines.Count - 1);
- lastp = last.Count;
+ List last = _model.GetLine (CurrentRow + lines.Count - 1);
+ lastPosition = last.Count;
last.InsertRange (last.Count, rest);
addedLines.Last ().InsertRange (addedLines.Last ().Count, rest);
@@ -4848,11 +4913,11 @@ private void InsertAllText (string text)
// Now adjust column and row positions
CurrentRow += lines.Count - 1;
- CurrentColumn = rest is { } ? lastp : lines [lines.Count - 1].Count;
+ CurrentColumn = rest is { } ? lastPosition : lines [^1].Count;
Adjust ();
_historyText.Add (
- new () { new (line) },
+ [new (line)],
CursorPosition,
HistoryText.LineStatus.Replaced
);
@@ -4861,7 +4926,7 @@ private void InsertAllText (string text)
OnContentsChanged ();
}
- private bool InsertText (Key a, ColorScheme? colorScheme = null)
+ private bool InsertText (Key a, Attribute? attribute = null)
{
//So that special keys like tab can be processed
if (_isReadOnly)
@@ -4871,16 +4936,16 @@ private bool InsertText (Key a, ColorScheme? colorScheme = null)
SetWrapModel ();
- _historyText.Add (new () { new (GetCurrentLine ()) }, CursorPosition);
+ _historyText.Add ([new (GetCurrentLine ())], CursorPosition);
- if (Selecting)
+ if (IsSelecting)
{
ClearSelectedRegion ();
}
if ((uint)a.KeyCode == '\n')
{
- _model.AddLine (CurrentRow + 1, new ());
+ _model.AddLine (CurrentRow + 1, []);
CurrentRow++;
CurrentColumn = 0;
}
@@ -4892,7 +4957,7 @@ private bool InsertText (Key a, ColorScheme? colorScheme = null)
{
if (Used)
{
- Insert (new () { Rune = a.AsRune, ColorScheme = colorScheme });
+ Insert (new () { Rune = a.AsRune, Attribute = attribute });
CurrentColumn++;
if (CurrentColumn >= _leftColumn + Viewport.Width)
@@ -4903,13 +4968,13 @@ private bool InsertText (Key a, ColorScheme? colorScheme = null)
}
else
{
- Insert (new () { Rune = a.AsRune, ColorScheme = colorScheme });
+ Insert (new () { Rune = a.AsRune, Attribute = attribute });
CurrentColumn++;
}
}
_historyText.Add (
- new () { new (GetCurrentLine ()) },
+ [new (GetCurrentLine ())],
CursorPosition,
HistoryText.LineStatus.Replaced
);
@@ -4935,7 +5000,7 @@ private void KillToEndOfLine ()
SetWrapModel ();
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
var setLastWasKill = true;
if (currentLine.Count > 0 && CurrentColumn == currentLine.Count)
@@ -4953,7 +5018,7 @@ private void KillToEndOfLine ()
{
if (CurrentRow < _model.Count - 1)
{
- List> removedLines = new () { new (currentLine) };
+ List> removedLines = new () { new (currentLine) };
_model.RemoveLine (CurrentRow);
@@ -4989,7 +5054,7 @@ private void KillToEndOfLine ()
else
{
int restCount = currentLine.Count - CurrentColumn;
- List rest = currentLine.GetRange (CurrentColumn, restCount);
+ List rest = currentLine.GetRange (CurrentColumn, restCount);
var val = string.Empty;
val += StringFromRunes (rest);
@@ -5019,7 +5084,7 @@ [ [.. GetCurrentLine ()]],
DoNeededAction ();
}
- private void KillToStartOfLine ()
+ private void KillToLeftStart ()
{
if (_isReadOnly)
{
@@ -5034,7 +5099,7 @@ private void KillToStartOfLine ()
SetWrapModel ();
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
var setLastWasKill = true;
if (currentLine.Count > 0 && CurrentColumn == 0)
@@ -5077,7 +5142,7 @@ private void KillToStartOfLine ()
CurrentRow--;
currentLine = _model.GetLine (CurrentRow);
- List> removedLine =
+ List> removedLine =
[
[..currentLine],
[]
@@ -5095,7 +5160,7 @@ private void KillToStartOfLine ()
else
{
int restCount = CurrentColumn;
- List rest = currentLine.GetRange (0, restCount);
+ List rest = currentLine.GetRange (0, restCount);
var val = string.Empty;
val += StringFromRunes (rest);
@@ -5135,7 +5200,7 @@ private void KillWordBackward ()
SetWrapModel ();
- List currentLine = GetCurrentLine ();
+ List currentLine = GetCurrentLine ();
_historyText.Add ([ [.. GetCurrentLine ()]], CursorPosition);
@@ -5203,7 +5268,7 @@ private void KillWordForward ()
SetWrapModel ();
- List | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | | |