Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Fixes #3691 - Adds ViewArrangement.Popover #3852

Draft
wants to merge 98 commits into
base: v2_develop
Choose a base branch
from
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
Show all changes
98 commits
Select commit Hold shift + click to select a range
f314848
Added Applicaton.Popover.
tig Sep 19, 2024
4073e23
Popover prototype
tig Sep 19, 2024
fd632e3
Merge branch 'v2_develop' into v2_3691-Popover
tig Sep 19, 2024
4da7f48
Merged v2_3750
tig Sep 22, 2024
9f84b3a
Testing highlight
tig Sep 22, 2024
aa5cf82
Merged latest
tig Sep 22, 2024
9222972
Merge branch 'v2_3750-MouseEnter' into v2_3691-Popover
tig Sep 23, 2024
f589416
Fixed click outside issue
tig Sep 23, 2024
5d1d6cb
Fixed DialogTests
tig Sep 23, 2024
c623206
Fixed click outside issue (agbain)
tig Sep 23, 2024
e2a4620
Enabled mouse wheel in Bar
tig Sep 23, 2024
7b7649a
Enabled mouse wheel in Bar
tig Sep 23, 2024
bfefabc
Progress. Broke arrangement
tig Sep 23, 2024
7346afb
Merge branch 'v2_develop' into v2_3691-Popover
tig Sep 24, 2024
ec847a3
Rebased onto v2_3750-MouseEnter
tig Sep 24, 2024
850c308
Merge branch 'v2_develop' into v2_3691-Popover
tig Sep 26, 2024
3e8ed7a
Added popover tests.
tig Sep 26, 2024
1369bb2
Can't set ForceDriver to empty in Resources/config.json.
tig Sep 26, 2024
3aa3598
added BUGBUG
tig Sep 26, 2024
ae4b172
Made Position/ScreenPosition clear
tig Sep 26, 2024
4e6bf04
Added View.IsInHierarchy tests
tig Sep 26, 2024
a5044df
Added Contextmenuv2 scenario.
tig Sep 26, 2024
06bcefe
Implemented CM2 in TextView
tig Sep 26, 2024
a708648
Removed unneeded CM stuff from testhelpers
tig Sep 26, 2024
d888de8
Shortcut API docs
tig Sep 26, 2024
8954fec
Fixed keybinding unit tests
tig Sep 27, 2024
47a9ef7
Fixed mouse handling
tig Sep 27, 2024
23c3ec8
Fighting with CM related unit test failures
tig Sep 27, 2024
493ece7
Unit tests pass. I think.
tig Sep 27, 2024
16e7689
Shortcut code cleanup
tig Sep 27, 2024
5d9e0d1
TextView uses new CM2
tig Sep 27, 2024
fb85d44
Starting on OnSelect etc...
tig Sep 28, 2024
bdc947e
Starting on OnSelect etc...
tig Sep 28, 2024
8b54152
Merge branch 'v2_develop' into v2_3691-Popover
tig Oct 2, 2024
6800168
Merged and debugged and mostly fixed stuff.
tig Oct 3, 2024
5d67850
Merge branch 'v2_develop' into v2_3691-Popover
tig Oct 7, 2024
be46664
Merged - builds
tig Oct 7, 2024
a68dec2
Fixed ContextMenuv2
tig Oct 7, 2024
040f73a
Merge branch 'v2_3691-Popover' of tig:tig/Terminal.Gui into v2_3691-P…
tig Oct 7, 2024
8346348
ContextMenu is working again.
tig Oct 8, 2024
22dc075
Ugh. ANd fixed button api docs
tig Oct 8, 2024
125bc4e
merged
tig Oct 11, 2024
ed0b179
Fixed DrawHorizontalShadowTransparent (vertical was already fixed).
tig Oct 11, 2024
4fef1c2
Merge branch 'v2_3691-Popover' of tig:tig/Terminal.Gui into v2_3691-P…
tig Oct 11, 2024
f5ab50f
Made Scenarios compatible with #nullable enable
tig Oct 11, 2024
90fb7cc
Merge branch 'v2_develop' into v2_3691-Popover
tig Oct 11, 2024
e81ab44
Undid some keybinding stuff
tig Oct 11, 2024
3d5d101
Fixed stuff
tig Oct 11, 2024
a243cc8
Sped up unit tests
tig Oct 11, 2024
e63fb7c
Sped up unit tests 2
tig Oct 11, 2024
2f3dbe7
Sped up unit tests 3
tig Oct 11, 2024
883ab67
Messing with menus
tig Oct 13, 2024
4547d1b
Merged v2_develop
tig Nov 5, 2024
ce572e8
Merge branch 'v2_develop' into v2_3691-Popover
tig Nov 7, 2024
8f27e8e
Merge branch 'v2_3691-Popover' of tig:tig/Terminal.Gui into v2_3691-P…
tig Nov 10, 2024
3fee0aa
merged latest v2_develop
tig Nov 10, 2024
bc23482
merged latest v2_develop
tig Nov 10, 2024
cf105dd
mergfed - probably broken
tig Nov 19, 2024
aa0499d
mergfed - probably broken2
tig Nov 19, 2024
b1e1da4
Added more Popover unit tests
tig Nov 24, 2024
2cc7297
Added more Popover unit tests2
tig Nov 24, 2024
9f2635d
Fixed positioning bug
tig Nov 24, 2024
677aaae
Fixed mouse bug
tig Nov 24, 2024
10711c7
Fixed Bar draw issue
tig Nov 24, 2024
15d9dfd
WIP
tig Nov 24, 2024
d554c70
Fixed merge issues. Code cleanup
tig Nov 25, 2024
3d1ec0e
Merged latest v2_develop
tig Nov 26, 2024
8bb63f4
Merge branch 'v2_develop' into v2_3691-ViewArrangement-Popover
tig Nov 26, 2024
7975004
Merge branch 'v2_develop' into v2_3691-ViewArrangement-Popover
tig Dec 5, 2024
4f5265c
Merge branch 'v2_develop' into v2_3691-ViewArrangement-Popover
tig Dec 10, 2024
b735b48
merge v2_develop
tig Dec 10, 2024
9f7d358
CM2 sorta works
tig Dec 10, 2024
e0014fa
Enabled Bar subclasses to have IDesignable
tig Dec 10, 2024
3880cc7
Added ViewportSettings.Transparent
tig Dec 14, 2024
4d6913b
Region -> nullable enable
tig Dec 14, 2024
b639b2c
Added ViewportSettigs Editor
tig Dec 14, 2024
e9e3588
merged v2_develop
tig Mar 6, 2025
be8210a
merged v2_develop part 2
tig Mar 6, 2025
3b89515
merged v2_develop part 3
tig Mar 6, 2025
3640334
Merge branch 'v2_3691-ViewArrangement-Popover' of tig:tig/Terminal.Gu…
tig Mar 9, 2025
51829e4
Merged latest
tig Mar 9, 2025
6fff834
WIP: GetViewsUnderMouse
tig Mar 9, 2025
4241cb3
WIP: More GetViewsUnderMouse work
tig Mar 9, 2025
57f20c6
Merge branch 'v2_develop' into v2_3691-ViewArrangement-Popover
tig Mar 10, 2025
c30742f
Bars works again
tig Mar 10, 2025
ed44e16
Added unit tests
tig Mar 10, 2025
804fdbc
CM now works
tig Mar 10, 2025
22e6e98
MenuItemv2 POC
tig Mar 10, 2025
b00cee2
SubMenu POC
tig Mar 13, 2025
bd5d3e8
Merge branch 'v2_3691-ViewArrangement-Popover' of tig:tig/Terminal.Gu…
tig Mar 13, 2025
856fe8a
CommandNotBound
tig Mar 13, 2025
a138289
More POC
tig Mar 13, 2025
7d54eb7
Optimize Margin to not defer draw if there's no shadow
tig Mar 13, 2025
06fb4da
Logger cleanup
tig Mar 13, 2025
0430fdc
Reverted Generic
tig Mar 13, 2025
33d4f3d
Merged.
tig Mar 13, 2025
3f3d2b5
Cascading mostly working
tig Mar 16, 2025
a5830c3
Merge branch 'v2_develop' into v2_3691-ViewArrangement-Popover
tig Mar 17, 2025
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 2 additions & 0 deletions Terminal.Gui/Application/Application.Initialization.cs
Original file line number Diff line number Diff line change
Expand Up @@ -162,6 +162,8 @@ internal static void InternalInit (

SynchronizationContext.SetSynchronizationContext (new MainLoopSyncContext ());

PopoverHost.Init ();

MainThreadId = Thread.CurrentThread.ManagedThreadId;
bool init = Initialized = true;
InitializedChanged?.Invoke (null, new (init));
Expand Down
8 changes: 8 additions & 0 deletions Terminal.Gui/Application/Application.Keyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -20,6 +20,14 @@ public static bool RaiseKeyDownEvent (Key key)
return true;
}

if (PopoverHost is { Visible: true })
{
if (PopoverHost.NewKeyDownEvent (key))
{
return true;
}
}

if (Top is null)
{
foreach (Toplevel topLevel in TopLevels.ToList ())
Expand Down
18 changes: 18 additions & 0 deletions Terminal.Gui/Application/Application.Mouse.cs
Original file line number Diff line number Diff line change
@@ -1,5 +1,6 @@
#nullable enable
using System.ComponentModel;
using System.Diagnostics;

namespace Terminal.Gui;

Expand Down Expand Up @@ -168,6 +169,22 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
return;
}

// Dismiss the Popover if the user clicked outside of it
if (PopoverHost is { Visible: true }
&& View.IsInHierarchy (PopoverHost, deepestViewUnderMouse, includeAdornments: true) is false
&& (mouseEvent.Flags.HasFlag (MouseFlags.Button1Pressed)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button2Pressed)
|| mouseEvent.Flags.HasFlag (MouseFlags.Button3Pressed)))
{

PopoverHost.Visible = false;

// Recurse once
RaiseMouseEvent (mouseEvent);

return;
}

if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
{
return;
Expand Down Expand Up @@ -216,6 +233,7 @@ internal static void RaiseMouseEvent (MouseEventArgs mouseEvent)
else
{
// The mouse was outside any View's Viewport.
//Debug.Fail ("this should not happen.");

// Debug.Fail ("This should never happen. If it does please file an Issue!!");

Expand Down
21 changes: 21 additions & 0 deletions Terminal.Gui/Application/Application.Popover.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,21 @@
#nullable enable
using static Unix.Terminal.Curses;

namespace Terminal.Gui;

public static partial class Application // Popover handling
{
/// <summary>Gets the Application <see cref="PopoverHost"/>.</summary>
/// <remarks>
/// <para>
/// Any View added as a SubView will be a Popover.
/// </para>
/// <para>
/// To show or hide a Popover, set the <see cref="View.Visible"/> property of the PopoverHost.
/// </para>
/// <para>
/// If the user clicks anywhere not occulded by a SubView of the PopoverHost, the PopoverHost will be hidden.
/// </para>
/// </remarks>
public static PopoverHost? PopoverHost { get; set; }
}
14 changes: 12 additions & 2 deletions Terminal.Gui/Application/Application.Run.cs
Original file line number Diff line number Diff line change
Expand Up @@ -426,7 +426,16 @@ public static T Run<T> (Func<Exception, bool>? errorHandler = null, IConsoleDriv

internal static void LayoutAndDrawImpl (bool forceDraw = false)
{
bool neededLayout = View.Layout (TopLevels.Reverse (), Screen.Size);
List<View> tops = new (TopLevels);

if (PopoverHost is { Visible: true })
{
//PopoverHost.SetNeedsDraw();
//PopoverHost.SetNeedsLayout ();
tops.Insert (0, PopoverHost);
}

bool neededLayout = View.Layout (tops.ToArray ().Reverse (), Screen.Size);

if (ClearScreenNextIteration)
{
Expand All @@ -440,7 +449,7 @@ internal static void LayoutAndDrawImpl (bool forceDraw = false)
}

View.SetClipToScreen ();
View.Draw (TopLevels, neededLayout || forceDraw);
View.Draw (tops, neededLayout || forceDraw);
View.SetClipToScreen ();
Driver?.Refresh ();
}
Expand Down Expand Up @@ -554,6 +563,7 @@ internal static void OnNotifyStopRunState (Toplevel top)
public static void End (RunState runState)
{
ArgumentNullException.ThrowIfNull (runState);
PopoverHost.Cleanup();

runState.Toplevel.OnUnloaded ();

Expand Down
3 changes: 3 additions & 0 deletions Terminal.Gui/Application/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -103,6 +103,7 @@ internal static List<CultureInfo> GetAvailableCulturesFromEmbeddedResources ()
.ToList ();
}

// BUGBUG: This does not return en-US even though it's supported by default
internal static List<CultureInfo> GetSupportedCultures ()
{
CultureInfo [] cultures = CultureInfo.GetCultures (CultureTypes.AllCultures);
Expand Down Expand Up @@ -148,6 +149,8 @@ internal static void ResetState (bool ignoreDisposed = false)
t!.Running = false;
}

PopoverHost.Cleanup ();

TopLevels.Clear ();
#if DEBUG_IDISPOSABLE

Expand Down
4 changes: 4 additions & 0 deletions Terminal.Gui/Application/ApplicationNavigation.cs
Original file line number Diff line number Diff line change
Expand Up @@ -104,6 +104,10 @@ internal void SetFocused (View? value)
/// </returns>
public bool AdvanceFocus (NavigationDirection direction, TabBehavior? behavior)
{
if (Application.PopoverHost is { Visible: true })
{
return Application.PopoverHost.AdvanceFocus (direction, behavior);
}
return Application.Top is { } && Application.Top.AdvanceFocus (direction, behavior);
}
}
106 changes: 106 additions & 0 deletions Terminal.Gui/Application/PopoverHost.cs
Original file line number Diff line number Diff line change
@@ -0,0 +1,106 @@
#nullable enable
using System.Diagnostics;
using System.Net.Mime;
using static System.Net.Mime.MediaTypeNames;

namespace Terminal.Gui;

/// <summary>
/// Singleton View that hosts Views to be shown as Popovers. The host covers the whole screen and is transparent both
/// visually and to the mouse. Set <see cref="View.Visible"/> to show or hide the Popovers.
/// </summary>
/// <remarks>
/// <para>
/// If the user clicks anywhere not occulded by a SubView of the PopoverHost, the PopoverHost will be hidden.
/// </para>
/// </remarks>
public sealed class PopoverHost : View
{
/// <summary>
/// Initializes <see cref="Application.PopoverHost"/>.
/// </summary>
internal static void Init ()
{
// Setup PopoverHost
if (Application.PopoverHost is { })
{
throw new InvalidOperationException (@"PopoverHost is a singleton; Init and Cleanup must be balanced.");
}

Application.PopoverHost = new PopoverHost ();

// TODO: Add a diagnostic setting for this?
Application.PopoverHost.TextFormatter.VerticalAlignment = Alignment.End;
Application.PopoverHost.TextFormatter.Alignment = Alignment.End;
Application.PopoverHost.Text = "popoverHost";

Application.PopoverHost.BeginInit ();
Application.PopoverHost.EndInit ();
}

/// <summary>
/// Cleans up <see cref="Application.PopoverHost"/>.
/// </summary>
internal static void Cleanup ()
{
Application.PopoverHost?.Dispose ();
Application.PopoverHost = null;
}


/// <summary>
/// Creates a new instance.
/// </summary>
public PopoverHost ()
{
Id = "popoverHost";
CanFocus = true;
ViewportSettings = ViewportSettings.Transparent | ViewportSettings.TransparentMouse;
Width = Dim.Fill ();
Height = Dim.Fill ();
base.Visible = false;


AddCommand (Command.Quit, Quit);

bool? Quit (ICommandContext? ctx)
{
if (Visible)
{
Visible = false;

return true;
}

return null;
}

KeyBindings.Add (Application.QuitKey, Command.Quit);
}

/// <inheritdoc />
protected override bool OnClearingViewport () { return true; }

/// <inheritdoc />
protected override bool OnVisibleChanging ()
{
if (!Visible)
{
//ColorScheme ??= Application.Top?.ColorScheme;
//Frame = Application.Screen;

SetRelativeLayout (Application.Screen.Size);
}

return false;
}

/// <inheritdoc />
protected override void OnVisibleChanged ()
{
if (Visible)
{
SetFocus ();
}
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -52,7 +52,7 @@ public bool IsMouse (string? cur)
Flags = GetFlags (buttonCode, terminator)
};

Logging.Trace ($"{nameof (AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");
//Logging.Trace ($"{nameof (AnsiMouseParser)} handled as {input} mouse {m.Flags} at {m.Position}");

return m;
}
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/ConsoleDrivers/V2/InputProcessor.cs
Original file line number Diff line number Diff line change
Expand Up @@ -79,7 +79,7 @@ public void OnMouseEvent (MouseEventArgs a)

foreach (MouseEventArgs e in _mouseInterpreter.Process (a))
{
Logging.Trace ($"Mouse Interpreter raising {e.Flags}");
// Logging.Trace ($"Mouse Interpreter raising {e.Flags}");

// Pass on
MouseEvent?.Invoke (this, e);
Expand Down
5 changes: 5 additions & 0 deletions Terminal.Gui/Input/Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,11 @@ namespace Terminal.Gui;
/// </remarks>
public enum Command
{
/// <summary>
/// Indicates the command is not bound or invalid. Will call <see cref="View.RaiseCommandNotBound"/>.
/// </summary>
NotBound = 0,

#region Base View Commands

/// <summary>
Expand Down
68 changes: 54 additions & 14 deletions Terminal.Gui/View/View.Command.cs
Original file line number Diff line number Diff line change
Expand Up @@ -14,6 +14,9 @@ public partial class View // Command APIs
/// </summary>
private void SetupCommands ()
{
// NotBound - Invoked if no handler is bound
AddCommand (Command.NotBound, RaiseCommandNotBound);

// Enter - Raise Accepted
AddCommand (Command.Accept, RaiseAccepting);

Expand Down Expand Up @@ -50,6 +53,45 @@ private void SetupCommands ()
});
}

/// <summary>
/// Called when a command that has not been bound is invoked.
/// </summary>
/// <returns>
/// <see langword="null"/> if no event was raised; input processing should continue.
/// <see langword="false"/> if the event was raised and was not handled (or cancelled); input processing should continue.
/// <see langword="true"/> if the event was raised and handled (or cancelled); input processing should stop.
/// </returns>
protected bool? RaiseCommandNotBound (ICommandContext? 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 (OnCommandNotBound (args) || args.Cancel)
{
return true;
}

// If the event is not canceled by the virtual method, raise the event to notify any external subscribers.
CommandNotBound?.Invoke (this, args);

return CommandNotBound is null ? null : args.Cancel;
}

/// <summary>
/// Called when a command that has not been bound is invoked.
/// Set CommandEventArgs.Cancel to
/// <see langword="true"/> and return <see langword="true"/> to cancel the event. The default implementation does nothing.
/// </summary>
/// <param name="args">The event arguments.</param>
/// <returns><see langword="true"/> to stop processing.</returns>
protected virtual bool OnCommandNotBound (CommandEventArgs args) { return false; }

/// <summary>
/// Cancelable event raised when a command that has not been bound is invoked.
/// </summary>
public event EventHandler<CommandEventArgs>? CommandNotBound;

/// <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.
Expand Down Expand Up @@ -294,9 +336,7 @@ private void SetupCommands ()
{
if (!_commandImplementations.ContainsKey (command))
{
throw new NotSupportedException (
@$"A Binding was set up for the command {command} ({binding}) but that command is not supported by this View ({GetType ().Name})"
);
Logging.Warning (@$"{command} is not supported by this View ({GetType ().Name}). Binding: {binding}.");
}

// each command has its own return value
Expand Down Expand Up @@ -327,16 +367,15 @@ private void SetupCommands ()
/// </returns>
public bool? InvokeCommand<TBindingType> (Command command, TBindingType binding)
{
if (_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
if (!_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
{
return implementation (new CommandContext<TBindingType> ()
{
Command = command,
Binding = binding,
});
_commandImplementations.TryGetValue (Command.NotBound, out implementation);
}

return null;
return implementation! (new CommandContext<TBindingType> ()
{
Command = command,
Binding = binding,
});
}

/// <summary>
Expand All @@ -350,11 +389,12 @@ private void SetupCommands ()
/// </returns>
public bool? InvokeCommand (Command command)
{
if (_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
if (!_commandImplementations.TryGetValue (command, out CommandImplementation? implementation))
{
return implementation (null);
_commandImplementations.TryGetValue (Command.NotBound, out implementation);
}

return null;
return implementation! (null);

}
}
Loading
Loading