Class Slider

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

public final class Slider extends Widget<Slider>
Horizontal slider selecting a value in [min, max]. Drag the thumb or click the track to jump; keyboard arrows nudge by the step (Home/End jump to the ends, PageUp/PageDown by ten steps). A continuous slider (step == 0) nudges by 1% of the range. The filled portion, thumb and focus ring all come from the Theme; the thumb grows on hover and the focus ring fades in through the shared Transition animator.

 Slider volume = new Slider(0, 100).setStep(5).setValue(30);
 volume.onChange(v -> audio.setGain(v / 100f));          // the user moved it
 volume.observeChanges((w, c) -> readout.setText(...));  // anything moved it
 

onChange and onCommit are the application's response to the user operating the slider; setValue(float) and setStep(float) reach neither. Every change, whatever moved it, reaches a watcher as VALUE, and a drag's release or a key press as COMMITTED.

Sizes follow the ControlSize resolved on this widget. This control's height derives from a focus-ring constant, 2 * (knobHover + FOCUS_GAP_SLIDER + border), rather than from a font or a design height, so it must not be rounded onto the shared control-height ramp. It is also the one place Strokes.MIN_HIT_TARGET binds: at the densest step the natural height falls below the floor, because the ring needs absolute room the knob ramp does not predict.

The preferred length is free: 220 at every step, equal to ProgressBar's so the two stack in a settings panel without a ragged column. Only the thickness moves with the step.

The value axis follows the resolved layout direction: reading right to left, min is at the right end of the track, the fill grows leftwards from it, and Left/Right nudge the value the way the thumb visibly moves. Up/Down are a vertical pair and keep their meaning in both directions, and Home/End/PageUp/PageDown name a value rather than a side, so they never mirror either. The track rectangle itself does not move: the pad is reserved at both ends, so only the meaning of each end changes.

  • Constructor Details

    • Slider

      public Slider(float min, float max)
      A slider over [min, max], starting at min.
  • Method Details

    • setValue

      public Slider setValue(float newValue)
      Sets the value (clamped + snapped to the step). Announces VALUE/CODE when it moved, and reaches no handler: a caller's write is not the user operating the slider.
      Throws:
      IllegalArgumentException - if newValue is not a number
    • value

      public float value()
      The current value, always within [min, max].
    • min

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

      public float max()
      Upper bound, inclusive.
    • setStep

      public Slider setStep(float newStep)
      Sets the discrete increment (0 = continuous). Re-snaps the current value, and says so: when the re-snap moves it, VALUE/ADJUSTMENT is announced first, and then the step's own RANGE/CODE last, as the aspect the call names.

      The step is published to an assistive technology as the grid a set is snapped onto, so a change to it is an accessible fact even when the value already sits on the new grid and nothing repaints; that case is flagged to the tree directly rather than through damage the picture does not have.

    • step

      public float step()
      Returns:
      the grid a value is snapped onto, or 0 for a continuous slider
    • onChange

      public Slider onChange(@Nullable FloatConsumer listener)
      The application's response to the user moving the slider: called with the new value on a drag, a key or an assistive technology's step, and never for setValue(float). To hear every move whatever caused it, watch the slider instead.
      Parameters:
      listener - the handler, or null to clear the slot
      Returns:
      this slider
      Throws:
      IllegalStateException - if a handler is already registered
    • onCommit

      public Slider onCommit(@Nullable FloatConsumer listener)
      Called with the value the user settled on: once when a drag ends, and after each change made with the keyboard. onChange fires continuously while a drag is in progress and this fires once at the end of it, which is the difference between a preview and a decision.

      For anything a value change starts that should not be started per pixel: a decode, a request, a recomputation. A control bound only to onChange does that work for every position the thumb passes through; one bound to both can show something cheap on the way and do the real work here.

      Fires on a drag that ends where it began, because the user still chose that value, and does not fire for setValue(float), which is not a user change at all.

      Parameters:
      listener - the handler, or null to clear the slot
      Returns:
      this slider
      Throws:
      IllegalStateException - if a handler is already registered
    • handleUserChange

      protected void handleUserChange(Change.Aspect aspect)
      The handler half of the channel: the base class calls this after every watcher, for a USER origin only, and the slot is read here so the ordering stays in the base.
      Overrides:
      handleUserChange in class Widget<Slider>
      Parameters:
      aspect - what changed; the origin is USER or this is not called
    • 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<Slider>
    • onPaint

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

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

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

      protected void onAccessibility(Accessibility a)
      What this slider is to an assistive technology: one Accessible.Role.SLIDER node, horizontal because the class has no other axis, carrying a writable value facet read from the model and offering Accessible.Action.INCREMENT and Accessible.Action.DECREMENT, and nothing else.

      No name is declared, on purpose. The widget holds no string and formats none, so it has nothing to hand over: a tooltip names it through the walk's free default, an application's setAccessibleName wins over that and turns the tooltip into the description, and a caption beside the track is a relation the application declares rather than one guessed here. There is no value text either, and so no cache and no witness: the number is the whole of it, and a read-out is the application's to render beside the control.

      The facet is the value field, already clamped and snapped, with the bounds and the step field as set: 0 when the slider is continuous, because the facet's step is the grid a set is snapped onto and a continuous slider accepts any value in range. The amount the keyboard nudges by is not the same number on a continuous slider and is not published; it is what the two verbs move by. The facet is writable, so SET_VALUE is advertised by its presence and never appears in the action list.

      Neither fade, the drag flag nor the pointer's whereabouts is read here. The hover and focus transitions damage this widget on every frame they run for, and a hook that published either would make the difference find a change on each of those frames; publishing the model means they walk, bounded and without allocating, and publish nothing. A drag moves the model and republishes once per frame, which is what a drag is. The box, the focus and scroll-into-view verbs and the enabled, visible, showing, focusable and focused states are the walk's.

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

      protected boolean onAccessibilityAction(Accessible.Action action, Accessible.Argument arg)
      Performs a change an assistive technology asked for exactly as a key press does: through the value seam at USER, so onChange fires when the value moves, and then a commit whether it moved or not, because a request from a reader is a whole gesture the way a key press is and the change and the decision are the same moment. Never through setValue(float), which is a caller's write: an application that starts something on commit, as the toolkit's own media transport does, would otherwise never hear that the user chose a value.

      INCREMENT and DECREMENT move by the step, or by one percent of the range when the slider is continuous, in the sense Up and Down have: a direction of the value and not a side of the track, so they do not mirror when the layout reads right to left, where Left and Right do. SET_VALUE takes an Accessible.Argument.OfValue and hands it to the same clamp and snap a drag reaches; a value that is not a finite number is refused before it gets there, because the clamp would pass it through untouched. Any other verb is refused, and so is every verb while this widget is disabled; the scene has already re-checked the ancestors, showing and modality before the call arrives, and this guard is the one the key path keeps for itself.

      What a reader hears is the value change the next publish's difference produces on this node. No invocation event is raised for a value verb, on any platform.

      Overrides:
      onAccessibilityAction in class Widget<Slider>
      Parameters:
      action - what was asked
      arg - the value for SET_VALUE; ignored by the two steps
      Returns:
      whether the request ran, which at the ends of the range can be a commit of the value already held
    • onFocusGained

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

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