Class Slider
[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 Summary
Constructors -
Method Summary
Modifier and TypeMethodDescriptionprotected voidhandleUserChange(Change.Aspect aspect) The handler half of the channel: the base class calls this after every watcher, for aUSERorigin only, and the slot is read here so the ordering stays in the base.floatmax()Upper bound, inclusive.floatmin()Lower bound, inclusive.protected voidWhat this slider is to an assistive technology: oneAccessible.Role.SLIDERnode, horizontal because the class has no other axis, carrying a writable value facet read from the model and offeringAccessible.Action.INCREMENTandAccessible.Action.DECREMENT, and nothing else.protected booleanonAccessibilityAction(Accessible.Action action, Accessible.Argument arg) Performs a change an assistive technology asked for exactly as a key press does: through the value seam atUSER, soonChangefires 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.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 forsetValue(float).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.protected voidCalled when this widget takes keyboard focus.protected voidCalled when this widget loses keyboard focus.protected voidonKeyEvent(KeyEvent event) Key events (focused widget first, then ancestors).protected SizeonMeasure(Constraints constraints) Reports the size this widget wants withinconstraints.protected voidonMouseEvent(MouseEvent event) Mouse events (bubbling); callevent.consume()when handled.protected voidWidget's own background/content, in local coordinates.setStep(float newStep) Sets the discrete increment (0= continuous).setValue(float newValue) Sets the value (clamped + snapped to the step).floatstep()floatvalue()The current value, always within[min, max].Methods inherited from class limn.scene.Widget
acceptsTextInput, accessibleLabelAccessKey, accessibleLabelTarget, accessibleLabelText, add, add, announceChange, backdropReach, baselineOffset, baselineOffsetOf, caretRect, children, clipboard, clipHeight, clipsChildren, clipWidth, clipX, clipY, controlSize, cursor, declaredControlSize, declaredLayoutDirection, declaredLocale, focusArrivedBackward, focusArrivedByTraversal, height, hitTest, hitTestChild, id, imageCursor, inheritanceHost, invalidate, invalidate, invalidateAccessible, isEnabled, isFocusable, isFocused, isRightToLeft, isSelfEnabled, isShowing, isVisible, isVisibleByItsOwnFlag, layoutBox, layoutDirection, locale, localToScene, localToSceneBounds, markNeedsContainedLayout, markNeedsLayout, markNeedsLayoutInPlace, maxHeight, maxWidth, measure, minHeight, minWidth, moveChild, moveChild, moveChild, neutralBase, notifyChange, notifyTextEdit, observeChanges, onAccessibilityChild, onAccessibilityChildAction, onAccessibilityChildIdentity, onAttached, onCanEdit, onCharTyped, onDetached, onDragExit, onDragOver, onDragStart, onDrop, onEdit, onHitTest, onLayout, onPaintOverlay, onPreedit, onSyntheticAction, overlayPassesPointer, paintChildren, paintOutset, paintsDecoration, paintsFromBackdrop, paintWidget, parent, remove, remove, requestFocus, requestFocus, revealInView, revealInView, runHandler, scene, sceneNanos, sceneToLocal, self, setAccessibleDescribedBy, setAccessibleDescription, setAccessibleDescription, setAccessibleIgnored, setAccessibleLabelledBy, setAccessibleName, setAccessibleName, setAccessibleRole, setControlSize, setCursor, setEnabled, setFocusable, setId, setImageCursor, setInheritanceHost, setLayoutDirection, setLocale, setMaxHeight, setMaxWidth, setMinHeight, setMinWidth, setPresentedElsewhere, setTooltip, setTooltip, setVisible, shapeText, textRuler, tooltip, tooltipSource, width, x, y
-
Constructor Details
-
Slider
public Slider(float min, float max) A slider over[min, max], starting atmin.
-
-
Method Details
-
setValue
Sets the value (clamped + snapped to the step). AnnouncesVALUE/CODEwhen it moved, and reaches no handler: a caller's write is not the user operating the slider.- Throws:
IllegalArgumentException- ifnewValueis 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
Sets the discrete increment (0= continuous). Re-snaps the current value, and says so: when the re-snap moves it,VALUE/ADJUSTMENTis announced first, and then the step's ownRANGE/CODElast, 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
0for a continuous slider
-
onChange
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 forsetValue(float). To hear every move whatever caused it, watch the slider instead.- Parameters:
listener- the handler, ornullto clear the slot- Returns:
- this slider
- Throws:
IllegalStateException- if a handler is already registered
-
onCommit
Called with the value the user settled on: once when a drag ends, and after each change made with the keyboard.onChangefires 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
onChangedoes 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, ornullto clear the slot- Returns:
- this slider
- Throws:
IllegalStateException- if a handler is already registered
-
handleUserChange
The handler half of the channel: the base class calls this after every watcher, for aUSERorigin only, and the slot is read here so the ordering stays in the base.- Overrides:
handleUserChangein classWidget<Slider>- Parameters:
aspect- what changed; the origin isUSERor this is not called
-
onMeasure
Description copied from class:WidgetReports the size this widget wants withinconstraints. 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
ControlSizeand theLayoutDirectiononce each here and thread them down; never read either in a constructor. The locale needs no threading: it is in scope, andI18n.locale()answers it wherever text is resolved. -
onPaint
Description copied from class:WidgetWidget's own background/content, in local coordinates. -
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<Slider>
-
onKeyEvent
Description copied from class:WidgetKey events (focused widget first, then ancestors).- Overrides:
onKeyEventin classWidget<Slider>
-
onAccessibility
What this slider is to an assistive technology: oneAccessible.Role.SLIDERnode, horizontal because the class has no other axis, carrying a writable value facet read from the model and offeringAccessible.Action.INCREMENTandAccessible.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
setAccessibleNamewins 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
valuefield, already clamped and snapped, with the bounds and thestepfield as set:0when 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, soSET_VALUEis 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:
onAccessibilityin classWidget<Slider>- Parameters:
a- the node being described
-
onAccessibilityAction
Performs a change an assistive technology asked for exactly as a key press does: through the value seam atUSER, soonChangefires 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 throughsetValue(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.INCREMENTandDECREMENTmove 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_VALUEtakes anAccessible.Argument.OfValueand 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:
onAccessibilityActionin classWidget<Slider>- Parameters:
action- what was askedarg- the value forSET_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:WidgetCalled when this widget takes keyboard focus. Default: nothing.- Overrides:
onFocusGainedin classWidget<Slider>
-
onFocusLost
protected void onFocusLost()Description copied from class:WidgetCalled when this widget loses keyboard focus. Default: nothing.- Overrides:
onFocusLostin classWidget<Slider>
-