* The current, stable, release of Terminal.Gui v1 is [](https://www.nuget.org/packages/Terminal.Gui).
* The current, stable, release of Terminal.Gui v1 is [](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).
* 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.
* `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.
**Terminal.Gui**: A toolkit for building rich console apps for .NET, .NET Core, and Mono that works on Windows, the Mac, and Linux/Unix.
-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.
@@ -8,49 +8,7 @@ public static partial class Application // Toplevel handling
/// <summary>Holds the stack of TopLevel views.</summary>
/// <summary>Holds the stack of TopLevel views.</summary>
internal static Stack<Toplevel> TopLevels { get; } = new ();
internal static Stack<Toplevel> TopLevels { get; } = new ();
- /// <summary>The <see cref="Toplevel"/> object used for the application on startup (<seealso cref="Top"/>)</summary>
+ /// <summary>The <see cref="Toplevel"/> that is currently active.</summary>
/// <value>The top.</value>
/// <value>The top.</value>
public static Toplevel? Top { get; internal set; }
public static Toplevel? Top { get; internal set; }
-
- // TODO: Determine why this can't just return _topLevels.Peek()?
- /// <summary>
- /// The current <see cref="Toplevel"/> object. This is updated in <see cref="Application.Begin"/> enters and leaves to
- /// point to the current
- /// <see cref="Toplevel"/> .
- /// </summary>
- /// <remarks>
- /// This will only be distinct from <see cref="Application.Top"/> in scenarios where <see cref="Toplevel.IsOverlappedContainer"/> is <see langword="true"/>.
- /// </remarks>
- /// <value>The current.</value>
- public static Toplevel? Current { get; internal set; }
-
- /// <summary>
- /// If <paramref name="topLevel"/> is not already Current and visible, finds the last Modal Toplevel in the stack and makes it Current.
- // 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 ();
- }
- }
- }
-
- /// <summary>
- /// Move to the next Overlapped child from the <see cref="OverlappedTop"/> and set it as the <see cref="Application.Top"/> if
- /// it is not already.
- /// </summary>
- /// <param name="top"></param>
- /// <returns></returns>
- 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;
- }
-
- /// <summary>Move to the next Overlapped child from the <see cref="OverlappedTop"/>.</summary>
- 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)
/// The items will be arranged from end (right/bottom) to start (left/top).
/// The items will be arranged from end (right/bottom) to start (left/top).
/// </summary>
/// </summary>
- /// <remarks>
- /// Not implemented.
- /// </remarks>
EndToStart = 1,
EndToStart = 1,
/// <summary>
/// <summary>
/// At least one space will be added between items. Useful for justifying text where at least one space is needed.
/// At least one space will be added between items. Useful for justifying text where at least one space is needed.
/// </summary>
/// </summary>
/// <remarks>
/// <remarks>
- /// <para>
- /// If the total size of the items is greater than the container size, the space between items will be ignored
- /// starting from the end.
- /// </para>
+ /// If the total size of the items is greater than the container size, the space between items will be ignored
+ /// starting from the end.
/// </remarks>
/// </remarks>
AddSpaceBetweenItems = 2,
AddSpaceBetweenItems = 2,
/// <summary>
/// <summary>
- /// When aligning via <see cref="Alignment.Start"/> or <see cref="Alignment.End"/>, the item opposite to the alignment (the first or last item) will be ignored.
+ /// When aligning via <see cref="Alignment.Start"/> or <see cref="Alignment.End"/>, the item opposite to the alignment
+ /// (the first or last item) will be ignored.
/// </summary>
/// </summary>
/// <remarks>
/// <remarks>
- /// <para>
- /// 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).
- /// </para>
+ /// If the container is smaller than the total size of the items, the end items will be clipped (their locations
@@ -97,49 +97,49 @@ public readonly partial record struct Color
)
)
{
{
return (formatString, formatProvider) switch
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 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) =>
- // 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
- // 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.
- // 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 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) =>
+ // 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
+ // 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.
+ // 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 (
?? throw new InvalidOperationException (
$"Unable to create string from Color with value {Argb}, using format string {formatString}"
$"Unable to create string from Color with value {Argb}, using format string {formatString}"
);
);
@@ -205,7 +205,7 @@ public readonly partial record struct Color
/// <summary>Converts the provided <see langword="string"/> to a new <see cref="Color"/> value.</summary>
/// <summary>Converts the provided <see langword="string"/> to a new <see cref="Color"/> value.</summary>
/// <param name="text">
/// <param name="text">
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#ARGB", "#AARRGGBB", "rgb(r,g,b)",
/// 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 <see cref="Gui.ColorName"/> string values.
+ /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="ColorName16"/> string values.
/// </param>
/// </param>
/// <param name="formatProvider">
/// <param name="formatProvider">
/// If specified and not <see langword="null"/>, will be passed to
/// If specified and not <see langword="null"/>, will be passed to
@@ -246,7 +246,7 @@ public readonly partial record struct Color
/// </summary>
/// </summary>
/// <param name="text">
/// <param name="text">
/// The text to analyze. Formats supported are "#RGB", "#RRGGBB", "#RGBA", "#AARRGGBB", "rgb(r,g,b)",
/// 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 <see cref="Gui.ColorName"/> string values.
+ /// "rgb(r,g,b,a)", "rgba(r,g,b)", "rgba(r,g,b,a)", and any of the <see cref="ColorName16"/> string values.
/// </param>
/// </param>
/// <param name="formatProvider">
/// <param name="formatProvider">
/// Optional <see cref="IFormatProvider"/> to provide parsing services for the input text.
/// Optional <see cref="IFormatProvider"/> to provide parsing services for the input text.
@@ -265,95 +265,95 @@ public readonly partial record struct Color
public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvider = null)
public static Color Parse (ReadOnlySpan<char> text, IFormatProvider? formatProvider = null)
{
{
return text switch
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..]
- 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); }
/// <summary>Gets the "closest" named color to this <see cref="Color"/> value.</summary>
/// <summary>Gets the "closest" named color to this <see cref="Color"/> value.</summary>
/// <param name="inputColor"></param>
/// <param name="inputColor"></param>
/// <remarks>
/// <remarks>
/// Distance is defined here as the Euclidean distance between each color interpreted as a <see cref="Vector3"/>.
/// Distance is defined here as the Euclidean distance between each color interpreted as a <see cref="Vector3"/>.
- /// <para/>
- /// The order of the values in the passed Vector3 must be
/// Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
/// Provides a collection of <see cref="KeyBinding"/> objects bound to a <see cref="Key"/>.
/// </summary>
/// </summary>
+/// <seealso cref="Application.KeyBindings"/>
+/// <seealso cref="View.KeyBindings"/>
+/// <seealso cref="Command"/>
public class KeyBindings
public class KeyBindings
{
{
/// <summary>
/// <summary>
@@ -284,12 +287,21 @@ public class KeyBindings
return Array.Empty<Command> ();
return Array.Empty<Command> ();
}
}
- /// <summary>Gets the Key used by a set of commands.</summary>
- /// <remarks></remarks>
+ /// <summary>Gets the first Key bound to the set of commands specified by <paramref name="commands"/>.</summary>
/// <param name="commands">The set of commands to search.</param>
/// <param name="commands">The set of commands to search.</param>
- /// <returns>The <see cref="Key"/> used by a <see cref="Command"/></returns>
- /// <exception cref="InvalidOperationException">If no matching set of commands was found.</exception>
- public Key GetKeyFromCommands (params Command [] commands) { return Bindings.First (a => a.Value.Commands.SequenceEqual (commands)).Key; }
+ /// <returns>The first <see cref="Key"/> bound to the set of commands specified by <paramref name="commands"/>. <see langword="null"/> if the set of caommands was not found.</returns>
+ public Key? GetKeyFromCommands (params Command [] commands)
+ {
+ return Bindings.FirstOrDefault (a => a.Value.Commands.SequenceEqual (commands)).Key;
+ }
+
+ /// <summary>Gets Keys bound to the set of commands specified by <paramref name="commands"/>.</summary>
+ /// <param name="commands">The set of commands to search.</param>
+ /// <returns>The <see cref="Key"/>s bound to the set of commands specified by <paramref name="commands"/>. An empty list if the set of caommands was not found.</returns>
+ public IEnumerable<Key> GetKeysFromCommands (params Command [] commands)
+ {
+ return Bindings.Where (a => a.Value.Commands.SequenceEqual (commands)).Select (a => a.Key);
+ }
/// <summary>Removes a <see cref="KeyBinding"/> from the collection.</summary>
/// <summary>Removes a <see cref="KeyBinding"/> from the collection.</summary>
+ /// Helper to configure all things Command related for a View. Called from the View constructor.
+ /// </summary>
+ 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;
+ });
+ }
+
+ /// <summary>
+ /// Called when the user is accepting the state of the View and the <see cref="Command.Accept"/> has been invoked. Calls <see cref="OnAccepting"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
+ /// event. The default <see cref="Command.Accept"/> handler calls this method.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// The <see cref="Accepting"/> event should raised after the state of the View has changed (after <see cref="Selecting"/> is raised).
+ /// </para>
+ /// <para>
+ /// If the Accepting event is not handled, <see cref="Command.Accept"/> will be invoked on the SuperView, enabling default Accept behavior.
+ /// </para>
+ /// <para>
+ /// If a peer-View raises the Accepting event and the event is not cancelled, the <see cref="Command.Accept"/> will be invoked on the
+ /// first Button in the SuperView that has <see cref="Button.IsDefault"/> set to <see langword="true"/>.
+ /// </para>
+ /// </remarks>
+ /// <returns>
+ /// <see langword="null"/> if no event was raised; input proessing should continue.
+ /// <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should continue.
+ /// <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
+ /// Cancelable event raised when the user is accepting the state of the View and the <see cref="Command.Accept"/> has been invoked. Set
+ /// CommandEventArgs.Cancel to cancel the event.
+ /// </summary>
+ /// <remarks>
+ /// <para>
+ /// See <see cref="View.RaiseAccepting"/> for more information.
+ /// </para>
+ /// </remarks>
+ public event EventHandler<CommandEventArgs>? Accepting;
+
+ /// <summary>
+ /// Called when the user has performed an action (e.g. <see cref="Command.Select"/>) causing the View to change state. Calls <see cref="OnSelecting"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
+ /// event. The default <see cref="Command.Select"/> handler calls this method.
+ /// </summary>
+ /// <remarks>
+ /// The <see cref="Selecting"/> event should raised after the state of the View has been changed and before see <see cref="Accepting"/>.
+ /// </remarks>
+ /// <returns>
+ /// <see langword="null"/> if no event was raised; input proessing should continue.
+ /// <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should continue.
+ /// <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
+ /// Cancelable event raised when the user has performed an action (e.g. <see cref="Command.Select"/>) causing the View to change state.
+ /// CommandEventArgs.Cancel to <see langword="true"/> to cancel the state change.
+ /// </summary>
+ public event EventHandler<CommandEventArgs>? Selecting;
+
+ /// <summary>
+ /// Called when the View is handling the user pressing the View's <see cref="HotKey"/>s. Calls <see cref="OnHandlingHotKey"/> which can be cancelled; if not cancelled raises <see cref="Accepting"/>.
+ /// event. The default <see cref="Command.HotKey"/> handler calls this method.
+ /// </summary>
+ /// <returns>
+ /// <see langword="null"/> if no event was raised; input proessing should continue.
+ /// <see langword="false"/> if the event was raised and was not handled (or cancelled); input proessing should continue.
+ /// <see langword="true"/> if the event was raised and handled (or cancelled); input proessing should stop.
+ /// </returns>
+ 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;
+ }
+
+ /// <summary>
+ /// Called when the View is handling the user pressing the View's <see cref="HotKey"/>. Set CommandEventArgs.Cancel to
+ /// <see langword="true"/> to stop processing.
+ /// </summary>
+ /// <param name="args"></param>
+ /// <returns><see langword="true"/> to stop processing.</returns>
+ /// <summary>Returns all commands that are supported by this <see cref="View"/>.</summary>
+ /// <returns></returns>
+ public IEnumerable<Command> GetSupportedCommands () { return CommandImplementations.Keys; }
+
+ /// <summary>
+ /// Invokes the specified commands.
+ /// </summary>
+ /// <param name="commands">The set of commands to invoke.</param>
+ /// <param name="key">The key that caused the command to be invoked, if any. This will be passed as context with the command.</param>
+ /// <param name="keyBinding">The key binding that was bound to the key and caused the invocation, if any. This will be passed as context with the command.</param>
+ /// <returns>
+ /// <see langword="null"/> if no command was found; input proessing should continue.
+ /// <see langword="false"/> if at least one command was invoked and was not handled (or cancelled); input proessing should continue.
+ /// <see langword="true"/> if at least one command was invoked the command was handled (or cancelled); input proessing should stop.
+ // 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;
+ }
+
+ /// <summary>Invokes the specified command.</summary>
+ /// <param name="command">The command to invoke.</param>
+ /// <param name="key">The key that caused the command to be invoked, if any. This will be passed as context with the command.</param>
+ /// <param name="keyBinding">The key binding that was bound to the key and caused the invocation, if any. This will be passed as context with the command.</param>
+ /// <returns>
+ /// <see langword="null"/> if no command was found; input proessing should continue.
+ /// <see langword="false"/> if the command was invoked and was not handled (or cancelled); input proessing should continue.
+ /// <see langword="true"/> if the command was invoked the command was handled (or cancelled); input proessing should stop.
+ /// See the View Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+ /// </para>
+ /// <para>
/// Negative sizes are not supported.
/// Negative sizes are not supported.
/// </para>
/// </para>
/// <para>
/// <para>
@@ -55,6 +58,9 @@ public partial class View
/// </summary>
/// </summary>
/// <remarks>a>
/// <remarks>a>
/// <para>
/// <para>
+ /// See the View Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
+ /// </para>
+ /// <para>
/// If the content size was not explicitly set by <see cref="SetContentSize"/>, and the View has no visible subviews, <see cref="GetContentSize ()"/> will return the
/// If the content size was not explicitly set by <see cref="SetContentSize"/>, and the View has no visible subviews, <see cref="GetContentSize ()"/> will return the
/// size of
/// size of
/// <see cref="Viewport"/>.
/// <see cref="Viewport"/>.
@@ -85,6 +91,9 @@ public partial class View
/// size or not.
/// size or not.
/// </summary>
/// </summary>
/// <remarks>
/// <remarks>
+ /// <para>
+ /// See the View Layout Deep Dive for more information: <see href="https://gui-cs.github.io/Terminal.GuiV2Docs/docs/layout.html"/>
@@ -20,10 +20,9 @@ public enum ViewDiagnosticFlags : uint
Padding = 0b_0000_0010,
Padding = 0b_0000_0010,
/// <summary>
/// <summary>
- /// When enabled, <see cref="Adornment.OnMouseEnter(Gui.MouseEvent)"/> and <see cref="Adornment.OnMouseLeave(Gui.MouseEvent)"/>
- /// 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 <see cref="View.MouseEnter"/> and <see cref="View.MouseLeave"/>.
- /// The view that was found at the <paramref name="location"/> coordinate.
- /// <see langword="null"/> if no view was found.
- /// </returns>
-
- // 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 ();
@@ -681,32 +756,48 @@ public partial class View // Focus and cross-view navigation management (TabStop
#region Tab/Focus Handling
#region Tab/Focus Handling
/// <summary>
/// <summary>
- /// 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
+ /// Raised when the <see cref="Visible"/> value is being changed. Can be cancelled by setting Cancel to
+ /// <see langword="true"/>.
+ /// </summary>
+ public event EventHandler<CancelEventArgs<bool>>? VisibleChanging;
+
+ /// <summary>Called when <see cref="Visible"/> has changed.</summary>
+ protected virtual void OnVisibleChanged () { }
- /// <summary>Event fired when the <see cref="Visible"/> value is being changed.</summary>
+ /// <summary>Raised when <see cref="Visible"/> has changed.</summary>
public event EventHandler? VisibleChanged;
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
/// <summary>
/// <summary>
/// INTERNAL Indicates whether all views up the Superview hierarchy are visible.
/// INTERNAL Indicates whether all views up the Superview hierarchy are visible.
/// </summary>
/// </summary>
/// <param name="view">The view to test.</param>
/// <param name="view">The view to test.</param>
- /// <returns> <see langword="false"/> if `view.Visible` is <see langword="false"/> or any Superview is not visible, <see langword="true"/> otherwise.</returns>
+ /// <returns>
+ /// <see langword="false"/> if `view.Visible` is <see langword="false"/> or any Superview is not visible,
+ /// <see langword="true"/> otherwise.
+ /// </returns>
internal static bool CanBeVisible (View view)
internal static bool CanBeVisible (View view)
{
{
if (!view.Visible)
if (!view.Visible)
@@ -416,7 +408,7 @@ public partial class View : Responder, ISupportInitializeNotification
return true;
return true;
}
}
-#endregion Visibility
+#endregion Visibility
#region Title
#region Title
@@ -492,18 +484,16 @@ public partial class View : Responder, ISupportInitializeNotification
+ /// <summary>Raised when the <see cref="CheckBox"/> state is changing.</summary>
/// <remarks>
/// <remarks>
- /// <para>
- /// Cycles through the states <see cref="CheckState.None"/>, <see cref="CheckState.Checked"/>, and <see cref="CheckState.UnChecked"/>.
- /// </para>
- /// <para>
- /// If the <see cref="CheckedStateChanging"/> event is not canceled, the <see cref="CheckedState"/> will be updated and the <see cref="Command.Accept"/> event will be raised.
- /// </para>
+ /// <para>
+ /// This event can be cancelled. If cancelled, the <see cref="CheckBox"/> will not change its state.
+ /// </para>
+ /// </remarks>
+ public event EventHandler<CancelEventArgs<CheckState>>? CheckedStateChanging;
+
+ /// <summary>Called when the <see cref="CheckBox"/> state has changed.</summary>