class

Tryst::ValueSlider

Inherits Tryst::OwnerDrawnWidget < Reference < Object

A single-thumb value slider: rounded track with a filled portion, an antialiased thumb, optional tick marks with min/max labels, and a value bubble that tracks the thumb while dragging or keyboard- adjusting. Built on OwnerDrawnWidget - construct it, #pack/#grid it, #on_change it. Nothing about canvas, Surface, or ThorVG is part of its own public surface; see OwnerDrawnWidget's own doc comment for why #canvas itself is protected, not public.

Rendering split

Track, fill, ticks, and thumb are all drawn each #redraw through a tryst-vector Surface, then blitted in as one Photo (OwnerDrawnWidget# blit) - real antialiasing, real gradients if a future version wants them. The bubble's CHROME (rounded body + pointer arrow) draws the same way, but its TEXT is a real Tk label floated over the canvas with place - tryst-vector doesn't expose text yet (see its own README), and a native label gets correct font metrics/DPI/RTL for free where hand-measuring glyph widths wouldn't. font: defaults to Tk's own TkTextFont (whatever system UI font Tk resolves per platform) rather than a hardcoded face, and is a real Tk font spec/name so any of Tk's own named fonts or a literal "family size" string works the same way it would on any other widget. The label's own background is set to match the drawn chrome exactly, and the chrome is sized a few pixels larger than the label on every side, so the label's own square corners land inside the chrome's straight edges and only the chrome's rounded corners are ever visible past it - the label itself can't be rounded, so the surrounding chrome is drawn larger specifically to hide that fact.

Both the chrome and the label are a FIXED size, not shrink-to-fit around the current value's own text - see #initialize's own comment on why sizing them per-frame made the whole bubble visibly wobble while dragging.

Value flow

#value= sets a value programmatically and does NOT fire #on_change - only a user-driven change (drag, click-to-position, keyboard) does, the same "user action vs Crystal-driven set" split every other stateful widget in this codebase draws (see Tryst::UI::Var#on_change for the DSL-layer version of the same idea). There is currently no bridge from this App-layer widget into that DSL-layer Var/bind: machinery - a caller wanting two-way sync with one wires slider.on_change { |v| var.value = v } and var.on_change { |v| slider.value = v } itself.

Constants

BUBBLE_ARROW_H = 5.0
BUBBLE_ARROW_W = 10.0
BUBBLE_GAP = 10.0
BUBBLE_MARGIN = 4.0
FOCUS_RING = 5.0
HIDE_DELAY_MS = 3000
LABEL_GAP = 6.0
MARGIN = 14.0

Layout constants, all in logical pixels (always rendered at Surface's default scale: 1.0 - see #ensure_surface's own doc comment on why). Not exposed as options: a slider that wants a genuinely different look is a different widget, not a config surface on this one.

SHOW_MS = 140
THUMB_RADIUS = 9.0
TICK_GAP = 6.0
TICK_HEIGHT = 6.0
TICK_WIDTH = 2.0
TRACK_HEIGHT = 8.0

Constructors

new(app : App, min : Float64 = 0.0, max : Float64 = 100.0, step : Float64 = 1.0, value : Float64 | Nil = nil, ticks : Int32 = 0, format : Proc(Float64, String) = ->(v : Float64) do v.round.to_i.to_s end, accent : String | Nil = nil, bubble_width : Int32 | Nil = nil, font : String = "TkTextFont", width : Int32 = 220, height : Int32 = 104, parent = nil)
Source

Class methods

snap(raw : Float64, min : Float64, max : Float64, step : Float64) : Float64

Snaps raw to the nearest step from min, then clamps to [min, max] - the pure math every value change (drag, click, keyboard, #value=) funnels through. A class method, not an instance one, so it's testable with no App/Tk involved at all.

Source

Instance methods

destroy

Releases the underlying canvas (and its Photo, if any) now, rather than waiting for a collection - same contract as Photo#delete. Bind-callback cleanup for #canvas's own path happens for free (see App's setup_destroy_cleanup, installed unconditionally for every widget); only this class's own extra state (running tweens) needs explicit teardown here.

Source
on_change

Fires on every user-driven change (drag, click-to-position, arrow/ Home/End/PageUp/PageDown keys) - never for a programmatic #value=.

Source
redraw

Subclasses draw here - called after every resize and every state change (#hover?/#pressed?/#focused?/#disabled? all changing). Given no arguments deliberately: #canvas's own current width/height (via #canvas.width/#canvas.height, i.e. real winfo queries) are always the authority, not a value that could go stale between when a resize fired and when this actually runs.

Source
step
Source
ticks
Source
value

The current value. See this class's own doc comment on why setting this does NOT fire #on_change.

Source
value=(new_value : Float64) : Float64
Source