A draggable value slider: a track, a fill up to the current value, a thumb, and an optional label and value readout.
The value lives on the widget. Keys, the mouse and the scroll wheel all move it,
:on_change reports it, and bind: keeps it in step with a key of the app state.
Component tag
Tag :slider, built by Drafter.App as {:slider, opts}:
slider(opts)
slider(value, opts)The two-argument form puts value into opts under :value. :value and
:on_change go through the binding layer, so bind: :some_key reads the value from
that app-state key and writes each new one back.
Options
:value-number/0the slider starts at, clamped into the range and snapped to:step. Default:min.:min-number/0low end of the range. Default0.0.:max-number/0high end of the range. Default1.0.:step-number/0the value moves in. Defaultnil, a hundredth of the range — whole numbers when:minand:maxare both integers. A range whose bounds and step are all integers keeps integer values; any other range works in floats.:bind- app-state key atom for two-way binding of the value. Default: none. Without it a later:valueprop does not reach the mounted widget, which owns whatever the user set it to.:label-String.t/0drawn ahead of the track, ornil. Defaultnil.:show_value-boolean/0, draw the value after the track. Defaulttrue. The readout reserves the width of the widest value in the range, so the track does not move as the value changes.:format-(number() -> String.t())for the readout. Defaultnil, which writes the number with:precisiondecimals.:precision- decimals in the readout. Default: as many as:stepneeds.:orientation-:horizontal | :vertical. Default:horizontal. A vertical slider runs bottom to top, with the label on its first row and the readout on its last.:disabled-boolean/0. Defaultfalse. A disabled slider draws muted and bubbles every key and click.:track_color/:fill_color/:thumb_color-{r, g, b}overrides for the three parts. Defaultnil, which takes them from the theme.:renderer-:text(default) draws characters;:brailledraws the shape throughDrafter.Widget.Slider.Pixel; a graphics protocol atom (:pixel,:kitty,:iterm2,:sixel,:auto) transmits a picture, falling back to braille where the terminal has no protocol. Unset, the mode the app was run with applies;DRAFTER_MODEoverrides both.:class- theme class atom or list of them. Default[].:style- inline style map merged over the theme. Default%{}.
update/2 accepts every option above except :class and :style, which are
mount-only. Through the component tree update_props_from_mount/3 narrows that
further, adding :value only when :bind is set and the bound value differs from
the widget's own.
Key bindings
→,↑- one step up;←,↓- one step downPageUp,PageDown- ten stepsHome,End- the ends of the range
A press or drag anywhere on the track moves the thumb there, and the scroll wheel moves one step.
Widget value
Drafter.get_widget_value/1 returns the number, and Drafter.set_widget_value/2
writes one, clamped and snapped like any other.
Usage
slider(value: 0.5, label: "Gain", on_change: :set_gain)
slider(min: 0, max: 11, step: 1, bind: :volume)
slider(value: 0.546, precision: 3, renderer: :auto)
Summary
Functions
The component tag this widget registers under.
Builds the props map for a {:slider, opts} element.
Tracks :hover and :unhover; every other event bubbles.
Moves the thumb to the pointer while a button is held.
Callback implementation for Drafter.Widget.handle_event/2.
Moves the value by one step for → and ↑, down one for ← and ↓, ten for the
page keys, and to the ends of the range for Home and End.
Moves the thumb to the pointer and ends the drag gesture.
Moves the thumb to the pointer and starts a drag gesture, so later motion keeps tracking even once the pointer leaves the widget.
Moves the value one step per wheel notch.
Whether this slider is drawing a transmitted image rather than characters.
Builds the widget state from props.
Records the rect the layout gave the widget, so a click can be turned into a value.
The number of rows the element asks for: :height, defaulting to 8 for a vertical
slider and 1 for a horizontal one.
Draws the slider into rect, returning exactly rect.height strips.
Callback implementation for Drafter.Widget.unmount/1.
Folds fresh props into state, keeping the current value for any key that is
absent.
Narrows a re-render to the props that may change after mount.
Types
@type rgb() :: {0..255, 0..255, 0..255}
@type t() :: %Drafter.Widget.Slider{ app_module: module() | nil, classes: [atom()], disabled: boolean(), dragging: boolean(), fill_color: rgb() | nil, focused: boolean(), format: (number() -> String.t()) | nil, height: pos_integer() | nil, hovered: boolean(), label: String.t() | nil, max: number(), min: number(), on_change: (number() -> term()) | nil, orientation: :horizontal | :vertical, precision: non_neg_integer() | nil, renderer: atom() | nil, show_value: boolean(), step: number() | nil, style: map(), thumb_color: rgb() | nil, track_color: rgb() | nil, value: number(), width: pos_integer() | nil }
Functions
@spec component_tag() :: :slider
The component tag this widget registers under.
iex> Drafter.Widget.Slider.component_tag()
:slider
@spec from_component_opts( term(), keyword() ) :: Drafter.Widget.props()
Builds the props map for a {:slider, opts} element.
The positional argument is ignored. :value is the bound value for that key, so bind: :key reads it from
opts[:__app_state__], and :on_change is the binding's writer. :class is
normalised into :classes and :__app_module__ into :app_module.
iex> props = Drafter.Widget.Slider.from_component_opts(nil, min: 0, max: 10, step: 1)
iex> {props.value, props.min, props.max, props.step}
{0, 0, 10, 1}
iex> opts = [bind: :gain, __app_state__: %{gain: 0.75}]
iex> Drafter.Widget.Slider.from_component_opts(nil, opts).value
0.75
@spec handle_custom_event(Drafter.Event.t(), t()) :: {:ok, t()} | {:bubble, t()}
Tracks :hover and :unhover; every other event bubbles.
Moves the thumb to the pointer while a button is held.
Callback implementation for Drafter.Widget.handle_event/2.
@spec handle_key(Drafter.Widget.key(), t()) :: {:ok, t()} | {:bubble, t()}
Moves the value by one step for → and ↑, down one for ← and ↓, ten for the
page keys, and to the ends of the range for Home and End.
A key that does not move the value still returns {:ok, state}; every other key,
and every key at all while :disabled, bubbles.
iex> state = Drafter.Widget.Slider.mount(%{value: 0.5, step: 0.1})
iex> {:ok, moved} = Drafter.Widget.Slider.handle_key(:right, state)
iex> moved.value
0.6
iex> state = Drafter.Widget.Slider.mount(%{value: 0.5})
iex> {:ok, moved} = Drafter.Widget.Slider.handle_key(:home, state)
iex> moved.value
0.0
Moves the thumb to the pointer and ends the drag gesture.
Moves the thumb to the pointer and starts a drag gesture, so later motion keeps tracking even once the pointer leaves the widget.
x and y are widget-relative cells.
@spec handle_scroll(Drafter.Widget.scroll_direction(), t()) :: {:ok, t()} | {:bubble, t()}
Moves the value one step per wheel notch.
Whether this slider is drawing a transmitted image rather than characters.
Only a :pixel mode on a terminal with a graphics protocol draws one; a :text or
:braille slider costs nothing on the image path.
iex> Drafter.Widget.Slider.image_active?(Drafter.Widget.Slider.mount(%{renderer: :text}))
false
@spec mount(Drafter.Widget.props()) :: t()
Builds the widget state from props.
The value is clamped into :min..:max and snapped to :step, so a slider can
never mount off its own scale.
iex> state = Drafter.Widget.Slider.mount(%{})
iex> {state.value, state.min, state.max, state.orientation}
{0.0, 0.0, 1.0, :horizontal}
iex> Drafter.Widget.Slider.mount(%{value: 0.37, step: 0.25}).value
0.25
iex> Drafter.Widget.Slider.mount(%{value: 42, min: 0, max: 10, step: 1}).value
10
@spec on_rect_change(Drafter.Widget.rect(), t()) :: t()
Records the rect the layout gave the widget, so a click can be turned into a value.
iex> state = Drafter.Widget.Slider.mount(%{})
iex> sized = Drafter.Widget.Slider.on_rect_change(%{x: 0, y: 0, width: 40, height: 1}, state)
iex> {sized.width, sized.height}
{40, 1}
@spec preferred_height( term(), keyword() ) :: pos_integer()
The number of rows the element asks for: :height, defaulting to 8 for a vertical
slider and 1 for a horizontal one.
iex> Drafter.Widget.Slider.preferred_height(nil, [])
1
iex> Drafter.Widget.Slider.preferred_height(nil, orientation: :vertical)
8
@spec render(t() | Drafter.Widget.props(), Drafter.Widget.rect()) :: [ Drafter.Draw.Strip.t() ]
Draws the slider into rect, returning exactly rect.height strips.
state may be a plain props map, in which case it is passed through mount/1
first. The renderer decides the track: a :text slider draws characters, a
:braille one the braille shape, and a :pixel one leaves the track blank for the
picture the widget server transmits. The label and the readout are characters in
every mode. A rect with no width draws nothing.
Callback implementation for Drafter.Widget.unmount/1.
@spec update(Drafter.Widget.props(), t()) :: t()
Folds fresh props into state, keeping the current value for any key that is
absent.
The value is re-snapped against whichever range the props leave behind, so moving
:min, :max or :step never leaves the thumb off its scale.
iex> state = Drafter.Widget.Slider.mount(%{value: 90, min: 0, max: 100})
iex> Drafter.Widget.Slider.update(%{max: 50}, state).value
50
@spec update_props_from_mount(Drafter.Widget.props(), t() | map(), keyword()) :: Drafter.Widget.props()
Narrows a re-render to the props that may change after mount.
:value is added only when opts carries :bind and the bound value differs from
the widget's own, so an unbound slider keeps whatever the user dragged it to.
:class and :style are dropped, making them mount-only.