Class ContextMenus

java.lang.Object
limn.components.ContextMenus

public final class ContextMenus extends Object
Gives any widget a context menu, and answers what a request for one looks like.

 column.add(ContextMenus.attach(fileList, () -> new Menu()
         .addItem("Rename…", this::rename)
         .addItem("Delete",  this::delete)));
 

It wraps rather than sets, and that is not a style choice. A widget has no context-menu property to assign because a Widget cannot name a Menu: the two live on opposite sides of a dependency boundary, and the toolkit side is the one that depends on nothing. So the menu is held here, one layer above the widget, which is the nearest place the type exists. attach(limn.scene.Widget, java.util.function.Supplier<limn.components.Menu>) therefore returns the widget to add to the tree, not the one passed in.

The gesture is more than the right button. A keyboard user asks for the menu with the Menu key or Shift+F10 and never presses a mouse button at all; a hand-rolled right-press check leaves them with no route to the same commands, which is the failure this class exists to stop repeating. isRequest(MouseEvent) and isRequest(KeyEvent) are the same question for code that already has its own onMouseEvent and only wants the answer.

  • Method Summary

    Modifier and Type
    Method
    Description
    static Widget
    attach(Widget content, Supplier<Menu> source)
    Wraps content so that a context request anywhere inside it opens the menu source supplies.
    static boolean
    Whether this event is the keyboard's request for a context menu: the dedicated Menu key that sits between the right Alt and Control on most keyboards, or Shift+F10 for the many that do not have one.
    static boolean
    Whether this event is the pointer's request for a context menu, for a widget that has its own onMouseEvent and wants the answer rather than the wrapper.
    static void
    showAt(Widget anchor, Menu menu, float localX, float localY)
    Opens menu with its corner at a point in anchor's own coordinates (what MouseEvent.x() reports) rather than the scene coordinates a popup is placed in.
    static void
    showForFocus(Widget anchor, Menu menu)
    Opens menu for a request that carries no point: the keyboard route.

    Methods inherited from class java.lang.Object

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

    • attach

      public static Widget attach(Widget content, Supplier<Menu> source)
      Wraps content so that a context request anywhere inside it opens the menu source supplies. The wrapper is invisible: it measures, lays out and paints as content alone would, and adds nothing to the picture.

      source is asked at the moment of the gesture, never at attach time, because the interesting menus depend on what is under the pointer or what is selected when it happens. Returning null (or a menu with no rows) opens nothing, which is how a region says "not here" for a particular spot without the caller writing a second gesture check.

      A child that answers the gesture itself keeps it. Events bubble and stop at the first widget that consumes them, so a TextField inside an attached region still raises its own Cut/Copy/Paste menu rather than the region's. That is the desired order and not a leak: the specific widget is the better answer.

      Parameters:
      content - the widget to give a menu to; it becomes the wrapper's only child
      source - consulted per gesture, on the UI thread, and may answer null
      Returns:
      the widget to put in the tree in content's place
    • isRequest

      public static boolean isRequest(MouseEvent event)
      Whether this event is the pointer's request for a context menu, for a widget that has its own onMouseEvent and wants the answer rather than the wrapper.

      True only for the press, never the release or the click: a menu that waited for the release would open under a button the user has already let go of, and every desktop raises this one on the way down.

    • isRequest

      public static boolean isRequest(KeyEvent event)
      Whether this event is the keyboard's request for a context menu: the dedicated Menu key that sits between the right Alt and Control on most keyboards, or Shift+F10 for the many that do not have one.

      True only on the press, and false for a repeat: holding the key must not raise a stack of menus.

    • showAt

      public static void showAt(Widget anchor, Menu menu, float localX, float localY)
      Opens menu with its corner at a point in anchor's own coordinates (what MouseEvent.x() reports) rather than the scene coordinates a popup is placed in. The conversion is the whole reason this exists: it is two field reads, it is wrong in a way that only shows up on a scrolled or nested widget, and every place that hand-rolled a context menu wrote it out again.

      A null or empty menu opens nothing, so a caller that computes its rows can hand the result straight over.

      The point is not mirrored, and that is the whole of the direction story here. A menu raised at the pointer lands on the pointer reading either way; which corner of the column meets that point is PopupMenu's decision and is already taken there. Reflecting the point as well would move the menu away from the spot the user aimed at.

    • showForFocus

      public static void showForFocus(Widget anchor, Menu menu)
      Opens menu for a request that carries no point: the keyboard route. It drops from the lower leading corner of whatever currently holds focus — the bottom left reading left to right and the bottom right reading right to left — so the menu appears at the row or field the user was on rather than at a corner of the region containing it.

      Falls back to anchor's own lower leading corner when nothing in the scene has focus, which is the only place left that is still related to the request.

      The direction is the focused widget's, not the region's — for the corner and for the cascade both. A right-to-left field inside a left-to-right form starts reading at its own right edge, and that is the corner the user's eye is at when the key arrives; taking the region's direction instead would drop the menu at the end of a field the user is not reading from. The cascade must agree: a menu whose corner is the field's right edge but whose column grows as the region reads opens away from the field it dropped from, so the popup is anchored on the same widget the corner came from, and its growth, its step and its corner are one answer.