Class Button

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

public final class Button extends Widget<Button>
Push button with the full state set (normal, hover, pressed, disabled, focused) and an onAction(Runnable) fired by click or by Enter/Space while focused. All visuals come from the Theme. Keep actions short (the slow-handler instrumentation will flag violations); heavy work goes through Ui.async.

Sizes follow the ControlSize resolved on this widget: every metric comes from the SizeTokens row, every weight from Strokes. Tokens are resolved once per pass into a local and threaded down; resolving twice inside one pass is how measure and paint end up disagreeing about where the label sits.

A caption alone, or an icon alone, is centred, so a text-only button draws the same coordinates whichever way its subtree reads. An icon beside a caption is leading: it stands from the edge reading starts from by the inset a text field's or a combo box's leading icon keeps, so icons line up down a form, and the caption is centred in the rest of the button inside the button's own padding, which at the width the button asks for puts it the icon gap away. Reading right to left the whole of that is mirrored. Whether the glyph drawn inside the square turns around as well is the caller's to say — see Icon.Mirroring.

An icon with no caption makes a square button: as wide as it is tall, which is the control height of its ControlSize, 24, 28, 32, 40 or 50 points, the icon in the middle. That is the width it asks for, and like any widget's it is the parent's to grant: a button that should be wider or narrower says so with Widget.setMinWidth(float) or Widget.setMaxWidth(float), both together for an exact width, and the icon stays in the middle of the box it is given. Those bounds are the one way to say it, for this button as for any other widget; with none set, the square is the default. At the smallest step the square is 24 points, the pointer target this toolkit keeps every control at or above. A button with a caption, beside an icon or not, asks for its caption's width and padding as before.

  • Constructor Details

    • Button

      public Button(String text)
      A button with a fixed caption; see the I18nString constructor for localized text.
    • Button

      public Button(I18nString text)
      A button whose caption follows the UI language; see I18nString.
  • Method Details

    • setIcon

      public Button setIcon(@Nullable Icon newIcon)
      Sets a leading icon, drawn tinted to the label color. null clears it. Leading is the end of the caption block reading starts from, so the square trades ends with the caption when the button reads right to left; the glyph inside it is drawn as authored. Use setIcon(Icon, Icon.Mirroring) for an icon that means a direction.
    • setIcon

      public Button setIcon(@Nullable Icon newIcon, Icon.Mirroring mirroring)
      A leading icon that says whether its glyph turns around when the interface does. Only the code that placed an icon knows whether its arrow means "back" or "download", which is why this is a flag here and never a classification inside the toolkit.
      Parameters:
      mirroring - Icon.Mirroring.NEVER unless this glyph is directional; the square's own end of the block moves either way
    • setStyle

      public Button setStyle(Button.Style newStyle)
      Which of the three looks this button is drawn in; Button.Style.PRIMARY by default.
      Parameters:
      newStyle - the look; never null
      Returns:
      this button
    • style

      public Button.Style style()
      Returns:
      the look this button is drawn in
    • setDestructive

      public Button setDestructive(boolean value)
      Marks the button as one that destroys something ("Delete order"), drawn in the palette's danger colour at whichever Button.Style it has: a primary one is filled in it, a secondary and a tertiary one write their caption in it.

      Nothing is published for it: no platform carries the notion to a reader, and the caption is what says what the button does. A dialog's destructive button is never its default one; see Dialog.addDestructiveButton(I18nString, String).

      Parameters:
      value - whether the button destroys something
      Returns:
      this button
    • isDestructive

      public boolean isDestructive()
      Returns:
      whether the button is drawn as one that destroys something
    • onAction

      public Button onAction(@Nullable Runnable newAction)
      The application's response to the user pressing the button: a click, Space or Enter while focused, an assistive technology's press. Not called while disabled. A press is announced to the watchers as INVOKED first.
      Parameters:
      newAction - the handler, or null to clear the slot
      Returns:
      this button
      Throws:
      IllegalStateException - if a handler is already registered
    • handleUserChange

      protected void handleUserChange(Change.Aspect aspect)
      Description copied from class: Widget
      Runs this widget's application handler for a change it has just announced. Called by Widget.runHandler(limn.scene.Change), and so by Widget.notifyChange(limn.scene.Change), only for a USER origin and after every watcher has run.

      A component overrides it once and switches on the aspect, reading its own accessors for the payload: a slider passes value(), a combo box selectedIndex(), a text field text(). A component with no handler does not override it.

      An override that does not recognise the aspect must call super, because a subclass of a component that has a handler inherits that component's dispatch through this method: a SearchField that overrode this for its own aspect and did not chain would silently delete TextField's. The ordering lives in the base class, this is the one link in it a subclass can break, and the contract test drives every subclass through its parent's gestures to catch one that does.

      Overrides:
      handleUserChange in class Widget<Button>
      Parameters:
      aspect - what changed; the origin is USER or this is not called
    • text

      public String text()
      The caption as it currently reads, an access letter's & mark taken out ("&Save" reads "Save"); see textSource() for the value behind it.
    • textSource

      public I18nString textSource()
      The localizable value this button holds, which a language change re-resolves.
    • setText

      public Button setText(String newText)
      Replaces the caption with a fixed string. UI thread only.
    • setText

      public Button setText(I18nString newText)
      Replaces the caption with a value that follows the UI language. UI thread only.
    • 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<Button>
    • baselineOffset

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

      protected float paintOutset()
      The focus ring is drawn OUTSIDE the box: centred Strokes.FOCUS_GAP_BUTTON out with a Strokes.FOCUS_RING pen, so its outer ink reaches Strokes.FOCUS_RING_OUTSET, and damage has to know that. Scene inflates a widget's damage rect by 1 + paintOutset(), so without this override the fade-out of a ring on the toolkit's most-used widget sheds stale pixels under partial rendering.

      Declares reach only; it moves no ink. Checkbox, RadioButton and Label carry the same kind of override for the same reason.

      Overrides:
      paintOutset in class Widget<Button>
    • onPaint

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

      protected void onAccessibility(Accessibility a)
      What this button is to an assistive technology: one Accessible.Role.BUTTON named by the caption it holds and offering Accessible.Action.PRESS, and nothing else.

      The name is the held I18nString handed over by reference, never text(): the walk compares the reference, the locale and the translation epoch, so a hover or focus fade that damages this button on every frame concludes that nothing moved without allocating. It is handed over even when the caption is empty, because the walk names a nameless node from its tooltip and describes a named one with it, which is how an icon-only button is named by the tooltip it should have and a captioned one keeps the tooltip as its description. The role is unconditional so that a disabled button stays in the tree and is heard as disabled rather than vanishing.

      Declared here is all a reader is told. The focus and scroll-into-view verbs, the enabled, visible, showing, focusable and focused states and the box are the walk's; the pressed visual is not published, because no platform maps it and a press from an assistive technology never arms; whether this is a dialog's default button is the dialog's fact and not this widget's, which holds no such notion; and the icon is paint with no name and no operation, so it is not a node.

      Overrides:
      onAccessibility in class Widget<Button>
      Parameters:
      a - the node being described
    • onAccessibilityAction

      protected boolean onAccessibilityAction(Accessible.Action verb, Accessible.Argument arg)
      Performs a press an assistive technology asked for, exactly as a click or a Space release does: the same private action slot, so the application is notified the same way. Any other verb is refused, and so is a press while this button is disabled, since the click and key paths rely on the scene never delivering to a disabled widget and this one keeps the guard itself.

      It does not arm, invalidate or request focus. A pointer click gets click-to-focus from the scene and not from this widget, a reader that wants focus has the focus verb, and a press that pulled focus out of a text field onto a tool-bar button would be worse than one that leaves it.

      Overrides:
      onAccessibilityAction in class Widget<Button>
      Parameters:
      verb - what was asked
      arg - ignored; a press carries none
      Returns:
      whether the action ran
    • onMouseEvent

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

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

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

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