Class RangeSlider
[min, max]: a lower value and
an upper one over the same track, the span between them filled. Built as Slider is: the
same track, knobs, steps, hover and focus ring, and the same direction rules reading right to
left.
RangeSlider price = new RangeSlider(0, 1000).setStep(10).setMinimumGap(50).setValues(200, 600);
price.onChange((low, high) -> filter.setPrice(low, high));
A thumb stops at the other, or at the minimum gap the
application sets: the lower can never pass above upper - gap, nor the upper below
lower + gap. A press on the track moves the nearer thumb there and drags it; with the two
on one spot, the one the pointer goes towards.
Each thumb is a control of its own for the keyboard and for an assistive technology: two Tab stops, the lower first, each moved by the arrows, Page Up and Page Down, Home and End within what the other leaves it, and each a slider node named "Minimum" or "Maximum" in the interface's language, whose bounds are the range it may reach now.
onChange and onCommit are the application's response to the user;
setValues(float, float) reaches neither. Every change, whatever moved it, reaches a
watcher as VALUE, and the end of a drag or a key press as
COMMITTED.
-
Nested Class Summary
Nested ClassesModifier and TypeClassDescriptionstatic interfaceWhat hears the range the user set. -
Constructor Summary
ConstructorsConstructorDescriptionRangeSlider(float min, float max) A slider over[min, max], its range the whole of it. -
Method Summary
Modifier and TypeMethodDescriptionprotected voidhandleUserChange(Change.Aspect aspect) Runs this widget's application handler for a change it has just announced.floatlower()floatmax()floatmin()floatprotected voidSays what this widget is, for an assistive technology.onChange(@Nullable RangeSlider.RangeListener listener) The application's response to the user moving either thumb, with both values: on a drag, a key or an assistive technology's step, and never forsetValues(float, float).onCommit(@Nullable RangeSlider.RangeListener listener) Called with the range the user settled on: once when a drag ends, and after each key press.protected voidonLayout()Containers position children here (measure +Widget.layoutBox(float, float, float, float)per child).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.protected floatHow far beyond this widget's bounds its painting may reach, in logical points: its own painting and any descendant no ancestor clips (a drop shadow, a glow, a child positioned outside the parent's box).protected booleanWhether whatWidget.onPaint(limn.graphics.Canvas)draws is material rather than meaning: a wash, a blur, a shadow, a rule.setMinimumGap(float newGap) Sets the least distance between the two values, 0 unless set: a range of at least a day, a price band of at least ten.setStep(float newStep) Sets the discrete increment (0= continuous) and snaps both values onto it.setValues(float newLower, float newUpper) Sets both values, clamped to the bounds, snapped to the step and held the minimum gap apart.floatstep()floatupper()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, onAccessibilityAction, onAccessibilityChild, onAccessibilityChildAction, onAccessibilityChildIdentity, onAttached, onCanEdit, onCharTyped, onDetached, onDragExit, onDragOver, onDragStart, onDrop, onEdit, onFocusGained, onFocusLost, onHitTest, onKeyEvent, onPaintOverlay, onPreedit, onSyntheticAction, overlayPassesPointer, paintChildren, 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
-
RangeSlider
public RangeSlider(float min, float max) A slider over[min, max], its range the whole of it.- Throws:
IllegalArgumentException- ifmaxis not abovemin
-
-
Method Details
-
setValues
Sets both values, clamped to the bounds, snapped to the step and held the minimum gap apart. Values closer than the gap move apart, the upper up first and the lower down when the upper meets the maximum, each to the next step past the gap, so a value is always one the steps reach. AnnouncesVALUE/CODEwhen either moved and reaches no handler.- Throws:
IllegalArgumentException- ifnewLoweris abovenewUpper, or either is not a number
-
lower
public float lower()- Returns:
- the lower value
-
upper
public float upper()- Returns:
- the upper value
-
min
public float min()- Returns:
- the lower bound, inclusive
-
max
public float max()- Returns:
- the upper bound, inclusive
-
setStep
Sets the discrete increment (0= continuous) and snaps both values onto it.- Throws:
IllegalArgumentException- if it is negative or not a number
-
step
public float step()- Returns:
- the grid the values are snapped onto, or
0for a continuous slider
-
setMinimumGap
Sets the least distance between the two values, 0 unless set: a range of at least a day, a price band of at least ten. The values are moved apart if they are closer.- Throws:
IllegalArgumentException- if it is negative, not a number, or larger than the bounds
-
minimumGap
public float minimumGap()- Returns:
- the least distance between the two values
-
onChange
The application's response to the user moving either thumb, with both values: on a drag, a key or an assistive technology's step, and never forsetValues(float, float).- Parameters:
listener- the handler, ornullto clear the slot- Returns:
- this slider
- Throws:
IllegalStateException- if a handler is already registered
-
onCommit
Called with the range the user settled on: once when a drag ends, and after each key press.- Parameters:
listener- the handler, ornullto clear the slot- Returns:
- this slider
- Throws:
IllegalStateException- if a handler is already registered
-
handleUserChange
Description copied from class:WidgetRuns this widget's application handler for a change it has just announced. Called byWidget.runHandler(limn.scene.Change), and so byWidget.notifyChange(limn.scene.Change), only for aUSERorigin 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 boxselectedIndex(), a text fieldtext(). 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: aSearchFieldthat overrode this for its own aspect and did not chain would silently deleteTextField'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:
handleUserChangein classWidget<RangeSlider>- 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.- Specified by:
onMeasurein classWidget<RangeSlider>
-
onLayout
protected void onLayout()Description copied from class:WidgetContainers position children here (measure +Widget.layoutBox(float, float, float, float)per child).- Overrides:
onLayoutin classWidget<RangeSlider>
-
paintOutset
protected float paintOutset()Description copied from class:WidgetHow far beyond this widget's bounds its painting may reach, in logical points: its own painting and any descendant no ancestor clips (a drop shadow, a glow, a child positioned outside the parent's box). Partial rendering uses it both ways:Widget.invalidate()damage grows by it, and a repaint pass skips this subtree only when bounds + outset miss the pass region; painting farther than declared leaves stale pixels.The work is divided by who knows the number. A widget declares what it paints itself outside its box:
Label's icon overhang, the focus ringButton,CheckboxandRadioButtondraw outside theirs. A widget that lays out children and does not clip them declares how far they may paint past it, because a ring on a child at its edge hangs past it too, and a pass landing only in that band would otherwise skip it and never reach the child. Every layout the toolkit ships does that by default with the reach the theme gives a focus ring, and takes another number through its publicpaintOverflow, which sets what this hook answers; a layout an application writes inherits both by extendinglimn.scene.layout.Container, and one that extends this class directly overrides this hook itself. A widget that clips its children declares only its own painting, since the clip bounds theirs.Asked of every widget a repaint pass considers, and on every
invalidate(), so an override answers in constant time without allocating: a number held or looked up, never a walk over the children. Default0: everything stays inside the bounds, which is true for most leaves.- Overrides:
paintOutsetin classWidget<RangeSlider>
-
onPaint
Description copied from class:WidgetWidget's own background/content, in local coordinates.- Overrides:
onPaintin classWidget<RangeSlider>
-
onMouseEvent
Description copied from class:WidgetMouse events (bubbling); callevent.consume()when handled.- Overrides:
onMouseEventin classWidget<RangeSlider>
-
onAccessibility
Description copied from class:WidgetSays what this widget is, for an assistive technology.Called on the UI thread inside the publish step, with this widget's own locale in scope, and never from a paint. A widget that overrides nothing still gets a node with its bounds, its enabled, focusable, visible and showing states, its language, its children in tree order, the focus and scroll-into-view verbs when it is focusable, and its tooltip as a name when nothing else supplied one. What it does not get is a role: a widget that declares no role, no name, no description, no state, no verb and no facet is scaffolding, and the tree removes it and hoists its children in its place, so that a screen reader hears the controls rather than the boxes.
Nothing here may allocate. The builder's setters take a facet's fields rather than a facet, and a name is handed over as the localizable string this widget holds rather than as a resolved one, so that a frame which damaged this widget and changed nothing about it costs a comparison and no memory at all.
Accessibilitystates the two rules and what breaks when they are ignored.- Overrides:
onAccessibilityin classWidget<RangeSlider>- Parameters:
a- the node being described; write into it, and never keep it
-
paintsDecoration
protected boolean paintsDecoration()Description copied from class:WidgetWhether whatWidget.onPaint(limn.graphics.Canvas)draws is material rather than meaning: a wash, a blur, a shadow, a rule. Defaultfalse.A widget that draws its own content and declares nothing is removed from the accessible tree by the transparency rule, and the toolkit says so once per class — because the usual cause is a gauge or a sparkline nobody named, and the interface it drew is then simply absent with nothing said. That guard asks "does this class draw?", which reflection can answer; it cannot ask whether the drawing means anything, and this widget is the only thing that can. Overriding this to
trueis that answer, and the removal then happens in silence.It is not
Widget.setAccessibleIgnored(boolean), and the difference is the subtree. Ignored takes this widget's children out of the tree with it, which on a panel whose whole purpose is to sit behind controls hides every control it wraps. Saying the painting is decoration changes nothing about the tree at all: the node is removed exactly as it would have been, the children hoist into its place, and only the warning goes away.Answer it
trueonly for a class whose drawing a reader loses nothing by never hearing about. A widget that paints information is named or given a role instead; there is no third answer that leaves the picture out and keeps the user informed.- Overrides:
paintsDecorationin classWidget<RangeSlider>- Returns:
- whether this widget's own drawing carries no information
-