springbutton

package
v0.1.3 Latest Latest
Warning

This package is not in the latest version of its module.

Go to latest
Published: Aug 7, 2026 License: MIT Imports: 12 Imported by: 0

Documentation

Overview

Package springbutton ships the spring-physics variant of prism/button: the same button, with a press that scales down and springs back.

It is a sibling of github.com/vibrantgio/prism/button.Button, not a decorator over it. Pulse never animates prism globally — a caller picks the animated component by name, so the call site says what it does and the dependency runs pulse → prism and never back. SpringButton owns its own widget.Clickable and renders through github.com/vibrantgio/prism/button.Render, the pure renderer, then wraps the output in an op.Affine scale driven by a github.com/vibrantgio/pulse/spring.Spring. The underlying button's visual contract — hover, focus, press, disabled colours; 44 dp minimum hit target; semantic ops — is preserved.

// Static prism.Button:
w, _ := button.Button(theme, button.Props{Label: "Save", OnClick: save}).First()

// Spring-physics variant:
w, _ := springbutton.SpringButton(theme,
    button.Props{Label: "Save", OnClick: save},
    springbutton.Options{}, // zero-valued: package defaults
).First()

Physics

On press, the spring retargets to Options.PressScale; on release, back to 1.0. The free particle's position is read every frame as a scale factor applied around the button's centre. The default parameters (Stiffness 300, Damping 22, Mass 1) are underdamped, so the release overshoots slightly and comes back — the "pop". Measured against this package's own settle tolerance, a press-and-release settles 25 frames after the release.

While the spring is in flight the widget schedules its own redraws via op.InvalidateCmd; once it settles, no further frames are requested, so a SpringButton at rest costs the same per frame as a static prism button.

Frames, not seconds

The spring is ticked at a hard-coded inverse step of 60 rather than at the window's real frame rate, so those 25 frames are 25 frames whatever the display does. On a 120 Hz screen the press animation takes half the wall-clock time it takes on a 60 Hz one, and under a stuttering host it stretches out to match. Nothing here reads the frame rate, and there is no option to supply it; a frame-rate-aware step is a later refinement, shared with github.com/vibrantgio/pulse/conductor.

Fonts

The label is shaped with the theme's cached shaper (Typography.Shaper(), ADR-003: the theme owns the typeface) in the LabelLarge role. That shaper is built once for the process and shared by every component reading the same typography: spectrum's cache lives behind the Typography value, so it survives the copy the map function below makes of it (spectrum F5.1). It is not safe to use from two goroutines — Gio lays the widget forest out on the one goroutine that runs the event loop, which is what makes sharing it correct. Props.Shaper is an explicit per-instance override only; leave it nil in normal use. Since prism v0.2.0 the role's whole text style — typeface, weight, size and line height — reaches github.com/vibrantgio/prism/button.Render, as does the theme's Density, so a spring button is glyph- and pixel-identical to the static one at scale 1.

State scope

The clickable and the spring are allocated inside the rx.Defer closure, so they persist across theme and disabled emissions and across frames for the lifetime of one subscription. A new subscription resets the physics — a button that is subscribed twice has two independent springs, and one that is resubscribed mid-press restarts at scale 1.

Index

Constants

View Source
const (
	DefaultStiffness  = 300.0
	DefaultDamping    = 22.0
	DefaultMass       = 1.0
	DefaultPressScale = 0.92
)

Defaults tuned for a visible-but-snappy button press. The amplitude of the scale change is small (8 %), so the spring must be stiff enough to traverse most of it within a ~150 ms press window at a 60 Hz frame rate. Stiffness 300 with Damping 22 (zeta ≈ 0.635) gives a small overshoot for "pop" feel. Measured against settleTolerance, each leg settles 25 frames after the pointer event — ~415 ms at 60 Hz, not the ~250 ms this was tuned for.

Variables

This section is empty.

Functions

func SpringButton

func SpringButton(
	th rx.Observable[theme.Theme],
	props button.Props,
	opts Options,
) rx.Observable[layout.Widget]

SpringButton returns an rx.Observable[layout.Widget] that renders the prism.Button visual with a spring-driven scale on press/release. All button.Props fields are honoured (label, description, disabled observable, OnClick, Message, custom Shaper) — the FRP and MVU integration paths from prism/button continue to work unchanged.

Types

type Options

type Options struct {
	// Stiffness, Damping, Mass are the same spring parameters
	// documented in [pulse/spring.Options]. Zero values are replaced
	// with [DefaultStiffness], [DefaultDamping], [DefaultMass].
	Stiffness float64
	Damping   float64
	Mass      float64

	// PressScale is the scale the button shrinks to while pressed.
	// 1.0 means no shrink (the spring becomes a no-op visual);
	// values below 1.0 produce a press-down effect. Zero is replaced
	// with [DefaultPressScale].
	PressScale float64
}

Options configures the spring physics layered on top of prism/button. Zero-valued fields use package defaults so an empty Options{} produces the canonical SpringButton feel.

Jump to

Keyboard shortcuts

? : This menu
/ : Search site
f or F : Jump to
y or Y : Canonical URL