Files
Terminal.Gui/Examples/UICatalog/Scenarios/MouseTester.cs
2025-12-19 20:02:04 -07:00

605 lines
25 KiB
C#

using System.Collections.ObjectModel;
namespace UICatalog.Scenarios;
[ScenarioMetadata ("Mouse Tester", "Illustrates Mouse event flow and handling")]
[ScenarioCategory ("Mouse and Keyboard")]
public class MouseTester : Scenario
{
public override void Main ()
{
Application.Init ();
// Prepping for modern app model
using IApplication app = Application.Instance;
using Runnable runnable = new ()
{
Id = "runnable",
};
MenuBar menuBar = new ();
menuBar.Add (new MenuBarItem ("_File", [new MenuItem { Title = "_Quit", Action = () => app.RequestStop () }]));
FlagSelector<DemoMouseFlags> mouseFlagsFilter = new ()
{
AssignHotKeys = true,
Value = (DemoMouseFlags.All & ~DemoMouseFlags.PositionReport),
};
menuBar.Add (
new MenuBarItem (
"_Filter",
[
new MenuItem
{
CommandView = mouseFlagsFilter
},
]
),
new MenuBarItem (runnable, Command.DeleteAll, "_Clear Logs")
);
runnable.Add (menuBar);
View lastDriverEvent = new ()
{
Height = 1,
Width = Dim.Auto (),
Y = Pos.Bottom (menuBar),
Text = "Last Driver Event: "
};
runnable.Add (lastDriverEvent);
View lastAppEvent = new ()
{
Height = 1,
Width = Dim.Auto (),
Y = Pos.Bottom (lastDriverEvent),
Text = "Last App Event: "
};
runnable.Add (lastAppEvent);
View lastViewEvent = new ()
{
Height = 1,
Width = Dim.Auto (),
Y = Pos.Bottom (lastAppEvent),
Text = "Last View Event: "
};
runnable.Add (lastViewEvent);
CheckBox cbRepeatOnHold = new ()
{
Y = Pos.Bottom (lastViewEvent),
Title = "_Repeat On Hold"
};
runnable.Add (cbRepeatOnHold);
CheckBox cbHighlightOnPressed = new ()
{
Y = Pos.Bottom (cbRepeatOnHold),
Title = "_Highlight on Pressed"
};
runnable.Add (cbHighlightOnPressed);
CheckBox cbHighlightOnPressedOutside = new ()
{
Y = Pos.Bottom (cbHighlightOnPressed),
Title = "_Highlight on PressedOutside"
};
runnable.Add (cbHighlightOnPressedOutside);
MouseEventDemoView demo = new ()
{
Id = "demo",
Y = Pos.Bottom (cbHighlightOnPressedOutside),
Width = Dim.Fill (),
Height = 15,
Title = "Enter/Leave Demo"
};
MouseEventDemoView demoInPadding = new ()
{
X = 0,
Y = 0,
Width = Dim.Fill (),
Height = Dim.Func (_ => demo.Padding!.Thickness.Top),
Title = "inPadding",
Id = "inPadding",
};
demo.Padding!.Add (demoInPadding);
demo.Padding!.Initialized += DemoPaddingOnInitialized;
void DemoPaddingOnInitialized (object o, EventArgs eventArgs)
{
demo.Padding!.Thickness = demo.Padding.Thickness with { Top = 5 };
}
MouseEventDemoView sub1 = new ()
{
X = 0,
Y = 0,
Width = Dim.Percent (20),
Height = Dim.Fill (),
Title = "sub1",
Id = "sub1"
};
demo.Add (sub1);
MouseEventDemoView sub2 = new ()
{
X = Pos.Right (sub1) - 4,
Y = Pos.Top (sub1) + 1,
Width = Dim.Percent (20),
Height = Dim.Fill (1),
Title = "sub2",
Id = "sub2"
};
demo.Add (sub2);
runnable.Add (demo);
cbHighlightOnPressed.CheckedState = demo.HighlightStates.HasFlag (MouseState.Pressed) ? CheckState.Checked : CheckState.UnChecked;
cbHighlightOnPressed.CheckedStateChanging += (_, e) =>
{
if (e.Result == CheckState.Checked)
{
demo.HighlightStates |= MouseState.Pressed;
}
else
{
demo.HighlightStates &= ~MouseState.Pressed;
}
foreach (View subview in demo.SubViews)
{
if (e.Result == CheckState.Checked)
{
subview.HighlightStates |= MouseState.Pressed;
}
else
{
subview.HighlightStates &= ~MouseState.Pressed;
}
}
foreach (View subview in demo.Padding.SubViews)
{
if (e.Result == CheckState.Checked)
{
subview.HighlightStates |= MouseState.Pressed;
}
else
{
subview.HighlightStates &= ~MouseState.Pressed;
}
}
};
cbHighlightOnPressedOutside.CheckedState = demo.HighlightStates.HasFlag (MouseState.PressedOutside) ? CheckState.Checked : CheckState.UnChecked;
cbHighlightOnPressedOutside.CheckedStateChanging += (_, e) =>
{
if (e.Result == CheckState.Checked)
{
demo.HighlightStates |= MouseState.PressedOutside;
}
else
{
demo.HighlightStates &= ~MouseState.PressedOutside;
}
foreach (View subview in demo.SubViews)
{
if (e.Result == CheckState.Checked)
{
subview.HighlightStates |= MouseState.PressedOutside;
}
else
{
subview.HighlightStates &= ~MouseState.PressedOutside;
}
}
foreach (View subview in demo.Padding.SubViews)
{
if (e.Result == CheckState.Checked)
{
subview.HighlightStates |= MouseState.PressedOutside;
}
else
{
subview.HighlightStates &= ~MouseState.PressedOutside;
}
}
};
cbRepeatOnHold.CheckedStateChanging += (_, _) =>
{
demo.WantContinuousButtonPressed = !demo.WantContinuousButtonPressed;
foreach (View subview in demo.SubViews)
{
subview.WantContinuousButtonPressed = demo.WantContinuousButtonPressed;
}
foreach (View subview in demo.Padding.SubViews)
{
subview.WantContinuousButtonPressed = demo.WantContinuousButtonPressed;
}
};
Label label = new ()
{
Text = "Dri_ver Events:",
//X = Pos.Right (filterSlider),
Y = Pos.Bottom (demo)
};
ObservableCollection<string> driverLogList = new ();
ListView driverLog = new ()
{
X = Pos.Left (label),
Y = Pos.Bottom (label),
Width = Dim.Auto (minimumContentDim: Dim.Percent (20)),
Height = Dim.Fill (),
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base),
Source = new ListWrapper<string> (driverLogList)
};
runnable.Add (label, driverLog);
app.Driver!.MouseEvent += (_, mouse) =>
{
if (!mouseFlagsFilter.Value.HasValue)
{
return;
}
if (mouseFlagsFilter.Value.Value.HasFlag ((DemoMouseFlags)mouse.Flags))
{
lastDriverEvent.Text = $"Last Driver Event: {mouse}";
Logging.Trace (lastDriverEvent.Text);
driverLogList.Add ($"{mouse.Position}:{mouse.Flags}");
driverLog.MoveEnd ();
}
};
label = new ()
{
Text = "_App Events:",
X = Pos.Right (driverLog) + 1,
Y = Pos.Bottom (demo)
};
ObservableCollection<string> appLogList = new ();
ListView appLog = new ()
{
X = Pos.Left (label),
Y = Pos.Bottom (label),
Width = Dim.Auto (minimumContentDim: Dim.Percent (20)),
Height = Dim.Fill (),
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base),
Source = new ListWrapper<string> (appLogList)
};
runnable.Add (label, appLog);
app.Mouse.MouseEvent += (_, mouse) =>
{
if (!mouseFlagsFilter.Value.HasValue)
{
return;
}
if (mouseFlagsFilter.Value.Value.HasFlag ((DemoMouseFlags)mouse.Flags))
{
lastAppEvent.Text = $" Last App Event: {mouse}";
appLogList.Add ($"{mouse.Position}:{mouse.Flags}");
appLog.MoveEnd ();
}
};
label = new ()
{
Text = "_View Events:",
X = Pos.Right (appLog) + 1,
Y = Pos.Top (label)
};
ObservableCollection<string> viewLogList = [];
ListView viewLog = new ()
{
X = Pos.Left (label),
Y = Pos.Bottom (label),
Width = Dim.Auto (minimumContentDim: Dim.Percent (20)),
Height = Dim.Fill (),
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base),
Source = new ListWrapper<string> (viewLogList)
};
runnable.Add (label, viewLog);
demo.MouseEvent += (_, mouse) =>
{
if (mouseFlagsFilter.Value.Value.HasFlag ((DemoMouseFlags)mouse.Flags))
{
lastViewEvent.Text = $" Last View Event: {mouse}";
viewLogList.Add ($"{mouse.Position}:{mouse.View!.Id}:{mouse.Flags}");
viewLog.MoveEnd ();
}
};
demoInPadding.MouseEvent += (_, mouse) =>
{
if (mouseFlagsFilter.Value.Value.HasFlag ((DemoMouseFlags)mouse.Flags))
{
lastViewEvent.Text = $" Last View Event: {mouse}";
viewLogList.Add ($"{mouse.Position}:{mouse.View!.Id}:{mouse.Flags}");
viewLog.MoveEnd ();
}
};
sub1.MouseEvent += (_, mouse) =>
{
if (mouseFlagsFilter.Value.Value.HasFlag ((DemoMouseFlags)mouse.Flags))
{
lastViewEvent.Text = $" Last View Event: {mouse}";
viewLogList.Add ($"{mouse.Position}:{mouse.View!.Id}:{mouse.Flags}");
viewLog.MoveEnd ();
}
};
sub2.MouseEvent += (_, mouse) =>
{
if (mouseFlagsFilter.Value.Value.HasFlag ((DemoMouseFlags)mouse.Flags))
{
lastViewEvent.Text = $" Last View Event: {mouse}";
viewLogList.Add ($"{mouse.Position}:{mouse.View!.Id}:{mouse.Flags}");
viewLog.MoveEnd ();
}
};
label = new ()
{
Text = "_Commands:",
X = Pos.Right (viewLog) + 1,
Y = Pos.Top (label)
};
ObservableCollection<string> commandLogList = [];
ListView commandLog = new ()
{
X = Pos.Left (label),
Y = Pos.Bottom (label),
Width = Dim.Auto (minimumContentDim: Dim.Percent (15)),
Height = Dim.Fill (),
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base),
Source = new ListWrapper<string> (commandLogList)
};
runnable.Add (label, commandLog);
demo.Activating += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
demo.Accepting += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
demoInPadding.Activating += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
demoInPadding.Accepting += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
sub1.Activating += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
sub1.Accepting += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
sub2.Activating += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
sub2.Accepting += (_, args) =>
{
commandLogList.Add ($"{args.Context!.Source!.Id}:{args.Context!.Command}");
commandLog.MoveEnd ();
args.Handled = true;
};
runnable.CommandNotBound += (_, args) =>
{
if (args.Context!.Command == Command.DeleteAll)
{
driverLogList.Clear ();
driverLog.SetSource (driverLogList);
appLogList.Clear ();
appLog.SetSource (appLogList);
viewLogList.Clear ();
viewLog.SetSource (viewLogList);
commandLogList.Clear ();
commandLog.SetSource (commandLogList);
args.Handled = true;
}
};
app.Run (runnable);
}
public class MouseEventDemoView : View
{
public MouseEventDemoView ()
{
CanFocus = true;
Id = "mouseEventDemoView";
MouseLeave += (_, _) => { Text = "Leave"; };
MouseEnter += (_, _) => { Text = "Enter"; };
}
/// <inheritdoc />
public override void EndInit ()
{
SchemeName = SchemeManager.SchemesToSchemeName (Schemes.Base);
TextAlignment = Alignment.Center;
VerticalTextAlignment = Alignment.Center;
Padding!.Thickness = new (1, 1, 1, 1);
Padding!.SetScheme (new (new Attribute (Color.DarkGray)));
Padding.Id = $"{Id}.Padding";
Border!.Thickness = new (1);
Border.LineStyle = LineStyle.Rounded;
Border.Id = $"{Id}.Border";
base.EndInit ();
}
/// <inheritdoc />
protected override void OnMouseStateChanged (EventArgs<MouseState> args)
{
base.OnMouseStateChanged (args);
Border!.LineStyle = args.Value.HasFlag (MouseState.PressedOutside) ? LineStyle.Dotted : LineStyle.Single;
SetNeedsDraw ();
}
/// <inheritdoc/>
protected override bool OnGettingAttributeForRole (in VisualRole role, ref Attribute currentAttribute)
{
switch (role)
{
case VisualRole.Normal when MouseState.HasFlag (MouseState.Pressed) && HighlightStates.HasFlag (MouseState.Pressed):
currentAttribute = currentAttribute with { Background = currentAttribute.Foreground.GetBrighterColor () };
return true;
default:
return base.OnGettingAttributeForRole (in role, ref currentAttribute);
}
}
}
}
// All the MouseFlags we can set for filtering
[Flags]
internal enum DemoMouseFlags
{
/// <summary>
/// No mouse event. This is the default value for <see cref="MouseEventArgs.Flags"/> when no mouse event is being
/// reported.
/// </summary>
None = 0,
/// <summary>The first mouse button was pressed.</summary>
Button1Pressed = 0x2,
/// <summary>The first mouse button was released.</summary>
Button1Released = 0x1,
/// <summary>The first mouse button was clicked (press+release).</summary>
Button1Clicked = 0x4,
/// <summary>The first mouse button was double-clicked.</summary>
Button1DoubleClicked = 0x8,
/// <summary>The first mouse button was triple-clicked.</summary>
Button1TripleClicked = 0x10,
/// <summary>The second mouse button was pressed.</summary>
MiddleButtonPressed = 0x80,
/// <summary>The second mouse button was released.</summary>
MiddleButtonReleased = 0x40,
/// <summary>The second mouse button was clicked (press+release).</summary>
MiddleButtonClicked = 0x100,
/// <summary>The second mouse button was double-clicked.</summary>
MiddleButtonDoubleClicked = 0x200,
/// <summary>The second mouse button was triple-clicked.</summary>
MiddleButtonTripleClicked = 0x400,
/// <summary>The third mouse button was pressed.</summary>
Button3Pressed = 0x2000,
/// <summary>The third mouse button was released.</summary>
Button3Released = 0x1000,
/// <summary>The third mouse button was clicked (press+release).</summary>
Button3Clicked = 0x4000,
/// <summary>The third mouse button was double-clicked.</summary>
Button3DoubleClicked = 0x8000,
/// <summary>The third mouse button was triple-clicked.</summary>
Button3TripleClicked = 0x10000,
/// <summary>The fourth mouse button was pressed.</summary>
Button4Pressed = 0x80000,
/// <summary>The fourth mouse button was released.</summary>
Button4Released = 0x40000,
/// <summary>The fourth mouse button was clicked.</summary>
Button4Clicked = 0x100000,
/// <summary>The fourth mouse button was double-clicked.</summary>
Button4DoubleClicked = 0x200000,
/// <summary>The fourth mouse button was triple-clicked.</summary>
Button4TripleClicked = 0x400000,
/// <summary>The mouse position is being reported in this event.</summary>
PositionReport = 0x8000000,
/// <summary>Vertical button wheeled up.</summary>
WheeledUp = 0x10000000,
/// <summary>Vertical button wheeled down.</summary>
WheeledDown = 0x20000000,
/// <summary>Vertical button wheeled up while pressing Ctrl.</summary>
WheeledLeft = 0x1000000 | WheeledUp,
/// <summary>Vertical button wheeled down while pressing Ctrl.</summary>
WheeledRight = 0x1000000 | WheeledDown,
All = -1
}