Class PopupMenu

java.lang.Object
limn.components.PopupMenu

public final class PopupMenu extends Object
A context menu / dropdown that presents a Menu (with checkable items, separators and cascading submenus) as a real native window: an undecorated, floating, transparent OS window (like a ComboBox popup) that may overflow the owner window exactly like a true OS menu.

The menu is positioned relative to the screen and never overflows the visible area: each column flips (opens above / to the left) and then clamps against the monitor's work area, so it stays on screen no matter where the pointer is. A single native window is sized to the open cascade's bounding box and re-fit as submenus open/close; the whole cascade is rendered and hit-tested inside it.

It grabs keyboard focus while open (arrows navigate, Enter chooses, Esc closes); a press back in the owner window, an item choice, or Esc dismisses. Requires a scene bound to a window that is still open: a scene in no window, or in one that has closed, has nowhere to draw it, and the menu opens nothing there.

Two presentations: a window of its own, or an overlay inside the owner window; same cascade, flip, clamp and navigation either way, but an overlay is clamped to that window rather than to the display, so a menu near the bottom edge has less room than it would elsewhere. setDisplayMode(limn.components.DisplayMode) chooses; displayMode() answers what was actually used, which is not always the same thing. Three cases have no choice and always draw in scene: a fullscreen window, which at a named mode is minimized as soon as another window takes the focus, as a menu's does at once, and which at the mode the display is in already covers the screen, so the menu loses nothing inside it; any window reporting no WindowPeer.supportsAbsolutePositioning() (Wayland), where a menu window would open somewhere other than the anchor; and a window reporting no display, as one does where the windowing system lists no monitor, the only one asleep or a session with none, which leaves no work area to keep a menu window inside. All three are asked when the menu opens; a menu already open in a window of its own when the owner enters or leaves fullscreen closes instead, cascade and all, as if dismissed, running no item: see DisplayMode.


 new PopupMenu(menu).showAt(grid, mouseX, mouseY); // context menu at the pointer
 

The whole cascade renders at one ControlSize step, resolved once when the menu opens: from setControlSize(limn.scene.ControlSize) if set, else through the anchor widget, else from the owner scene. Mixing steps across columns would desynchronise the submenu y-alignment, the column overlap and the shared border weight.

And at one LayoutDirection, resolved through the same link at the same moment. Reading right to left the whole cascade is mirrored: the root column aligns to the anchor's right edge, submenus open to the left, the check gutter is on the right and the accelerator hint and the submenu chevron on the left, and the arrow keys that open and close a submenu swap. What does not mirror is the check mark itself, which no platform mirrors, and the accelerator strings, which name physical keys.

  • Constructor Summary

    Constructors
    Constructor
    Description
    A popup presenting menu; show it with one of the show* methods.
  • Method Summary

    Modifier and Type
    Method
    Description
    void
    Closes the menu (idempotent).
     
     
    boolean
    Whether the cascade is currently showing.
    Observes this menu closing: a dismissal, an item chosen, or a close() from code alike, because a close is a lifecycle fact and not a gesture.
    onRootLeading(@Nullable Runnable listener)
    Hook for the arrow that walks toward the leading side at the root column (MenuBar: go to the previous menu).
    onRootTrailing(@Nullable Runnable listener)
    Hook for the arrow that walks toward the trailing side on a root item without a submenu (MenuBar: the next menu).
    @Nullable NativeWindow
    The window the open menu is drawn in, for an application that captures it or a test that drives it: the menu is a window of its own, so a capture of the owner window does not contain it.
    setControlSize(@Nullable ControlSize size)
    Pins the step of the whole cascade, overriding what the anchor or the owner scene would give it, the escape hatch for "a compact context menu over a MEDIUM surface".
    static void
    The presentation every PopupMenu built after this call starts from, including the ones the toolkit builds where an application never sees the object: MenuBar's dropdowns, ContextMenus, and a text field's own context menu.
    Asks for a presentation; see DisplayMode.
    setModal(boolean value)
    Makes the native popup block the owner window (window-modal), enforced by the backend exactly like a Dialog: a press on the parent is ignored and beeps, and the menu closes only on a choice or Escape.
    void
    showAnchored(Scene ownerScene, float ax, float ay, float aw, float ah)
    Opens the menu attached to an anchor rectangle in scene coordinates.
    void
    showAnchored(Widget<?> anchor, float ax, float ay, float aw, float ah)
    Opens the menu attached to an anchor rectangle in anchor's scene coordinates, sized at the step resolved on anchor: it drops below the rect (a menu-bar dropdown) and flips above when there is no room.
    void
    showAt(Scene ownerScene, float x, float y)
    Opens the menu with its corner at the scene point (x, y), at the scene's step.
    void
    showAt(Widget<?> anchor, float x, float y)
    Opens the menu with its corner at the scene point (x, y) (a context menu at the pointer), sized at the step resolved on anchor.

    Methods inherited from class java.lang.Object

    clone, equals, finalize, getClass, hashCode, notify, notifyAll, toString, wait, wait, wait
  • Constructor Details

    • PopupMenu

      public PopupMenu(Menu menu)
      A popup presenting menu; show it with one of the show* methods.
  • Method Details

    • observeClose

      public Subscription observeClose(Runnable observer)
      Observes this menu closing: a dismissal, an item chosen, or a close() from code alike, because a close is a lifecycle fact and not a gesture. Any number may observe; the menu bar that built this popup holds one, and an application may hold another.
      Parameters:
      observer - what to run when the menu closes; never null
      Returns:
      a handle that unregisters; cancelling it twice is a no-op. UI thread
    • onRootLeading

      public PopupMenu onRootLeading(@Nullable Runnable listener)
      Hook for the arrow that walks toward the leading side at the root column (MenuBar: go to the previous menu). That is LEFT reading left to right and RIGHT reading right to left; the physical key is flipped once, inside this class, so a listener registered here means "the previous menu" in either direction and must not flip again.

      One handler at a time: null clears it, and setting a second while one is set throws IllegalStateException.

    • onRootTrailing

      public PopupMenu onRootTrailing(@Nullable Runnable listener)
      Hook for the arrow that walks toward the trailing side on a root item without a submenu (MenuBar: the next menu). The trailing arrow is the one that opens a submenu where there is one, which is why it is the one that walks on where there is not; onRootLeading states the direction rule both share.

      One handler at a time: null clears it, and setting a second while one is set throws IllegalStateException.

    • setModal

      public PopupMenu setModal(boolean value)
      Makes the native popup block the owner window (window-modal), enforced by the backend exactly like a Dialog: a press on the parent is ignored and beeps, and the menu closes only on a choice or Escape. Default false (a press outside dismisses). Purely native: no in-scene overlay.
    • setControlSize

      public PopupMenu setControlSize(@Nullable ControlSize size)
      Pins the step of the whole cascade, overriding what the anchor or the owner scene would give it, the escape hatch for "a compact context menu over a MEDIUM surface". Applied when the menu opens; a call on an already-open menu is ignored, because the columns' geometry snapshots were built from the row captured at open (see tokens). null restores inheritance. UI thread only.
    • isOpen

      public boolean isOpen()
      Whether the cascade is currently showing.
    • popupWindow

      public @Nullable NativeWindow popupWindow()
      The window the open menu is drawn in, for an application that captures it or a test that drives it: the menu is a window of its own, so a capture of the owner window does not contain it. Supported API, not a test hook.
      Returns:
      this menu's native window while open, else null, which is also the answer while the menu is drawn in the scene
    • showAt

      public void showAt(Widget<?> anchor, float x, float y)
      Opens the menu with its corner at the scene point (x, y) (a context menu at the pointer), sized at the step resolved on anchor. This is the overload apps should use: new PopupMenu(m).showAt(grid, event.x(), event.y()) makes a context menu over an XSMALL data grid XSMALL, which the Scene overload cannot express.

      The coordinates are the anchor's scene coordinates (what MouseEvent.x() reports), not anchor-local ones.

      The menu hangs from anchor: it closes when the anchor leaves the scene, by itself, with a container or with the page Scene.setRoot(limn.scene.Widget<?>) replaces, and when it stops being shown, so its rows never act on a widget that has gone. A row of a list, a table or a tree that a refresh keeps because its item is still there does not leave: the menu stays, acting on the item as the row was built with it.

    • showAt

      public void showAt(Scene ownerScene, float x, float y)
      Opens the menu with its corner at the scene point (x, y), at the scene's step. It hangs from no widget: nothing leaving the scene closes it.
    • showAnchored

      public void showAnchored(Widget<?> anchor, float ax, float ay, float aw, float ah)
      Opens the menu attached to an anchor rectangle in anchor's scene coordinates, sized at the step resolved on anchor: it drops below the rect (a menu-bar dropdown) and flips above when there is no room. A no-op for a widget outside a scene. Hangs from anchor as showAt(Widget, float, float) does. UI thread only.
    • showAnchored

      public void showAnchored(Scene ownerScene, float ax, float ay, float aw, float ah)
      Opens the menu attached to an anchor rectangle in scene coordinates. A no-op on a scene bound to no window, or to one that has closed. UI thread only.

      The cascade takes the step resolved on the scene's root, the documented fallback, and the difference between "a context menu over an XSMALL data grid is XSMALL" and "it is whatever the scene says". Pass the widget it belongs to (showAnchored(Widget, float, float, float, float)) to get the former. When Scene.setRoot(limn.scene.Widget<?>) switches the page while the menu is open, the menu stays open and is hosted by the new root, holding nothing of the page that left; its columns keep the geometry they were built with until it closes.

    • close

      public void close()
      Closes the menu (idempotent). UI thread only.
    • setDisplayMode

      public PopupMenu setDisplayMode(DisplayMode mode)
      Asks for a presentation; see DisplayMode. Default DisplayMode.NATIVE_WINDOW, which is what the platform's own menus do wherever a window can be placed at an anchor.

      A preference, not a guarantee, and only in one direction: a menu asked for NATIVE_WINDOW still draws in scene on Wayland, where a menu window does not land where the anchor is, in a fullscreen window, for the reasons DisplayMode gives, and in a window that reports no display, with no work area to keep a menu window inside. IN_SCENE is always honoured; it needs nothing from the platform, a display included. Read displayMode() for what happened.

      Takes effect on the next open; a menu already on screen is not re-presented under it.

    • setDefaultDisplayMode

      public static void setDefaultDisplayMode(DisplayMode mode)
      The presentation every PopupMenu built after this call starts from, including the ones the toolkit builds where an application never sees the object: MenuBar's dropdowns, ContextMenus, and a text field's own context menu.

      This exists because the choice is almost never per-menu. An application either shows its popups in windows or it does not: because everything it draws has to be inside one window for a screen share, because it is being screenshotted for its own documentation, or because it is a kiosk with no desktop behind it to float over. Setting that per call site means finding every call site, including the ones inside this toolkit.

      Process-wide, like Theme.setCurrent(limn.components.Theme) and Fonts. Menus already open are unaffected, and setDisplayMode(limn.components.DisplayMode) still overrides it for one menu. UI thread.

    • defaultDisplayMode

      public static DisplayMode defaultDisplayMode()
      Returns:
      the presentation new menus start from.
    • displayMode

      public DisplayMode displayMode()
      Returns:
      how the menu is presented: while it is open, what it actually chose, including a platform forcing IN_SCENE over a request for a window. While it is closed, what setDisplayMode(limn.components.DisplayMode) last asked for, since nothing has been decided yet.