Skip to main content

Mouse

Mouse reporting is off until you ask for it:

builder.Services.AddArlecchino().UseMouse(); // or options.MouseInput = true

The hosted service then turns reporting on while it runs and off on the way out — button presses, releases, drags and the wheel.

MouseEvent

MemberMeaning
ActionPressed, Released, Moved (drag), ScrolledUp, ScrolledDown
ButtonLeft, Middle, Right, or None for the wheel
Row, ColumnZero-based cell in the frame — the same coordinates Surface.WriteAt takes
ModifiersShift, Alt, Control held at the time
IsScrollWhether this is a wheel event
IsLeftClickShorthand for a left button press

Handling one

A view opts in by implementing one method, and navigates by returning a route just as Handle does:

private int _offset;
private int _runRow;

public ViewRoute HandleMouse(MouseEvent mouse)
{
if (mouse.IsScroll)
{
_offset += mouse.Action == MouseAction.ScrolledDown ? 1 : -1;
return ViewRoute.None;
}

return mouse.IsLeftClick && mouse.Row == _runRow ? ViewKind.Run : ViewRoute.None;
}

Because Row and Column are frame cells, a view that draws with absolute coordinates already knows where its rows are — hit-testing is comparing numbers.

Hit-testing with regions

A view built from regions does not compare numbers at all. The same region that positioned a pane answers whether a click landed in it:

private SurfaceRegion _list;
private int _first;
private int _index;

public ViewRoute HandleMouse(MouseEvent mouse)
{
if (!_list.Contains(mouse.Row, mouse.Column))
{
return ViewRoute.None;
}

var (row, _) = _list.ToLocal(mouse.Row, mouse.Column);
_index = _first + row;
return ViewRoute.None;
}

A view with several panes should reach for a focus ring instead: it offers the event to each element and moves the focus to whichever one claims it, so a click both selects a pane and acts inside it.

While a modal is open

The wheel scrolls a list or choice modal; other events are swallowed rather than reaching the view behind it. The modals that have clickable parts — the slider track, the toggle chips, the color channels, the palette rows — publish the regions they were drawn into as they are drawn, which is what makes them clickable at all.

Why Windows is different

How mouse reporting is done differs by platform, and only SystemTerminal knows the difference.

Everywhere but Windows it is SGR reporting (?1000, ?1002, ?1006) mixed into the key stream. On Windows the console cannot do that: turning on virtual-terminal input is what delivers SGR reports, and with that flag Console.ReadKey stops delivering keys at all. So Windows reads the console's own event queue instead — ReadConsoleInput with ENABLE_MOUSE_INPUT, keys and mouse records out of the same stream, translated into the same MouseEvent.

Quick-edit mode is switched off while it runs, otherwise the console swallows clicks as text selection, and the previous mode is put back when the mouse is turned off.

That is the one place IArlecchinoTerminal.MouseAvailable and ReadMouse() matter: they exist for terminals that deliver the mouse outside the key stream. TerminalInputReader.ReadPending() drains both.

note

Mouse reporting has not been exercised inside a multiplexer. If it misbehaves there, an issue with the terminal and TERM is useful.