Class MenuBar

java.lang.Object
limn.scene.Widget<MenuBar>
limn.components.MenuBar

public final class MenuBar extends Widget<MenuBar>
A horizontal strip of top-level menu titles; the strip itself is drawn in-scene (part of the window chrome). Clicking a title drops its Menu as a PopupMenu (a native window) below it; Left/Right walk between menus, Down/Enter opens the focused one, and its submenus/checkable items behave like any other popup.

 MenuBar bar = new MenuBar()
     .addMenu("File", 'F', fileMenu)
     .addMenu("Edit", 'E', editMenu)
     .addMenu("View", 'V', viewMenu);
 

The bar is the keyboard entry point to the whole menu system, and it is one while it is in a scene rather than only while it is focused. From the moment it is attached it answers, anywhere in the scene and without focus: an item's Accelerator runs that item; a bare Alt released (or F10) moves focus here; Alt plus a declared access letter opens that menu. All of it is offered only after the focused widget has declined the key, so a shortcut never steals a keystroke from a text field that wanted it.

Where the system keeps the menu bar outside the window, which is macOS, the bar's menus are shown there and not here: see setPlacement(limn.components.MenuBar.Placement) and isInSystemBar(). The widget then takes no room in its scene, and what the paragraph above says of the keyboard is the system's to decide: it has its own keys for walking its bar, and it runs an item's accelerator before the focused widget sees the key. An item made for an editing command, MenuItem.of(MenuItem.Role), is the one written for that order: it does the command in the focused widget, and lets the key through when the widget cannot. Three other roles move an item to the application's own menu there (MenuItem.Role), and two menus may be named for the system to add its own items to (setWindowMenu(limn.components.Menu), setHelpMenu(limn.components.Menu)).

The strip mirrors where it reads right to left: the first title is at the right edge, each title's text sits on the pad of the side reading starts from, and Left selects the visually-left title, which is then the next one in declaration order. The dropdown inherits the direction through the anchor and mirrors with it. Accelerator labels do not mirror: they name physical keys.

The strip takes the width it is offered, as a platform's menu bar spans its window: at the top of a Column it runs from edge to edge, bottom rule included, whatever the column's cross alignment, and its titles keep to the end reading starts from. Where no width is offered, in a Row outside Expanded, it is as wide as its titles. The stretch past the last title is drawn and not hit: a press there reaches whatever is beneath the bar.

The strip's height is the ControlSize step's controlHeight, so a bar and the buttons beside it agree at every step, and the dropdown opens at the bar's step because PopupMenu is anchored on this widget. Every metric comes from the resolved SizeTokens row, every weight from Strokes, including the bottom rule, the most visible hairline in the toolkit, which stays 1 pt at XSMALL and at XLARGE.

  • Constructor Details

  • Method Details

    • addMenu

      public MenuBar addMenu(String title, Menu menu)
      Appends a top-level menu with the given title, and no access letter. UI thread only.
    • addMenu

      public MenuBar addMenu(I18nString title, Menu menu)
      A top-level menu whose title follows the UI language, and no access letter. UI thread only.
    • addMenu

      public MenuBar addMenu(String title, char mnemonic, Menu menu)
      Appends a top-level menu whose title carries an access letter: it is underlined in the strip, Alt plus that letter opens the menu from anywhere in the scene, and the bare letter opens it while the bar has focus.
      Parameters:
      mnemonic - a letter or digit, matched case-insensitively; 0 for none
      Throws:
      IllegalArgumentException - for any other character
    • addMenu

      public MenuBar addMenu(I18nString title, char mnemonic, Menu menu)
      addMenu(String, char, Menu) with a title that follows the UI language.
    • setDisplayMode

      public MenuBar setDisplayMode(DisplayMode mode)
      Asks for the presentation of every dropdown this bar opens; see DisplayMode. Default DisplayMode.NATIVE_WINDOW, which is what a platform menu bar does.

      A preference, not a guarantee: a dropdown asked for NATIVE_WINDOW is still drawn in scene where the platform cannot place a window at the anchor, and in a fullscreen window. IN_SCENE is always honoured, and the bar keeps its hover-to-switch behaviour under it, because pointer input over the strip falls through the overlay.

      Takes effect on the next open; a dropdown already down is not re-presented.

    • setPlacement

      public MenuBar setPlacement(MenuBar.Placement value)
      Says where the bar is shown; see MenuBar.Placement. Default MenuBar.Placement.SYSTEM.

      A preference, as setDisplayMode(limn.components.DisplayMode) is: isInSystemBar() answers what happened. Under SYSTEM the bar is still drawn in the window where the system keeps no bar for the application, while its window is fullscreen in the way that covers the system's bar, and when another bar of the same window got there first.

    • placement

      public MenuBar.Placement placement()
      Returns:
      where the bar asked to be shown
    • isInSystemBar

      public boolean isInSystemBar()
      Returns:
      whether the system's bar is showing this bar's menus, in which case the bar takes no room in its scene, paints nothing, holds no key and is read by a screen reader as the system's bar is. The answer can change while the application runs
    • setWindowMenu

      public MenuBar setWindowMenu(@Nullable Menu menu)
      Names the menu of the application's windows among this bar's menus. In the system's bar the system adds its own items to it, the list of windows among them, and an application that names none is given one there. In the window it changes nothing.
      Parameters:
      menu - a menu added to this bar, now or later, or null for none
    • setHelpMenu

      public MenuBar setHelpMenu(@Nullable Menu menu)
      Names the help menu among this bar's menus. In the system's bar the system adds its search to it. In the window it changes nothing.
      Parameters:
      menu - a menu added to this bar, now or later, or null for none
    • displayMode

      public DisplayMode displayMode()
      Returns:
      the presentation the next dropdown will be asked for.
    • isOpen

      public boolean isOpen()
      Returns:
      whether a dropdown is currently open.
    • onMeasure

      protected Size onMeasure(Constraints constraints)
      Description copied from class: Widget
      Reports the size this widget wants within constraints. Called once per layout pass, and the result is cached against the constraints and the resolved axes (size step, layout direction, locale), so it must be a pure function of them and of this widget's own state.

      Resolve the ControlSize and the LayoutDirection once each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, and I18n.locale() answers it wherever text is resolved.

      Specified by:
      onMeasure in class Widget<MenuBar>
    • baselineOffset

      protected float baselineOffset()
      The baseline BASELINE rows align on, the expression onPaint(limn.graphics.Canvas) draws titles with.
      Overrides:
      baselineOffset in class Widget<MenuBar>
    • onPaint

      protected void onPaint(Canvas canvas)
      Description copied from class: Widget
      Widget's own background/content, in local coordinates.
      Overrides:
      onPaint in class Widget<MenuBar>
    • onHitTest

      protected @Nullable Widget<?> onHitTest(float localX, float localY)
      The bar's own points are its titles'. The strip past the last one has nothing in it and is left to whatever is beneath, as it was when the bar ended at its last title and as a platform's empty menu-bar stretch is inert: a press there opens nothing and takes the keyboard from nobody, and the pointer over it is the one beneath rather than the hand the titles show.
      Overrides:
      onHitTest in class Widget<MenuBar>
      Parameters:
      localX - the point's x in this widget's space
      localY - the point's y in this widget's space
      Returns:
      the widget the point lands on, or null
    • onMouseEvent

      protected void onMouseEvent(MouseEvent event)
      Description copied from class: Widget
      Mouse events (bubbling); call event.consume() when handled.
      Overrides:
      onMouseEvent in class Widget<MenuBar>
    • onKeyEvent

      protected void onKeyEvent(KeyEvent event)
      Description copied from class: Widget
      Key events (focused widget first, then ancestors).
      Overrides:
      onKeyEvent in class Widget<MenuBar>
    • onAttached

      protected void onAttached()
      The bar answers for the whole scene while it is attached: accelerators, Alt/F10 and Alt+letter all arrive here, after the focused widget has declined them.
      Overrides:
      onAttached in class Widget<MenuBar>
    • onDetached

      protected void onDetached()
      A bar removed from the tree must never strand its dropdown on screen, nor keep answering the scene's keyboard from outside it, which is what the unhook is for.
      Overrides:
      onDetached in class Widget<MenuBar>
    • onFocusGained

      protected void onFocusGained()
      Description copied from class: Widget
      Called when this widget takes keyboard focus. Default: nothing.
      Overrides:
      onFocusGained in class Widget<MenuBar>
    • onFocusLost

      protected void onFocusLost()
      Description copied from class: Widget
      Called when this widget loses keyboard focus. Default: nothing.
      Overrides:
      onFocusLost in class Widget<MenuBar>
    • onAccessibility

      protected void onAccessibility(Accessibility a)
      The strip, and one node per title.

      Why the bar declares a selection. Its whole keyboard model is a cursor running along the titles, and the difference between two trees turns a moved cursor into an active-descendant event only for a node that carries the selection facet. Without it, marking a title active is a bit nobody diffs and Left/Right along the strip is silent to a reader. Not required, because with the bar unfocused and nothing open there is honestly no current title; not multi-selectable, because one menu is down at a time.

      Why the toolkit names it. The constructor makes the bar focusable unconditionally, so it is a permanent tab stop, and a focusable node with no name at all is what the gallery's invariants refuse. The class holds no string of its own to offer. The name is redundant with the role, which is the argument against it and is why it is written down; an application that wants better still wins, because setAccessibleName is applied after this hook.

      Each title's box is its own, not the widget's: the same prefix sum over titleWidth(int, limn.components.SizeTokens) that measure, paint, titleAt(float, limn.components.SizeTokens, boolean) and the dropdown's anchor share, with the row and the direction resolved once for the whole hook for the reason the geometry section states. A title past the strip's own width is not marked off screen: this class has no overflow handling of any kind, nothing clips it, and it really is painted over its neighbour and really is reachable by the keyboard. The overflow is a layout defect and telling a reader the title is not on screen would be a second one.

      The current title is not the hover. hoverIndex is both the pointer's hover and the keyboard's cursor in this class, and publishing a bare hover as active would announce a title to a user whose keyboard is in a text field, once per mouse move across the strip. The open menu is consulted first and is not folded into the hover, because a pointer leaving the strip clears the hover while a dropdown is still down.

      A title whose menu is empty carries no operation. PopupMenu refuses to show an empty menu on every platform, every time, so the popup state, the expanded facet, the opening verbs and the key binding are all gated on the menu having items — an absent verb rather than one that is always refused. It keeps FOCUS, which opens nothing: the arrows walk onto it, so a reader may too. The emptiness is read live from a model this bar never hears about, so a menu filled after the bar was drawn is republished on the next frame that walks, and on a quiet window not at all.

      Overrides:
      onAccessibility in class Widget<MenuBar>
      Parameters:
      a - the builder for this node
    • onSyntheticAction

      protected boolean onSyntheticAction(long key, Accessible.Action action, Accessible.Argument arg)
      Opens or closes one title's dropdown, through the same private path a click on it takes.

      openMenu(int) and closeMenu() are exactly what the pointer's own press branch calls, so an assistive technology's open builds the same PopupMenu, wires the same root-arrow callbacks and the same pointer pass-through, and reaches the application the same way. Nothing gains a public entry point for it.

      Three refusals, each of them a fact rather than a guard against a caller. A title whose menu is empty can never open one, so it is refused here as it is left without a verb above — deliberately stricter than the pointer, which has no such test and wedges the bar on such a title. A title already down is refused rather than reopened, because reopening tears the cascade down and builds another one, which the pointer never does. And a collapse addressed to a title that is not the open one is refused rather than quietly closing whatever is.

      "Down" is the fact the describe hook publishes: this title is the open one and a cascade is on screen. A title whose show was refused is published collapsed with the verbs that open it, so its EXPAND asks for the open again and its COLLAPSE is refused (until 2026-09-15 this hook and the verbs read the index alone, and the refused title published COLLAPSE beside collapsed).

      The verbs answered are exactly the verbs onAccessibility(limn.accessibility.Accessibility) publishes for the title's state: SHOW_MENU and EXPAND open a closed title, COLLAPSE closes the open one, FOCUS takes the keyboard and puts the bar's cursor on any title while no menu is down, without opening it, and PRESS — accepted here as a third synonym until 2026-09-14 — is refused, because a verb a node performs without publishing is one a reader cannot see and one platform invokes by accident.

      The answer to an open is whether a cascade is on screen, not whether the bar now believes one is: the show is refused outright over a window that cannot host a popup, and answering true there would report an action that did nothing.

      Overrides:
      onSyntheticAction in class Widget<MenuBar>
      Parameters:
      key - the title's index, which is its key
      action - what is being asked
      arg - unused; every verb here is parameterless
      Returns:
      whether this bar did it