class

Tryst::Spinner

Inherits Tryst::OwnerDrawnWidget < Reference < Object

An activity ring for tryst: indeterminate (a continuously rotating, breathing arc - the "something's happening, no ETA" case) or determinate (a fixed arc from 0 to a 0.0-1.0 value, with an optional centered percentage). One shard, one widget - determinate mode is just what happens when #value is set to something other than nil, not a separate widget (see #value='s own doc comment). Built on OwnerDrawnWidget and rendered through tryst-vector: an antialiased stroke with round caps is the whole visual point, which is exactly what Tk's own indeterminate ttk::progressbar has neither of.

spinner = Tryst::Spinner.new(app) # indeterminate
spinner.pack

sync = Tryst::Spinner.new(app, value: 0.0, show_value: true) # determinate
sync.pack
sync.value = 0.65

Nothing about canvas, Surface, or ThorVG is part of this class's own public surface - same boundary OwnerDrawnWidget itself draws (see its own doc comment on why #canvas is protected).

Constants

ANIMATION_TICK_MS = 33

~30fps, not 60 - plenty smooth for a rotating/breathing arc (unlike drag interactions elsewhere in this codebase, nothing about this animation needs to track input latency), and it runs for as long as the spinner is on screen rather than for one bounded interaction - see the README's own CPU-at-idle-spin cost for why that tradeoff matters here specifically.

EDGE_MARGIN = 1.0

Radial margin between the stroke's own outer edge and the canvas's own bounds - without it, a stroke sized right up to the edge gets AA-clipped by the buffer's own bounds the same way ValueSlider's bubble chrome once was (see that shard's own #initialize comment on the exact failure mode this avoids).

MAX_SWEEP_DEG = 300.0
MIN_SWEEP_DEG = 40.0
ROTATION_PERIOD_MS = 1500.0

Indeterminate motion: the arc's leading edge completes one full turn every ROTATION_PERIOD_MS (continuous, unbounded), while its own length breathes between MIN/MAX_SWEEP_DEG once every SWEEP_PERIOD_MS - two independent cycles is what keeps it reading as "alive" rather than a wedge just spinning in place.

SWEEP_PERIOD_MS = 1400.0
THICKNESS_RATIO = 0.12

Default thickness as a fraction of size, when thickness: isn't given explicitly - matches the design mock's own 16/24/48px -> ~2/3/6px progression.

VALUE_TWEEN_MS = 200

Constructors

new(app : App, size : Int32 = 32, thickness : Int32 | Nil = nil, value : Float64 | Nil = nil, accent : String | Nil = nil, show_value : Bool = false, font : String = "TkDefaultFont", parent = nil)

size/thickness in logical pixels. value: nil (the default) starts indeterminate; anything else is clamped to [0.0, 1.0] and starts determinate. accent: nil uses the active ttk theme's own accent color, or a "#rrggbb" hex string to override it (same convention as ValueSlider's own accent:). font: only matters when show_value is true.

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
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
show_value?
Source
value

nil while indeterminate, else the current value in [0.0, 1.0].

Source
value=(new_value : Float64 | Nil) : Float64 | Nil

Setting nil switches to indeterminate (restarting the sweep from its own beginning); setting a Float64 (clamped to [0.0, 1.0]) switches to/stays in determinate mode. A determinate-to-determinate change animates smoothly (matching CircularProgress's own #value=); the very first determinate value, or one arriving right after an indeterminate stretch, jumps straight there - there is no meaningful "previous position" to animate from in either case.

Source