Menu System & Context Menus (Specs)

This document specifies a cohesive menu system v1 integrated with the unified Command model.

It covers:

  • MenuBar (top-level menus)
  • menu items and submenus
  • keyboard/mouse navigation
  • context menus (right-click menus)
  • integration with Command and CommandPresentation

Related specs:


Goals / non-goals

Goals (v1)

  • A single “menu item” model that can be backed by a Command.
  • MenuBar remains a control, but menu structures should be definable in a simple, fluent way.
  • Support submenus and separators.
  • Support both mouse and keyboard:
    • open/close menus
    • navigate items
    • execute commands
  • Provide context menus that are easy to attach to any visual subtree:
    • opened via right-click
    • optionally via keyboard (Shift+F10 if terminal sends it)
  • Make enable/disable and visibility bindable through Command.CanExecute / Command.IsVisible.

Non-goals (v1)

  • Native platform menu integration (this is terminal UI).
  • Fully dynamic menu merging (“VS-style command routing”) – we can add later.

Current state (code)

Existing code:

  • Controls/MenuBar.cs implements a full interactive menu bar and popup submenus.
  • Controls/MenuItem.cs is a data object with:
    • Header (Visual)
    • optional Icon, Shortcut, Action
    • nested Items

What is missing:

  • A first-class context menu control/service.
  • Integration between MenuItem and Command.
  • A shared “collect commands for this context” helper for menus and context menus.

Command integration

MenuItem should be able to reference a Command directly:

public sealed partial class MenuItem
{
    [Bindable] public partial Command? Command { get; set; }
}

Rules when Command is set:

  • IsEnabled becomes derived by default:
    • Command.CanExecuteFor(target) (target is resolved by menu host)
  • Action becomes derived by default:
    • Command.Execute(target)
  • Shortcut is derived by default:
    • Command.Sequence?.ToString() ?? Command.Gesture?.ToString()

The existing explicit properties should still exist for advanced cases, but Command should be the “happy path”.

Target resolution for menu execution

Menu execution needs a target visual.

Rules:

  • For a MenuBar, default target is TerminalApp.FocusedElement ?? root.
  • For a context menu opened by right-click, target is TerminalApp.HoveredElement at open time.
  • If target becomes invalid (visual removed), fall back to root.

The target is passed to:

  • Command.IsVisibleFor(target)
  • Command.CanExecuteFor(target)
  • Command.Execute(target)

Fluent API for MenuItem

The menu API should be easy to author without manual list management.

Examples:

var file = new MenuItem("File")
    .Items(
        new MenuItem("Open", openCommand),
        MenuItem.Separator(),
        new MenuItem("Exit", exitCommand));

In practice, MenuItem should offer:

  • constructors:
    • MenuItem(string header, Command? command = null)
    • MenuItem(Visual header, Command? command = null)
  • Separator() factory
  • fluent .Items(params MenuItem[] items) generated by [Bindable] list fluent support

MenuBar.Items should remain a BindableList<MenuItem>.

MenuBar should not attempt to auto-build its structure from commands. Menu structure is explicit.

However, MenuBar can still provide helper integration:

  • show gesture hints from the backing Command by default
  • enable/disable based on command state

Context menus

New controls/services

Introduce a ContextMenu control (or MenuPopup) that renders a list of MenuItem values using the same visual building logic as menu submenus.

Introduce a lightweight host service:

public static class ContextMenuService
{
    public static void Show(Visual target, IEnumerable<MenuItem> items);
    public static void Close();
}

Notes:

  • Context menus are fullscreen-only (use window layer).
  • The service can be implemented on top of Popup/Dialog/WindowLayer as the existing menus do.

Context menu from commands (automatic)

To leverage the unified command system, allow “default context menu” generation:

  • collect commands from HoveredElement → Parent + global
  • filter CommandPresentation.ContextMenu
  • map each to a MenuItem backed by that Command

This makes it trivial for controls to contribute context actions:

  • TextArea contributes Find/Replace/Undo/Redo
  • LogControl contributes Find, Copy selection, Clear, etc.

How visuals opt-in

Provide a ContextMenuFactory hook on Visual:

public abstract partial class Visual
{
    public Func<Visual, IEnumerable<MenuItem>>? ContextMenuFactory { get; set; }
}

Rules:

  • if ContextMenuFactory is set on a visual, it is used as the primary source
  • otherwise, use command-based discovery (CommandPresentation.ContextMenu)

This allows both:

  • explicit context menu definitions, and
  • command-driven default context menus.

Keyboard and mouse behavior

Keep the existing behavior as baseline:

  • Left/Right selects top-level items
  • Enter/Down opens selected menu
  • Esc closes menu (and returns to menu bar focus)

Context menu behavior

  • Open via right-click:
    • use TerminalMouseButton.Right (or equivalent)
    • close on click outside
  • Keyboard:
    • Up/Down selects
    • Right opens submenu
    • Left closes submenu
    • Enter executes
    • Esc closes

Interaction with command routing

While a menu is open, it should capture key events so that:

  • command shortcuts do not execute “behind” the menu
  • Esc reliably closes the menu

Styling

Existing styles:

  • MenuBarStyle, MenuListStyle, etc.

Add or refine:

  • ContextMenuStyle (or reuse menu list style + popup template factory)
  • allow per-menu glyph/border style like other controls (GroupStyle, BorderStyle, etc.)

Demos and docs impact

  • Update ControlsDemo:
    • show a MenuBar demo driven by Command instances
    • show right-click context menu on a TextArea and LogControl
  • Update docs:

Tests

  • Menu item backed by command:
    • IsEnabled follows CanExecute
    • shortcut text derived correctly
    • executing invokes command with correct target
  • Context menu command discovery:
    • hovered element commands appear before global
    • presentation filtering works
  • Input capture:
    • while menu open, pressing a global shortcut does not execute it

Implementation steps

  1. Introduce CommandQuery helper (shared with command palette).
  2. Add MenuItem.Command and derivation logic for enable/execute/shortcut.
  3. Implement ContextMenu control and ContextMenuService.
  4. Add default command-based context menu generation using CommandPresentation.ContextMenu.
  5. Update demos/docs/tests.