MenuOverlayContent class Core

A self-drawn dropdown/context menu — the first IOverlayContent. Host-neutral: it draws via IRenderSurface and handles pointer + keyboard, so the operator menu, Σ menu and context menu render identically on every host. Data/actions come from the caller (e.g. IGridViewOps).

Public as pane SPI (level 3): panes outside this assembly (the pivot field list) open their submenus as THIS menu — a second menu drawing next to it would acquire exactly the differences nobody wants. Contract: entries + callbacks in, drawing and keyboard built in.

public sealed class MenuOverlayContent : IOverlayContent
Namespace
ArionUI.Presentation.Overlay
Package
ArionUI.Presentation · dotnet add package ArionUI.Presentation --prerelease
Implements
IOverlayContent

Constructors

MenuOverlayContent

public MenuOverlayContent(IReadOnlyList<MenuItem> items, Func<string, TextStyle, double> measure);

Properties

AccessibleDescription

public string? AccessibleDescription { get; }

What a screen reader says when this pane OPENS ("Menü, 5 Einträge"). Null = the generic pane announcement. The self-drawn panes are invisible to assistive technology as pixels — this sentence is their whole existence there.

AccessibleParts

public IReadOnlyList<AccessiblePart>? AccessibleParts { get; }

The entries as structure for assistive technology: a reader can walk the menu and pick an entry, not only hear the highlight move. Separators carry no action, so they are no elements. Boxes are grid-absolute, like every pane's parts; an empty list before the first layout, never null — the menu IS structure, it just does not know where it sits yet.

MinWidth

public double MinWidth { get; init; }

The narrowest the menu gets; a dropdown list passes its trigger's width, so the list lines up under the field it opened from.

WantsTextInput

public bool WantsTextInput { get; }

A menu takes typing: letters jump to the matching entry, the way every native menu and combo does. This is also the DEFAULT dropdown style, so without it the most common picker in the grid ignored the keyboard entirely.

WantsTick

public bool WantsTick { get; }

Frames while a type-ahead prefix is still collecting — that is what lets its pause expire without an ambient clock.

Methods

AccessibleToggleStateOf

public bool? AccessibleToggleStateOf(string id);

The check mark of an entry; null for an entry that does not check.

AttachAnnouncer

public void AttachAnnouncer(Action<string> announce);

The host hands the pane a voice: everything passed to announce is spoken through the grid's screen-reader channels (highlight moves, state changes). Default: mute, for panes that say nothing.

CursorAt

public GridCursor CursorAt(LogPoint local);

A menu is nothing but actions — the whole surface is click-to-act.

IndexOfLabel

public int IndexOfLabel(string needle);

Index of the first item whose label CONTAINS needle, or −1. By label rather than by position on purpose: the entries of a header menu depend on the column's state, so a positional target would silently start aiming at a different command the moment one entry appears or drops out.

InvokeAccessiblePart

public bool InvokeAccessiblePart(string id);

Pick an entry the way a click does — the same Activate the pointer and Enter reach, so a veto or a close rule can never apply to only one of them.

Measure

public LogSize Measure(double maxWidth, double maxHeight);

Desired size given the space available (for placement/clamping).

OnKey

public bool OnKey(ArionKey key, ArionModifiers mods);

Key press. Esc/Enter/arrows etc. Return true if handled.

OnPointerMoved

public void OnPointerMoved(LogPoint local, bool buttonDown);

OnPointerPressed

public bool OnPointerPressed(LogPoint local, ArionModifiers mods, int clickCount = 1);

Pointer press in LOCAL coords. clickCount is 2 for a double-click and 3 for a triple-click, so a text field in a pane selects a word or everything exactly like the grid's own fields do (the grid gets it from InteractionController.OnMultiClick). Return true if handled.

OnPointerReleased

public void OnPointerReleased(LogPoint local);

OnTextInput

public void OnTextInput(string text);

Placed

public void Placed(LogRect bounds);

Where the overlay manager placed this pane, in grid-logical coordinates — called on the UI thread whenever the layout runs, and before the pane is announced as opened. A pane that reports parts needs it: its boxes are grid-absolute, and taking them from the last DRAW would both come too late for the first query and read a field the render thread writes.

Render

public void Render(IRenderSurface s, LogRect b, OverlayFrameState state);

Paint into bounds (already placed + clamped). Panes without text input ignore state.

RowCenterY

public double RowCenterY(int index);

Vertical CENTRE of an item, in the menu's own local coordinates — the point a scripted scenario aims at to activate it. Same walk as MenuOverlayContent.ItemAt (separators are shorter), so a target and the press that lands on it cannot drift apart. NaN when the index names nothing clickable.

Tick

public void Tick(double dtMs);

Advance animations by dtMs milliseconds (called once per frame by the hosting surface while IOverlayContent.WantsTick).

Events

CloseRequested

public event Action? CloseRequested;

Raised when the pane wants to close itself (Enter/apply/pick).

Invalidated

public event Action? Invalidated;

Raised when the pane needs a repaint (hover, caret, content change).