Skip to content

Commit f1bc42a

Browse files
authored
Merge pull request gui-cs#3372 from tig/v2_3370_continuous_button
Fixes gui-cs#3370. Adds visual highlight for press and hold (for `Button`, `Checkbox`, `RadioGroup`, and `Border`)
2 parents ef6e22c + d71554d commit f1bc42a

Some content is hidden

Large Commits have some content hidden by default. Use the searchbox below for content that may be hidden.

66 files changed

+2260
-1552
lines changed

Terminal.Gui/Application.cs

Lines changed: 42 additions & 44 deletions
Original file line numberDiff line numberDiff line change
@@ -318,7 +318,7 @@ internal static void InternalInit (
318318
private static void Driver_SizeChanged (object sender, SizeChangedEventArgs e) { OnSizeChanging (e); }
319319
private static void Driver_KeyDown (object sender, Key e) { OnKeyDown (e); }
320320
private static void Driver_KeyUp (object sender, Key e) { OnKeyUp (e); }
321-
private static void Driver_MouseEvent (object sender, MouseEventEventArgs e) { OnMouseEvent (e); }
321+
private static void Driver_MouseEvent (object sender, MouseEvent e) { OnMouseEvent (e); }
322322

323323
/// <summary>Gets of list of <see cref="ConsoleDriver"/> types that are available.</summary>
324324
/// <returns></returns>
@@ -1441,38 +1441,29 @@ private static void OnUnGrabbedMouse (View view)
14411441
/// </para>
14421442
/// <para>The <see cref="MouseEvent.View"/> will contain the <see cref="View"/> that contains the mouse coordinates.</para>
14431443
/// </remarks>
1444-
public static event EventHandler<MouseEventEventArgs> MouseEvent;
1444+
public static event EventHandler<MouseEvent> MouseEvent;
14451445

14461446
/// <summary>Called when a mouse event occurs. Raises the <see cref="MouseEvent"/> event.</summary>
14471447
/// <remarks>This method can be used to simulate a mouse event, e.g. in unit tests.</remarks>
14481448
/// <param name="a">The mouse event with coordinates relative to the screen.</param>
1449-
internal static void OnMouseEvent (MouseEventEventArgs a)
1449+
internal static void OnMouseEvent (MouseEvent mouseEvent)
14501450
{
14511451
if (IsMouseDisabled)
14521452
{
14531453
return;
14541454
}
14551455

14561456
// TODO: In PR #3273, FindDeepestView will return adornments. Update logic below to fix adornment mouse handling
1457-
var view = View.FindDeepestView (Current, a.MouseEvent.X, a.MouseEvent.Y);
1458-
1459-
if (view is { WantContinuousButtonPressed: true })
1460-
{
1461-
WantContinuousButtonPressedView = view;
1462-
}
1463-
else
1464-
{
1465-
WantContinuousButtonPressedView = null;
1466-
}
1457+
var view = View.FindDeepestView (Current, mouseEvent.X, mouseEvent.Y);
14671458

14681459
if (view is { })
14691460
{
1470-
a.MouseEvent.View = view;
1461+
mouseEvent.View = view;
14711462
}
14721463

1473-
MouseEvent?.Invoke (null, new (a.MouseEvent));
1464+
MouseEvent?.Invoke (null, mouseEvent);
14741465

1475-
if (a.MouseEvent.Handled)
1466+
if (mouseEvent.Handled)
14761467
{
14771468
return;
14781469
}
@@ -1481,45 +1472,52 @@ internal static void OnMouseEvent (MouseEventEventArgs a)
14811472
{
14821473
// If the mouse is grabbed, send the event to the view that grabbed it.
14831474
// The coordinates are relative to the Bounds of the view that grabbed the mouse.
1484-
Point frameLoc = MouseGrabView.ScreenToFrame (a.MouseEvent.X, a.MouseEvent.Y);
1475+
Point boundsLoc = MouseGrabView.ScreenToBounds (mouseEvent.X, mouseEvent.Y);
14851476

14861477
var viewRelativeMouseEvent = new MouseEvent
14871478
{
1488-
X = frameLoc.X,
1489-
Y = frameLoc.Y,
1490-
Flags = a.MouseEvent.Flags,
1491-
ScreenPosition = new (a.MouseEvent.X, a.MouseEvent.Y),
1492-
View = view
1479+
X = boundsLoc.X,
1480+
Y = boundsLoc.Y,
1481+
Flags = mouseEvent.Flags,
1482+
ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
1483+
View = MouseGrabView
14931484
};
14941485

14951486
if (MouseGrabView.Bounds.Contains (viewRelativeMouseEvent.X, viewRelativeMouseEvent.Y) is false)
14961487
{
1497-
// The mouse has moved outside the bounds of the view that
1498-
// grabbed the mouse, so we tell the view that last got
1499-
// OnMouseEnter the mouse is leaving
1500-
// BUGBUG: That sentence makes no sense. Either I'm missing something or this logic is flawed.
1501-
_mouseEnteredView?.OnMouseLeave (a.MouseEvent);
1488+
// The mouse has moved outside the bounds of the view that grabbed the mouse
1489+
_mouseEnteredView?.NewMouseLeaveEvent (mouseEvent);
15021490
}
15031491

15041492
//System.Diagnostics.Debug.WriteLine ($"{nme.Flags};{nme.X};{nme.Y};{mouseGrabView}");
1505-
if (MouseGrabView?.OnMouseEvent (viewRelativeMouseEvent) == true)
1493+
if (MouseGrabView?.NewMouseEvent (viewRelativeMouseEvent) == true)
15061494
{
15071495
return;
15081496
}
15091497
}
15101498

1499+
if (view is { WantContinuousButtonPressed: true })
1500+
{
1501+
WantContinuousButtonPressedView = view;
1502+
}
1503+
else
1504+
{
1505+
WantContinuousButtonPressedView = null;
1506+
}
1507+
1508+
15111509
if (view is not Adornment)
15121510
{
15131511
if ((view is null || view == OverlappedTop)
15141512
&& Current is { Modal: false }
15151513
&& OverlappedTop != null
1516-
&& a.MouseEvent.Flags != MouseFlags.ReportMousePosition
1517-
&& a.MouseEvent.Flags != 0)
1514+
&& mouseEvent.Flags != MouseFlags.ReportMousePosition
1515+
&& mouseEvent.Flags != 0)
15181516
{
15191517
// This occurs when there are multiple overlapped "tops"
15201518
// E.g. "Mdi" - in the Background Worker Scenario
1521-
View? top = FindDeepestTop (Top, a.MouseEvent.X, a.MouseEvent.Y);
1522-
view = View.FindDeepestView (top, a.MouseEvent.X, a.MouseEvent.Y);
1519+
View? top = FindDeepestTop (Top, mouseEvent.X, mouseEvent.Y);
1520+
view = View.FindDeepestView (top, mouseEvent.X, mouseEvent.Y);
15231521

15241522
if (view is { } && view != OverlappedTop && top != Current)
15251523
{
@@ -1537,27 +1535,27 @@ internal static void OnMouseEvent (MouseEventEventArgs a)
15371535

15381536
if (view is Adornment adornment)
15391537
{
1540-
Point frameLoc = adornment.ScreenToFrame (a.MouseEvent.X, a.MouseEvent.Y);
1538+
Point frameLoc = adornment.ScreenToFrame (mouseEvent.X, mouseEvent.Y);
15411539

15421540
me = new ()
15431541
{
15441542
X = frameLoc.X,
15451543
Y = frameLoc.Y,
1546-
Flags = a.MouseEvent.Flags,
1547-
ScreenPosition = new (a.MouseEvent.X, a.MouseEvent.Y),
1544+
Flags = mouseEvent.Flags,
1545+
ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
15481546
View = view
15491547
};
15501548
}
1551-
else if (view.BoundsToScreen (view.Bounds).Contains (a.MouseEvent.X, a.MouseEvent.Y))
1549+
else if (view.BoundsToScreen (view.Bounds).Contains (mouseEvent.X, mouseEvent.Y))
15521550
{
1553-
Point boundsPoint = view.ScreenToBounds (a.MouseEvent.X, a.MouseEvent.Y);
1551+
Point boundsPoint = view.ScreenToBounds (mouseEvent.X, mouseEvent.Y);
15541552

15551553
me = new ()
15561554
{
15571555
X = boundsPoint.X,
15581556
Y = boundsPoint.Y,
1559-
Flags = a.MouseEvent.Flags,
1560-
ScreenPosition = new (a.MouseEvent.X, a.MouseEvent.Y),
1557+
Flags = mouseEvent.Flags,
1558+
ScreenPosition = new (mouseEvent.X, mouseEvent.Y),
15611559
View = view
15621560
};
15631561
}
@@ -1570,16 +1568,16 @@ internal static void OnMouseEvent (MouseEventEventArgs a)
15701568
if (_mouseEnteredView is null)
15711569
{
15721570
_mouseEnteredView = view;
1573-
view.OnMouseEnter (me);
1571+
view.NewMouseEnterEvent (me);
15741572
}
15751573
else if (_mouseEnteredView != view)
15761574
{
1577-
_mouseEnteredView.OnMouseLeave (me);
1578-
view.OnMouseEnter (me);
1575+
_mouseEnteredView.NewMouseLeaveEvent (me);
1576+
view.NewMouseEnterEvent (me);
15791577
_mouseEnteredView = view;
15801578
}
15811579

1582-
if (!view.WantMousePositionReports && a.MouseEvent.Flags == MouseFlags.ReportMousePosition)
1580+
if (!view.WantMousePositionReports && mouseEvent.Flags == MouseFlags.ReportMousePosition)
15831581
{
15841582
return;
15851583
}
@@ -1588,7 +1586,7 @@ internal static void OnMouseEvent (MouseEventEventArgs a)
15881586

15891587
//Debug.WriteLine ($"OnMouseEvent: ({a.MouseEvent.X},{a.MouseEvent.Y}) - {a.MouseEvent.Flags}");
15901588

1591-
if (view.OnMouseEvent (me))
1589+
if (view.NewMouseEvent (me) == false)
15921590
{
15931591
// Should we bubble up the event, if it is not handled?
15941592
//return;

Terminal.Gui/ConsoleDrivers/ConsoleDriver.cs

Lines changed: 2 additions & 2 deletions
Original file line numberDiff line numberDiff line change
@@ -538,11 +538,11 @@ public virtual Attribute MakeColor (in Color foreground, in Color background)
538538
public void OnKeyUp (Key a) { KeyUp?.Invoke (this, a); }
539539

540540
/// <summary>Event fired when a mouse event occurs.</summary>
541-
public event EventHandler<MouseEventEventArgs> MouseEvent;
541+
public event EventHandler<MouseEvent> MouseEvent;
542542

543543
/// <summary>Called when a mouse event occurs. Fires the <see cref="MouseEvent"/> event.</summary>
544544
/// <param name="a"></param>
545-
public void OnMouseEvent (MouseEventEventArgs a) { MouseEvent?.Invoke (this, a); }
545+
public void OnMouseEvent (MouseEvent a) { MouseEvent?.Invoke (this, a); }
546546

547547
/// <summary>Simulates a key press.</summary>
548548
/// <param name="keyChar">The key character.</param>

Terminal.Gui/ConsoleDrivers/CursesDriver/CursesDriver.cs

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -2,6 +2,7 @@
22
// Driver.cs: Curses-based Driver
33
//
44

5+
using System.Diagnostics;
56
using System.Runtime.InteropServices;
67
using Terminal.Gui.ConsoleDrivers;
78
using Unix.Terminal;
@@ -798,6 +799,9 @@ bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
798799
|| flag.HasFlag (MouseFlags.Button4DoubleClicked);
799800
}
800801

802+
Debug.WriteLine ($"CursesDriver: ({pos.X},{pos.Y}) - {mouseFlag}");
803+
804+
801805
if ((WasButtonReleased (mouseFlag) && IsButtonNotPressed (_lastMouseFlags)) || (IsButtonClickedOrDoubleClicked (mouseFlag) && _lastMouseFlags == 0))
802806
{
803807
return;
@@ -806,7 +810,9 @@ bool IsButtonClickedOrDoubleClicked (MouseFlags flag)
806810
_lastMouseFlags = mouseFlag;
807811

808812
var me = new MouseEvent { Flags = mouseFlag, X = pos.X, Y = pos.Y };
809-
OnMouseEvent (new MouseEventEventArgs (me));
813+
Debug.WriteLine ($"CursesDriver: ({me.X},{me.Y}) - {me.Flags}");
814+
815+
OnMouseEvent (me);
810816
}
811817

812818
#region Color Handling

Terminal.Gui/ConsoleDrivers/NetDriver.cs

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1137,8 +1137,8 @@ private void ProcessInput (InputResult inputEvent)
11371137
break;
11381138
case EventType.Mouse:
11391139
MouseEvent me = ToDriverMouse (inputEvent.MouseEvent);
1140-
//Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
1141-
OnMouseEvent (new MouseEventEventArgs (me));
1140+
Debug.WriteLine ($"NetDriver: ({me.X},{me.Y}) - {me.Flags}");
1141+
OnMouseEvent (me);
11421142

11431143
break;
11441144
case EventType.WindowSize:
@@ -1379,7 +1379,7 @@ public void StopReportingMouseMoves ()
13791379

13801380
private MouseEvent ToDriverMouse (NetEvents.MouseEvent me)
13811381
{
1382-
// System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
1382+
//System.Diagnostics.Debug.WriteLine ($"X: {me.Position.X}; Y: {me.Position.Y}; ButtonState: {me.ButtonState}");
13831383

13841384
MouseFlags mouseFlag = 0;
13851385

Terminal.Gui/ConsoleDrivers/WindowsDriver.cs

Lines changed: 26 additions & 15 deletions
Original file line numberDiff line numberDiff line change
@@ -1419,18 +1419,16 @@ internal void ProcessInput (WindowsConsole.InputRecord inputEvent)
14191419
break;
14201420
}
14211421

1422-
OnMouseEvent (new MouseEventEventArgs (me));
1422+
OnMouseEvent (me);
14231423

14241424
if (_processButtonClick)
14251425
{
1426-
OnMouseEvent (
1427-
new MouseEventEventArgs (
1428-
new MouseEvent
1429-
{
1430-
X = me.X,
1431-
Y = me.Y,
1432-
Flags = ProcessButtonClick (inputEvent.MouseEvent)
1433-
}));
1426+
OnMouseEvent (new ()
1427+
{
1428+
X = me.X,
1429+
Y = me.Y,
1430+
Flags = ProcessButtonClick (inputEvent.MouseEvent)
1431+
});
14341432
}
14351433

14361434
break;
@@ -1730,27 +1728,38 @@ private async Task ProcessButtonDoubleClickedAsync ()
17301728

17311729
private async Task ProcessContinuousButtonPressedAsync (MouseFlags mouseFlag)
17321730
{
1731+
// When a user presses-and-holds, start generating pressed events every `startDelay`
1732+
// After `iterationsUntilFast` iterations, speed them up to `fastDelay` ms
1733+
const int startDelay = 500;
1734+
const int iterationsUntilFast = 4;
1735+
const int fastDelay = 50;
1736+
1737+
int iterations = 0;
1738+
int delay = startDelay;
17331739
while (_isButtonPressed)
17341740
{
1735-
await Task.Delay (100);
1736-
17371741
var me = new MouseEvent
17381742
{
17391743
X = _pointMove.X,
17401744
Y = _pointMove.Y,
17411745
Flags = mouseFlag
17421746
};
17431747

1744-
View view = Application.WantContinuousButtonPressedView;
1745-
1746-
if (view is null)
1748+
if (Application.WantContinuousButtonPressedView is null)
17471749
{
17481750
break;
17491751
}
17501752

1753+
if (iterations++ >= iterationsUntilFast)
1754+
{
1755+
delay = fastDelay;
1756+
}
1757+
await Task.Delay (delay);
1758+
1759+
//Debug.WriteLine($"ProcessContinuousButtonPressedAsync: {view}");
17511760
if (_isButtonPressed && (mouseFlag & MouseFlags.ReportMousePosition) == 0)
17521761
{
1753-
Application.Invoke (() => OnMouseEvent (new MouseEventEventArgs (me)));
1762+
Application.Invoke (() => OnMouseEvent (me));
17541763
}
17551764
}
17561765
}
@@ -1919,6 +1928,8 @@ private MouseEvent ToDriverMouse (WindowsConsole.MouseEventRecord mouseEvent)
19191928
{
19201929
_point = null;
19211930
}
1931+
_processButtonClick = true;
1932+
19221933
}
19231934
else if (mouseEvent.EventFlags == WindowsConsole.EventFlags.MouseMoved
19241935
&& !_isOneFingerDoubleClicked

Terminal.Gui/Drawing/Color.cs

Lines changed: 22 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -6,6 +6,7 @@
66
using System.Runtime.CompilerServices;
77
using System.Runtime.InteropServices;
88
using System.Text.Json.Serialization;
9+
using ColorHelper;
910

1011
namespace Terminal.Gui;
1112

@@ -237,6 +238,27 @@ internal static ColorName GetClosestNamedColor (Color inputColor)
237238
[SkipLocalsInit]
238239
private static float CalculateColorDistance (in Vector4 color1, in Vector4 color2) { return Vector4.Distance (color1, color2); }
239240

241+
/// <summary>
242+
/// Gets a color that is the same hue as the current color, but with a different lightness.
243+
/// </summary>
244+
/// <returns></returns>
245+
public Color GetHighlightColor ()
246+
{
247+
// TODO: This is a temporary implementation; just enough to show how it could work.
248+
var hsl = ColorHelper.ColorConverter.RgbToHsl(new RGB (R, G, B));
249+
250+
var amount = .7;
251+
if (hsl.L <= 5)
252+
{
253+
return DarkGray;
254+
}
255+
hsl.L = (byte)(hsl.L * amount);
256+
257+
var rgb = ColorHelper.ColorConverter.HslToRgb (hsl);
258+
return new (rgb.R, rgb.G, rgb.B);
259+
260+
}
261+
240262
#region Legacy Color Names
241263

242264
/// <summary>The black color.</summary>

Terminal.Gui/Terminal.Gui.csproj

Lines changed: 1 addition & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -44,6 +44,7 @@
4444
<!-- Dependencies -->
4545
<!-- =================================================================== -->
4646
<ItemGroup>
47+
<PackageReference Include="ColorHelper" Version="1.8.1" />
4748
<PackageReference Include="JetBrains.Annotations" Version="2023.3.0" />
4849
<!-- Enable Nuget Source Link for github -->
4950
<PackageReference Include="Microsoft.SourceLink.GitHub" Version="8.0.0" PrivateAssets="All" />

0 commit comments

Comments
 (0)