Keyboard shortcuts

Press ← or → to navigate between chapters

Press S or / to search in the book

Press ? to show this help

Press Esc to hide this help

Slider

Slider preview

Slider — a draggable value selector bound to a Signal<f32>.

Public types

KindName
constSLIDER_PART_BODY — Widget::target_regions part id for the whole press surface — the track plus everything either side of it, which is what a tap or a drag acts on
constSLIDER_PART_THUMB — Widget::target_regions part id for the knob
enumSliderValueTooltipPlacement — Where Slider::value_tooltip's readout sits, set with Slider::value_tooltip_placement
structSlider — A draggable value selector bound to a Signal<f32> in a continuous or discrete range

Public functions

Slider

Detailed description

The widget owns all input handling: pointer drag (click-to-jump and thumb-drag), the keyboard, and the Increment / Decrement / SetValue accessibility actions. All visual chrome is delegated to a SliderStyle implementation; the IntUI default ships out of the box and is also the theme-wide slot override target (theme.style_slots.slider).

Keyboard

  • ArrowRight / ArrowUp and ArrowLeft / ArrowDown — one step, defaulting to 1 % of the range.
  • PageUp / PageDown — one page_step, defaulting to ten times the step and so to 10 % of the range. That is QAbstractSlider::pageStep, GtkScale's page increment, and what <input type=range> gives in both WebKit and Blink.
  • Home / End — the minimum and the maximum.
  • A chord holding Ctrl, Alt or Super is not the slider's and falls through to the application; Shift does not change the step.

The chord table is shared with every other bounded-scalar control; see docs/range-keyboard.md.

Accessibility

Exposes Role::Slider with numeric value, min, max, step, the page distance as numeric_value_jump, and orientation. Screen readers announce the current value on every change. SetValue accepts a number or a numeric string and snaps it to step, so an assistive technology's write lands on the same grid a drag does — AT-SPI's Value.SetCurrentValue is how Orca sets a slider, and macOS gates setAccessibilityValue: settability on the action being advertised. The focus ring follows the :focus-visible heuristic — visible after keyboard interaction, invisible after a pointer tap.

#![allow(unused)]
fn main() {
use teksilo_core::signal::Signal;
use teksilo_widgets::Slider;
let volume = Signal::new(0.5_f32);
let _w = Slider::new(volume, 0.0, 1.0).step(0.05);
}

Value readout and reset

value_tooltip shows the value as text beside the thumb — "-3.0 dB" on an equalizer band — while the pointer is over the slider, while a drag is moving it, and while it has keyboard focus. It is up at once rather than after a hover delay, follows the thumb wherever the slider goes, and changes as the value does. It takes no input: a press or a hover on it belongs to what is under it, and Escape takes it down without keeping the key from the field the user pressed it for. The same text becomes the slider's accessible value, beside the number, so a reader that speaks a value's text says "-3.0 dB" rather than "-3".

value_tooltip_placement moves the readout: Above or Below the thumb, or FollowPointer, which rides the pointer rather than the thumb while a drag moves the value — useful on a stepped slider, whose thumb snaps away from the hand. The default, Auto, is the placement described above. value_tooltip_style paints this slider's readout with a TooltipStyle of its own instead of the theme's tooltip chrome.

default_value adds a way back: a double-click on the slider, or the "Reset to default" accessibility action, puts the value back to the default, as a user change, so on_change reports it.

#![allow(unused)]
fn main() {
use teksilo_core::signal::Signal;
use teksilo_i18n::lit;
use teksilo_widgets::{Slider, SliderValueTooltipPlacement};
let gain = Signal::new(0.0_f32);
let _band = Slider::new(gain, -12.0, 12.0)
    .step(0.5)
    .label(lit!("Low shelf"))
    .value_tooltip(|db| lit!(format!("{db:+.1} dB")))
    .value_tooltip_placement(SliderValueTooltipPlacement::Below)
    .default_value(0.0);
}

Clicks, presses and buttons

A click on the track jumps the value to it, on release; a drag moves it from the moment the pointer has travelled far enough to be a drag. Both answer to the primary button only. An application that wants a click of its own on the slider — a secondary-click reset, say — attaches its own on_tap and widens that handler's mask; the slider's track click stays on the primary button, and the secondary click is the application's alone:

#![allow(unused)]
fn main() {
use teksilo_core::event::ButtonMask;
use teksilo_core::signal::Signal;
use teksilo_core::widget_builder::WidgetBuilder;
use teksilo_widgets::Slider;
let gain = Signal::new(0.0_f32);
let reset = gain.clone();
let _w = Slider::new(gain, -12.0, 12.0)
    .on_tap(move |_, _| reset.set(0.0))
    .accept_tap_buttons(ButtonMask::SECONDARY);
}

jump_on_press moves the value as soon as a mouse button goes down rather than on release, and a drag carries on from there. A finger's or a pen's press still writes nothing until it is a tap or a drag: such a press may yet become a scroll.

Touch and pen

A slider is a continuous manipulator: the value it produces is the press position, so a finger that lands on it adjusts it — even inside a scrolling form, and from the first movement rather than after a long-press timer. Two separate things deliver that. The press capture the drag takes makes the slider the innermost member of the pointer's sequence, which is what stops an enclosing scroller winning the gesture. touch_action(NONE) is the declaration on top: it forbids every default touch behaviour on the hit path, which in practice means a two-contact pinch started on the slider never reaches the surface under it. docs/touch-and-pen.md §7.3.

teksilo_core::widget::Widget::target_regions reports what the style painted inside the slider's one node: the whole node as the press surface, and the knob as the grab affordance, sized through the resolved style's density-aware thumb_diameter_for. Nothing else in the tree can see the knob — it is drawn on the same canvas as the track — so this is the only way a conformance audit or a coarse-press router learns it is there.

Right-to-left. A horizontal slider's minimum sits at the leading edge, which is the right-hand one in an RTL UI, so both the painted fill and the position→value map mirror. They were previously mirrored in neither, so an RTL slider's knob moved away from the finger dragging it.

Density

The picture above is the widget at TargetDensity::Compact, the mouse-and-keyboard ladder. Below is the same subject on the same canvas with only the ladder changed, so what moves is the density and nothing else — where the subject no longer fits, that is what the denser targets cost it at that size. See docs/density-and-targets.md.

Touch

Slider at Touch density

API reference

📖 Full rustdoc API for this module

pub const SLIDER_PART_BODY

Import: use teksilo::widgets::slider::SLIDER_PART_BODY;

Widget::target_regions part id for the whole press surface — the track plus everything either side of it, which is what a tap or a drag acts on.

#![allow(unused)]
fn main() {
pub const SLIDER_PART_BODY: u16 = 0;
}

pub const SLIDER_PART_THUMB

Import: use teksilo::widgets::slider::SLIDER_PART_THUMB;

Widget::target_regions part id for the knob.

#![allow(unused)]
fn main() {
pub const SLIDER_PART_THUMB: u16 = 1;
}

pub enum SliderValueTooltipPlacement

Import: use teksilo::widgets::SliderValueTooltipPlacement;

Where Slider::value_tooltip's readout sits, set with Slider::value_tooltip_placement.

Every placement keeps the readout clear of the strip the thumb occupies across the whole slider, so it covers neither the knob nor what the style draws beside the track, and flips to the other side when the window has no room on the one asked for.

#![allow(unused)]
fn main() {
pub enum SliderValueTooltipPlacement { /* variants */ }
}

Variants

  • Auto — Above a horizontal slider's thumb, or below it near the top of the window; beside a vertical slider's thumb, on the inline-start side when there is room. The default.
  • Above — Above the thumb, centred on it, below when the window has no room above. On a vertical slider this sits over the track above the thumb.
  • Below — Below the thumb, centred on it, above when the window has no room below. On a vertical slider this sits over the track below the thumb.
  • FollowPointer — Where Auto puts it, but at the pointer rather than the thumb while a drag is moving the value: along the slider's axis it tracks the pointer, past either end of the track too, where the thumb has stopped. Hovered or focused from the keyboard, it sits by the thumb as Auto does.

pub struct Slider

Import: use teksilo::widgets::Slider;

A draggable value selector bound to a Signal<f32> in a continuous or discrete range. Visual chrome is fully delegated to a SliderStyle implementation.

#![allow(unused)]
fn main() {
pub struct Slider { /* fields */ }
}

Methods

pub fn new(value: Signal<f32>, min: f32, max: f32) -> Self

Create a horizontal slider bound to value with the given inclusive range. Use orientation to switch to vertical.

pub fn on_change( mut self, f: impl Fn(f32, &mut teksilo_core::widget::EventContext) + 'static, ) -> Self

Run f for every value this control produces under the user's hand, with an EventContext, so it can do what a bare Signal write cannot (ctx.send_intent(...), opening a window). Fires for a track click, for each step of a drag, for the arrows, for an assistive technology's Increment / Decrement / SetValue, and for a reset to the default_value.

A drag fires this repeatedly — once per value it actually produces, not once per pointer sample, since a write that changes nothing reports nothing. It is still the wrong place for work that should happen once per interaction: persisting to disk, a network call, an undo entry. There is no commit-on-release callback yet; observe the signal and do that work when the value settles.

Does not fire for programmatic writes to the bound signal — there is no event in flight to carry. Observe the signal for that.

pub fn step(mut self, step: f32) -> Self

Set the discrete step size for keyboard arrows and accessibility Increment/Decrement actions. When unset, defaults to 1 % of the range.

pub fn page_step(mut self, page_step: f32) -> Self

Set the step size for PageUp / PageDown. When unset, ten times the effective step — so with the default step of 1 % of the range a page is 10 % of it, which is what QAbstractSlider::pageStep, GtkScale's page increment and <input type=range> in both WebKit and Blink all give, and the same 10 x rule SpinBox::page_step uses.

pub fn orientation(mut self, orientation: Orientation) -> Self

Set the slider orientation (Horizontal by default). Vertical sliders map Up/Down arrow keys to increase/decrease.

pub fn enabled(mut self, enabled: impl Into<Prop<bool>>) -> Self (default: true)

Set the enabled state, statically or reactively. Forwarded to the arena at build time via ctx.enabled_when(slider_id, self.enabled.clone()).

pub fn variant(mut self, variant: SliderVariant) -> Self

Pick a Tier-1 design-language variant (SliderVariant::Continuous / Discrete / Range). The active SliderStyle decides what to do with the hint — IntUI's default impl paints ticks for Discrete and ignores Range (the widget itself doesn't yet wire dual-thumb behaviour).

pub fn tick_count(mut self, count: u32) -> Self

Configure the tick count for a Discrete slider. The IntUI default paints n evenly spaced tick marks above the track (or to the leading side for vertical orientation).

pub fn style(mut self, style: impl SliderStyle) -> Self

Override the active SliderStyle for this widget instance only.

pub fn label(mut self, label: impl Into<LocalizedString>) -> Self

Set an accessible name for the slider, announced by screen readers. ARIA requires sliders to have a label; when none is set here the caller is responsible for labelling via a wrapping element.

pub fn tooltip(mut self, text: impl Into<LocalizedString>) -> Self

Attach a plain single-line tooltip shown after a hover delay. Mutually exclusive with rich_tooltip, rich_tooltip_content, and composite_tooltip — the last setter wins and clears the others. Independent of value_tooltip, which neither replaces nor is replaced by any of them.

pub fn rich_tooltip(mut self, key: impl Into<String>) -> Self

Attach a rich tooltip driven by a registry key. The registry entry supplies title, body markup, optional shortcut chip and cascade links. Mutually exclusive with the other tooltip setters.

pub fn rich_tooltip_content(mut self, content: crate::tooltip::TooltipContent) -> Self

Attach a rich tooltip from an inline TooltipContent value, bypassing the registry lookup. Mutually exclusive with the other tooltip setters.

pub fn composite_tooltip(mut self, content: impl Widget + 'static) -> Self

Attach a composite tooltip whose body is an arbitrary widget tree. Uses the heavier tooltip_delay_heavy delay. Mutually exclusive with the other tooltip setters.

pub fn value_tooltip(mut self, format: impl Fn(f32) -> LocalizedString + 'static) -> Self

Show the value as text by the thumb, formatted by format, while the slider is hovered, dragged, or focused from the keyboard.

Unlike a tooltip, the readout is up at once, with no hover delay; a press does not close it, so it stays up through a drag; and it moves with the thumb and changes as the value does, without a rebuild. By default it sits above a horizontal slider's thumb, or below it near the top of the window, and beside a vertical slider, on the inline-start side when there is room, so it covers neither the thumb nor the track; value_tooltip_placement picks another side. It is re-placed on every layout pass from where the slider is then, so it follows a value the application writes, a scroll that moves the slider and a resize that stretches it. Its chrome is the theme's tooltip chrome unless value_tooltip_style gives it another.

It takes no input of its own. A press or a hover on it reaches what is under it, and a press there closes a popover it is outside of. Escape hides it until the next hover, drag or key, and goes on to whatever else wanted the key, the focused field included, as a plain tooltip's Escape does. Focus leaving the slider takes it down only when nothing else holds it up: the pointer still on the slider keeps it.

The text is also the slider's accessible value, published beside the number: UI Automation and macOS carry it, so a reader there says "-3.0 dB" rather than "-3". AT-SPI carries the number alone, so Orca keeps reading the number.

format receives the value the bound signal holds, on every change to it. It returns a LocalizedString, so a tr!(..) message re-resolves on a locale change and lit!(..) serves text that is not translated.

A tooltip, rich or composite tooltip set beside it keeps its hover delay, its place under the slider and the accessible description it gives, and both show; to name the control in the readout instead, say so in format.

pub fn value_tooltip_placement(mut self, placement: SliderValueTooltipPlacement) -> Self

Choose where the value_tooltip readout sits: above or below the thumb, or at the pointer while a drag moves the value. SliderValueTooltipPlacement::Auto by default, which is where the readout has always gone. Has no effect without a value_tooltip.

pub fn value_tooltip_style(mut self, style: impl TooltipStyle) -> Self

Paint the value_tooltip readout with style rather than the active theme's tooltip chrome (theme.style_slots.tooltip, else the built-in recipe). Only this slider's readout changes; its plain, rich or composite tooltip keeps the theme's. Has no effect without a value_tooltip.

pub fn jump_on_press(mut self, on: bool) -> Self

Move the value to the pointer as soon as a mouse button goes down on the slider, rather than when the click is released or a drag starts. Off by default.

With it off, a press writes nothing: the value moves when the pointer has travelled far enough to start a drag, or to the click's position when it is released without one. With it on, a primary press of a mouse moves the value to the pointer at once, a drag that follows carries on from there without a jump, and the release of a click adds nothing. on_change reports the press's value as a user change.

Touch and pen keep the default whatever this says: a contact landing on a slider may be the start of a scroll or of something an ancestor claims, so it writes nothing until it is a drag or a tap. The second press of a double-click does not jump either when a default_value is set, since that click resets. The keyboard and the accessibility actions are unaffected.

pub fn default_value(mut self, value: f32) -> Self

Let the user put the value back to value: with a double-click on the slider, or with the "Reset to default" accessibility action, which this also advertises.

The reset is a user change, written through the same path as a drag or an arrow key, so on_change reports it unless the value was already there. It is clamped to the range and not snapped to the step: a default between two steps lands where it was asked to. A double-click's first click jumps the value to where it landed, as every click on the track does, so on_change reports that value and then the default; further clicks in the same burst leave the default where it is. A disabled slider ignores the double-click, and neither offers nor takes the action.

An application's own .access_custom_action(..) on the slider is offered beside the reset, not instead of it.