Skip to content

Commit

Permalink
Merge pull request #3766 from tig/v2_3764-Common-Events
Browse files Browse the repository at this point in the history
Fixes #3764. Makes common UI interactions consistent across built-in Views
  • Loading branch information
tig authored Oct 9, 2024
2 parents 5d2e28b + 2970e55 commit 426b991
Show file tree
Hide file tree
Showing 159 changed files with 5,511 additions and 2,537 deletions.
4 changes: 2 additions & 2 deletions CommunityToolkitExample/LoginView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
};
Expand Down
2 changes: 1 addition & 1 deletion Example/Example.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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")
{
Expand Down
2 changes: 1 addition & 1 deletion NativeAot/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -93,7 +93,7 @@ public ExampleWindow ()
};

// When login button is clicked display a message popup
btnLogin.Accept += (s, e) =>
btnLogin.Accepting += (s, e) =>

Check failure on line 96 in NativeAot/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 96 in NativeAot/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 96 in NativeAot/Program.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 96 in NativeAot/Program.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 96 in NativeAot/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 96 in NativeAot/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)
{
if (userNameText.Text == "admin" && passwordText.Text == "password")
{
Expand Down
4 changes: 2 additions & 2 deletions ReactiveExample/LoginView.cs
Original file line number Diff line number Diff line change
Expand Up @@ -108,7 +108,7 @@ public LoginView (LoginViewModel viewModel)

login
.Events ()
.Accept
.Accepting
.InvokeCommand (ViewModel, x => x.Login)
.DisposeWith (_disposable);
})
Expand All @@ -120,7 +120,7 @@ public LoginView (LoginViewModel viewModel)

clear
.Events ()
.Accept
.Accepting
.InvokeCommand (ViewModel, x => x.ClearCommand)
.DisposeWith (_disposable);
})
Expand Down
2 changes: 1 addition & 1 deletion SelfContained/Program.cs
Original file line number Diff line number Diff line change
Expand Up @@ -92,7 +92,7 @@ public ExampleWindow ()
};

// When login button is clicked display a message popup
btnLogin.Accept += (s, e) =>
btnLogin.Accepting += (s, e) =>

Check failure on line 95 in SelfContained/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 95 in SelfContained/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 95 in SelfContained/Program.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 95 in SelfContained/Program.cs

View workflow job for this annotation

GitHub Actions / Build and Publish to Nuget.org

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 95 in SelfContained/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)

Check failure on line 95 in SelfContained/Program.cs

View workflow job for this annotation

GitHub Actions / build_release

'Button' does not contain a definition for 'Accepting' and no accessible extension method 'Accepting' accepting a first argument of type 'Button' could be found (are you missing a using directive or an assembly reference?)
{
if (userNameText.Text == "admin" && passwordText.Text == "password")
{
Expand Down
16 changes: 16 additions & 0 deletions Terminal.Gui/Application/Application.Initialization.cs
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#nullable enable
using System.Diagnostics;
using System.Diagnostics.CodeAnalysis;
using System.Reflection;

Expand Down Expand Up @@ -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.
Expand All @@ -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 ();
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/Application/Application.Keyboard.cs
Original file line number Diff line number Diff line change
Expand Up @@ -169,7 +169,7 @@ public static bool OnKeyDown (Key keyEvent)
);
}

if (CommandImplementations.TryGetValue (command, out Func<CommandContext, bool?>? implementation))
if (CommandImplementations.TryGetValue (command, out View.CommandImplementation? implementation))
{
var context = new CommandContext (command, keyEvent, appBinding); // Create the context here

Expand Down Expand Up @@ -418,7 +418,7 @@ internal static List<KeyBinding> GetViewKeyBindings ()
/// <summary>
/// Commands for Application.
/// </summary>
private static Dictionary<Command, Func<CommandContext, bool?>>? CommandImplementations { get; set; }
private static Dictionary<Command, View.CommandImplementation>? CommandImplementations { get; set; }

private static void ReplaceKey (Key oldKey, Key newKey)
{
Expand Down
60 changes: 39 additions & 21 deletions Terminal.Gui/Application/Application.Mouse.cs
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,13 @@ namespace Terminal.Gui;

public static partial class Application // Mouse handling
{
internal static Point? _lastMousePosition;

/// <summary>
/// Gets the most recent position of the mouse.
/// </summary>
public static Point? GetLastMousePosition () { return _lastMousePosition; }

/// <summary>Disable or enable the mouse. The mouse is enabled by default.</summary>
[SerializableConfigurationProperty (Scope = typeof (SettingsScope))]
public static bool IsMouseDisabled { get; set; }
Expand Down Expand Up @@ -132,12 +139,18 @@ private static void OnUnGrabbedMouse (View? view)
/// <param name="mouseEvent">The mouse event with coordinates relative to the screen.</param>
internal static void OnMouseEvent (MouseEvent mouseEvent)
{
_lastMousePosition = mouseEvent.ScreenPosition;

if (IsMouseDisabled)
{
return;
}

List<View?> currentViewsUnderMouse = View.GetViewsUnderMouse (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<View?> currentViewsUnderMouse = View.GetViewsUnderMouse (mouseEvent.ScreenPosition);

View? deepestViewUnderMouse = currentViewsUnderMouse.LastOrDefault ();

Expand All @@ -159,7 +172,7 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
return;
}

if (GrabMouse (deepestViewUnderMouse, mouseEvent))
if (HandleMouseGrab (deepestViewUnderMouse, mouseEvent))
{
return;
}
Expand All @@ -180,44 +193,47 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
return;
}

// TODO: Move this after call to RaiseMouseEnterLeaveEvents once MouseEnter/Leave don't use MouseEvent anymore.
MouseEvent? me;
// Create a view-relative mouse event to send to the view that is under the mouse.
MouseEvent? viewMouseEvent;

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,
ScreenPosition = mouseEvent.ScreenPosition,
View = deepestViewUnderMouse
};
}
else if (deepestViewUnderMouse.ViewportToScreen (Rectangle.Empty with { Size = deepestViewUnderMouse.Viewport.Size }).Contains (mouseEvent.Position))
{
Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.Position);
Point viewportLocation = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);

me = new ()
viewMouseEvent = new ()
{
Position = viewportLocation,
Flags = mouseEvent.Flags,
ScreenPosition = mouseEvent.Position,
ScreenPosition = mouseEvent.ScreenPosition,
View = deepestViewUnderMouse
};
}
else
{
Debug.Fail ("This should never happen");
// The mouse was outside any View's Viewport.

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

return;
}

RaiseMouseEnterLeaveEvents (me.ScreenPosition, currentViewsUnderMouse);
RaiseMouseEnterLeaveEvents (viewMouseEvent.ScreenPosition, currentViewsUnderMouse);

WantContinuousButtonPressedView = deepestViewUnderMouse.WantContinuousButtonPressed ? deepestViewUnderMouse : null;

while (deepestViewUnderMouse.NewMouseEvent (me) is not true && MouseGrabView is not { })
while (deepestViewUnderMouse.NewMouseEvent (viewMouseEvent) is not true && MouseGrabView is not { })
{
if (deepestViewUnderMouse is Adornment adornmentView)
{
Expand All @@ -233,38 +249,38 @@ internal static void OnMouseEvent (MouseEvent mouseEvent)
break;
}

Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.Position);
Point boundsPoint = deepestViewUnderMouse.ScreenToViewport (mouseEvent.ScreenPosition);

me = new ()
viewMouseEvent = new ()
{
Position = boundsPoint,
Flags = mouseEvent.Flags,
ScreenPosition = mouseEvent.Position,
ScreenPosition = mouseEvent.ScreenPosition,
View = deepestViewUnderMouse
};
}
}

internal static bool GrabMouse (View? deepestViewUnderMouse, MouseEvent mouseEvent)
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.Position);
Point frameLoc = MouseGrabView.ScreenToViewport (mouseEvent.ScreenPosition);

var viewRelativeMouseEvent = new MouseEvent
{
Position = frameLoc,
Flags = mouseEvent.Flags,
ScreenPosition = mouseEvent.Position,
ScreenPosition = mouseEvent.ScreenPosition,
View = deepestViewUnderMouse ?? MouseGrabView
};

Expand Down Expand Up @@ -297,6 +313,7 @@ internal static void RaiseMouseEnterLeaveEvents (Point screenPosition, List<View
{
// Tell any views that are no longer under the mouse that the mouse has left
List<View?> viewsToLeave = _cachedViewsUnderMouse.Where (v => v is { } && !currentViewsUnderMouse.Contains (v)).ToList ();

foreach (View? view in viewsToLeave)
{
if (view is null)
Expand All @@ -322,7 +339,8 @@ internal static void RaiseMouseEnterLeaveEvents (Point screenPosition, List<View
}

_cachedViewsUnderMouse.Add (view);
bool raise = false;
var raise = false;

if (view is Adornment { Parent: { } } adornmentView)
{
Point superViewLoc = adornmentView.Parent.SuperView?.ScreenToViewport (screenPosition) ?? screenPosition;
Expand Down
3 changes: 1 addition & 2 deletions Terminal.Gui/Application/Application.cs
Original file line number Diff line number Diff line change
Expand Up @@ -197,6 +197,7 @@ internal static void ResetState (bool ignoreDisposed = false)
IsInitialized = false;

// Mouse
_lastMousePosition = null;
_cachedViewsUnderMouse.Clear ();
WantContinuousButtonPressedView = null;
MouseEvent = null;
Expand All @@ -214,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:
Expand Down
17 changes: 13 additions & 4 deletions Terminal.Gui/Configuration/ConfigurationManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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)
Expand Down
2 changes: 1 addition & 1 deletion Terminal.Gui/Configuration/ThemeManager.cs
Original file line number Diff line number Diff line change
Expand Up @@ -110,7 +110,7 @@ internal static string SelectedTheme
string oldTheme = _theme;
_theme = value;

if (oldTheme != _theme && Settings! ["Themes"]?.PropertyValue is Dictionary<string, ThemeScope> themes && themes.ContainsKey (_theme))
if ((oldTheme != _theme || oldTheme != Settings! ["Theme"].PropertyValue as string) && Settings! ["Themes"]?.PropertyValue is Dictionary<string, ThemeScope> themes && themes.ContainsKey (_theme))
{
Settings! ["Theme"].PropertyValue = _theme;
Instance.OnThemeChanged (oldTheme);
Expand Down
2 changes: 2 additions & 0 deletions Terminal.Gui/ConsoleDrivers/WindowsDriver.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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);
Expand Down
4 changes: 2 additions & 2 deletions Terminal.Gui/FileServices/DefaultFileOperations.cs
Original file line number Diff line number Diff line change
Expand Up @@ -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 ();
Expand Down
Loading

0 comments on commit 426b991

Please sign in to comment.