Skip to content

v0.7.0

Pre-release
Pre-release

Choose a tag to compare

@kungfusheep kungfusheep released this 20 May 17:24
· 231 commits to master since this release

glyph v0.7.0

v0.7.0 is a cleanup and capability release. It removes a lot of old public surface area, tightens the main component API, and adds several pieces that make larger Glyph apps easier to build: declarative routing, modal route scopes, stronger nested dynamic templates, interactive rich text, better forms, and more reliable screen effects.

Highlights

  • Declarative event routing with On and Key.
  • Modal route scopes for overlays, dialogs, and temporary input layers.
  • Much stronger nested dynamic UI support, especially around ForEach.
  • Rich text can now wrap, update live, and expose jump targets from spans.
  • Screen effects now compose better with live UI state such as focus refs and opacity.
  • Forms and inputs gained more styling and state-control options.
  • The public API has been tidied up, with legacy struct-literal node APIs removed.

Added

Declarative event routing

Added On and Key so key handlers can live in the component tree and follow the same branch/scope lifecycle as the UI they belong to.

VBox(
	On(
		Key("<C-s>", save),
		Key("<Esc>", close),
	),
	Text("settings"),
)

Conditional route scopes

Handlers declared inside conditional UI now activate and deactivate with that branch.

If(&editing).Then(
	VBox(
		On(
			Key("<Enter>", saveEdit),
			Key("<Esc>", cancelEdit),
		),
		Text("editing"),
	),
)

Modal route scopes

Added On.Modal for handlers that should push a modal input scope while their UI is active.

If(&showConfirm).Then(
	Overlay(
		VBox(
			On.Modal(
				Key("<Enter>", confirm),
				Key("<Esc>", func() { showConfirm = false }),
			),
			Text("confirm deploy?"),
		),
	),
)

Rich span jump targets

Rich text spans can now register jump targets directly, including inside scroll views and repeated content.

spans := []Span{
	{Text: "open docs", OnSelect: openDocs},
}

Rich(&spans)

Live rich spans

Rich now accepts live span slices directly, so updating the backing slice updates the rendered rich text.

status := []Span{
	{Text: "idle", Style: Style{FG: Ansi16(8)}},
}

view := Rich(&status)

status = []Span{
	{Text: "running", Style: Style{FG: Ansi16(10)}},
}

Custom effect compilation

Added EffectCompiler, EffectFloat64, and EffectCompilable so custom effects can compile dynamic values, animated values, and conditional values through the template system.

type shadeEffect struct {
	strengthArg any
	strength    EffectFloat64
}

func (e shadeEffect) CompileEffect(c EffectCompiler) Effect {
	e.strength = c.Float64(e.strengthArg)
	return e
}

func (e shadeEffect) Apply(buf *Buffer, ctx PostContext) {
	strength := e.strength.Float64()
	for y := 0; y < ctx.Height; y++ {
		for x := 0; x < ctx.Width; x++ {
			cell := buf.Get(x, y)
			if cell.Rune == 0 || cell.Style.FG.Mode == ColorDefault {
				continue
			}
			cell.Style.FG = Lerp(cell.Style.FG, RGB(0, 0, 0), strength)
			buf.Set(x, y, cell)
		}
	}
}

ScreenEffect(
	shadeEffect{
		strengthArg: In(Animate.Duration(time.Second)(0.5)).
			Out(Animate.Duration(time.Second)(0.0)),
	},
)

Readable colour helpers

Added helpers for contrast-aware colour work.

bg := RGB(18, 18, 22)
fg := RGB(120, 130, 145)
accent := RGB(60, 130, 255)

readable := ReadableTint(bg, accent, fg, 4.5, 0.65)
ratio := ContrastRatio(readable, bg)

Improved

Nested ForEach

Nested repeated UI can now bind to slice fields on the current item and preserve the correct outer and inner item contexts.

type Project struct {
	Name  string
	Tasks []Task
}

type Task struct {
	Title string
}

ForEach(&projects, func(project *Project) Component {
	return VBox(
		Text(project.Name),
		ForEach(&project.Tasks, func(task *Task) Component {
			return Text(task.Title)
		}),
	)
})

Scoped route lifecycle

Route handlers declared inside conditional and switched UI now attach only while that branch is active.

Switch(&mode).
	Case("list", VBox(
		On(Key("<Enter>", openSelected)),
		List(&items, renderItem),
	)).
	Case("search", VBox(
		On(Key("<Esc>", exitSearch)),
		Input(&query).Bind(),
	))

Modal input precedence

Modal route scopes now take input precedence while active, so modal handlers can shadow root handlers without leaking after the modal closes.

VBox(
	On(Key("<Esc>", quit)),
	If(&confirming).Then(
		Overlay(
			VBox(
				On.Modal(Key("<Esc>", func() { confirming = false })),
				Text("cancel deploy?"),
			),
		),
	),
)

Screen effect opacity and dynamic values

Screen effects now compose better with live UI state: focus-based effects track node opacity, drop shadows and SpinGlow have clearer opacity controls, and effect strength values can be compiled from dynamic inputs.

var cardRef NodeRef

VBox.Ref(&cardRef).Opacity(cardOpacity)(
	Text("syncing"),
)

ScreenEffect(
	SEGlow().
		Focus(&cardRef).
		Strength(&glowStrength).
		Radius(5),
	SEDropShadow().
		Focus(&cardRef).
		Opacity(&shadowOpacity).
		OpacityMode(OpacitySmooth).
		Radius(6),
	SESpinGlow(&cardRef).
		Opacity(&glowOpacity).
		OpacityMode(OpacityPaint).
		Radius(4).
		Speed(1.2),
)

Selection list layout

Selection list rendering and row layout are more robust for complex rows, shrinking lists, and constrained viewports.

List(&items, func(item *Item) Component {
	return HBox(
		Text(item.Name),
		Text(item.Status),
	)
})

Text view navigation naming

Text view navigation now uses the same naming pattern as other navigable components.

TextView(content).
	BindNav("j", "k").
	BindPageNav("<C-d>", "<C-u>")

Radio navigation naming

Radio navigation parameter names now match down/up navigation order.

Radio(&selected, "small", "medium", "large").
	BindNav("j", "k")

Changed

  • The public API has been renamed in several places for consistency.
  • Default animation easing is now EaseOutQuart.
  • Default animation duration is now 280ms.
  • Concrete screen effect types are exported, making configured effects easier to pass around.
  • ScreenEffect now returns a component rather than exposing the old node struct type.
  • Textf and Rich now return component values rather than public compile-form node structs.
  • Checkbox accepts either string or *string for static and dynamic labels.
  • App router methods have been renamed to PushRouter and PopRouter.
  • Layer view sizing methods have been renamed to Width and Height.
  • Text view scrolling bindings have been renamed to BindNav and BindPageNav.
  • Colour helpers have been renamed to shorter names such as Ansi16, Ansi256, Blend, and Lerp.
  • Blend mode names now use BlendDodge and BlendBurn.
  • SEDimAll has been renamed to SEDim.
  • MatchNode and SwitchNode have been renamed to MatchC and SwitchC.

Fixed

  • Fixed rich text wrapping across lines.
  • Corrected nested repeated-template bindings, including item field captures and nested slice fields.
  • Corrected ForEach row layout inside HBox, including parent-width constraints.
  • Reworked repeated-template rendering to reduce direct-vs-repeated inconsistencies for layers, overlays, screen effects, and autotables.
  • Corrected dynamic match, switch, and container style values inside repeated templates.
  • Improved screen effect fade behaviour for glow, bloom, shadow, SpinGlow, and vignette-style effects.
  • Propagated detected terminal backgrounds into compositing and effect rendering.
  • Fixed selection list clamping when source data shrinks or becomes empty.
  • Fixed rich span jump target registration after wrapping.
  • Fixed rich span jump targets inside scroll views.
  • Fixed dynamic checkbox labels through the main Checkbox constructor.
  • Improved jump label rendering across rich spans, scroll views, and composed layouts.
  • Fixed jump mode behaviour when no jump mode state has been initialised yet.

Removed

  • Removed legacy struct-literal compile paths for tabs, overlays, tables, custom nodes, rich text nodes, text inputs, and screen effect nodes.
  • Removed old public struct-literal node APIs from the preferred component-building path.
  • Removed the legacy table renderer in favour of the newer table/autotable paths.
  • Removed direct animation Out chaining from tweens. Use the In(...).Out(...) presence pattern instead.
  • Removed redundant examples that no longer matched the preferred API shape.

Migration notes

If you're running any migrations from v0.6.0 or previous you should first run go fix ./... which should automate the vast majority of the API cleanup for you.

Most migration work in this release is mechanical API cleanup.

// before
Widget(measure, render)

// after
Custom(measure, render)
// before
Scroll(contentSize, viewSize, &position)

// after
Scrollbar(contentSize, viewSize, &position)
// before
BasicColor(2)
PaletteColor(42)

// after
Ansi16(2)
Ansi256(42)
// before
BlendColor(base, top, BlendColorDodge)
LerpColor(a, b, 0.5)

// after
Blend(base, top, BlendDodge)
Lerp(a, b, 0.5)
// before
SEDimAll()

// after
SEDim()
// before
LayerView(layer).ViewWidth(40).ViewHeight(8)

// after
LayerView(layer).Width(40).Height(8)
// before
app.Push(router)
app.Pop()

// after
app.PushRouter(router)
app.PopRouter()
// before
TextView(content).BindScroll("j", "k")
TextView(content).BindPageScroll("<C-d>", "<C-u>")

// after
TextView(content).BindNav("j", "k")
TextView(content).BindPageNav("<C-d>", "<C-u>")
// before
CheckboxPtr(&enabled, &label)

// after
Checkbox(&enabled, &label)

Some deprecated shims remain for renamed helpers, but this release intentionally removes a lot of older public API surface as part of the cleanup.

Updated demos and examples

  • Updated remaining demos away from legacy struct-literal node APIs.
  • Updated showcase, table, widget, selection, postprocess, nesting, avionics, minivim, todo, and stream demos for the current API.
  • Added cmd/ondemo for declarative route handling.
  • Added cmd/easeharness for easing behaviour.
  • Updated examples for renamed helpers and preferred component constructors.

Full changelog: v0.6.0...v0.7.0