Class Spinner

java.lang.Object
limn.scene.Widget
limn.components.Spinner

public class Spinner extends Widget
A stepper input for a bounded value, in two modes:
  • Spinner.Mode.NUMERIC: new Spinner(min, max, step) shows a number (decimals inferred from the step);
  • Spinner.Mode.TIME: time() shows HH:MM, where the up/down arrows adjust the focused field (hours by 60, minutes by the step) and Left/Right (or a click) move between the two fields.

Adjust with the up/down buttons or the keyboard:

  • Up/Down, and Left/Right in numeric mode = ±step;
  • Shift + arrow = ±10 steps, the same as PageUp/PageDown;
  • Alt + arrow = one unit of the last displayed digit, for the values a step deliberately overshoots. Alt wins if Shift is also held;
  • PageUp/PageDown = ±10 steps, Home/End = min/max.
Time mode ignores Shift and Alt: its two fields already are the coarse/fine split. The value is clamped to [min, max]; onChange fires on user changes.

The value can also be typed. Type a digit and the spinner turns into a one-line text field with the old value selected, so the first keystroke replaces it; click into the value to place the caret instead. Enter commits, Escape restores, and moving focus away commits. Text that does not parse commits nothing and the value stands; a number outside the bounds is clamped.

Copy and paste work in both states. With no edit in progress Ctrl/Cmd+C copies the whole value as shown; while editing it copies the selection, and Ctrl/Cmd+X cuts it. Pasting replaces the value when nothing is being edited and inserts at the caret when something is. Within an edit, Ctrl/Cmd+Z and Ctrl/Cmd+Y undo and redo the typing.

Stepping never goes away: Up/Down step even mid-edit, taking the typed text as the number to step from, and Left/Right resume stepping (or switching time fields) once the edit is committed. setEditable(boolean) turns typing off entirely.

A programmatic setValue(double) cancels an edit in progress, because the half-typed text is no longer about the number now in the field and committing it later would silently undo whatever moved it.

The mouse wheel does not change the value, and must not: a spinner in a scrolling panel would otherwise swallow the wheel whenever the pointer crossed it, stopping the scroll and editing a field the user was only scrolling past. The wheel passes through to the scrollable ancestor.

Reading right to left the stepper column and the value swap sides, and the arrows that name the value swap with them: Left raises it and Right lowers it, because the low end of a horizontal axis sits on the side reading starts from. Home and End do not swap; they name min and max, which are not sides. Neither do time mode's Left and Right: those pick the hours and the minutes of an HH:MM run, and a run of digits keeps its own left-to-right order inside a right-to-left form, so the fields do not move and neither does the key that selects one. The up and down arrows are drawn on the vertical axis and are unchanged.

Sizes follow the widget's ControlSize. At XSMALL each stepper half is 18 × 12 pt, below fine-motor comfort, so a dense form should treat the keyboard as the primary way to adjust the value there.


 Spinner qty = new Spinner(0, 99, 1).setValue(1);
 Spinner alarm = Spinner.time().setValue(7 * 60 + 30); // 07:30
 
  • Constructor Details

    • Spinner

      public Spinner(double min, double max, double step)
      Numeric spinner over [min, max] stepping by step (> 0).
  • Method Details

    • time

      public static Spinner time()
      A 24-hour HH:MM time spinner (00:00–23:59, 1-minute step).
    • time

      public static Spinner time(int minMinutes, int maxMinutes, int stepMinutes)
      A time spinner bounded to [minMinutes, maxMinutes], stepping the minutes field by stepMinutes.
    • setValue

      public Spinner setValue(double newValue)
      Sets the value programmatically (clamped + snapped); does not fire onChange. Cancels an edit in progress; see the class comment.
    • setEditable

      public Spinner setEditable(boolean canType)
      Whether the value can be typed into (default true). Turning it off leaves a pure stepper: no caret, no clipboard, and Left/Right keep stepping (or switching time fields) as they always did.
    • isEditable

      public boolean isEditable()
      Whether the value can be typed as well as stepped.
    • isEditing

      public boolean isEditing()
      Whether the user is typing into the value right now.
    • value

      public double value()
      The current value, always within [min, max]. Minutes since midnight in time mode.
    • setSnapToStep

      public Spinner setSnapToStep(boolean snap)
      Whether values are snapped onto the step grid (min + k * step). On by default: a spinner that owns its value keeps it tidy.

      Turn it off when the spinner displays a value it does not own: a property inspector bound to a document, a field showing a number that came from a file or another tool. Snapping would silently misreport such a value (1200 shown as 1201 on a grid anchored at 1), and rewrite it the moment the user nudges the field. Stepping still moves by step; it just does so from the value actually held.

    • snapsToStep

      public boolean snapsToStep()
      Whether committed values are rounded to a multiple of the step.
    • min

      public double min()
      Lower bound, inclusive.
    • max

      public double max()
      Upper bound, inclusive.
    • mode

      public Spinner.Mode mode()
      Whether this spinner shows a number or a time.
    • onChange

      public Spinner onChange(Consumer<Double> listener)
      Called with the new value on user changes only, not on setValue(double).
    • text

      public String text()
      Returns:
      the value as it is displayed ("07:30" in time mode, a number otherwise).
    • baselineOffset

      protected float baselineOffset()
      Description copied from class: Widget
      Distance from this widget's top edge to its first text baseline, in logical points: the alignment reference for Flex.CrossAlignment.BASELINE. Default height(): align on the bottom edge, the correct fallback for a widget with no text. Text-bearing components override with the expression they already paint with, (height() - metrics.height()) / 2 + metrics.ascent().

      Valid only once this widget has been given a box. Flex guarantees that: it lays every child of a BASELINE line out at cross position 0 first, reads the baselines, then repositions with Widget.moveChild(limn.scene.Widget, float, float), which moves without re-running layout.

      Overrides:
      baselineOffset in class Widget
    • 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
    • onPaint

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

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

      protected void onCharTyped(CharEvent event)
      Description copied from class: Widget
      Committed text input (focused widget first, then ancestors).
      Overrides:
      onCharTyped in class Widget
    • onKeyEvent

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

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

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