v0.27.0
Typed widget DSL — ui! widget names are now first-class component constructors. Button (...) { Text("Click") } expands directly into the right world.insert(__w, Button { ... }) (and inserts a child Text node), no more enchant block boilerplate to wire up the actual widget kind. The whole gallery + esp32c3-animation moves over; lowercase widget names still parse for now (Layout fallback) but are slated for hard rejection in a follow-up.
Added
- Typed widget syntax — capitalised ui! names map to component types:
- Built-ins:
Button,Checkbox,Slider,Switch,ProgressBar,TabBar,TextInput,Image,Text,LazyList,MirrorOf,BackgroundBlur,TemporalMix. - Reserved layout names
View / Row / Columnstay as pure layout containers;Row/Columnauto-setFlexDirection. - Anything else capitalised expands the same way and lets rustc check the type — user-defined components opt in by registering a
Viewand (typically) impl'ingDefault.
- Built-ins:
- Component widgets accept positional arg for a primary attr —
Text("hello"),Image(&IMG_THUMBS_UP). Widgets without a registered primary attr reject positional args at compile time. - Named niche slots —
@name { children }inside a component widget routes children into aNicheMap-registered anchor entity. Components publish aNicheMapfrom theirauto_attach; ui! resolves the anchor at fill time. idreferences —id: "foo"attr registers aNamedIdmarker on the entity and an entry in the newIdMapresource.id("foo")(or unquotedid(foo)) inside any other attr value expands to aWorld::find_by_idlookup hoisted to the surrounding scope, so a sibling can refer to a marked entity by name.IdMapis auto-inserted byApp::new, available everywhere;World::find_by_id(&'static str) -> Option<Entity>is the read API.
matchsyntax —ui! { match expr { Pat => { children } ... } }inserts only the winning arm's children. Composable with niche / walk / if.- Empty children blocks may be omitted —
Image (texture: &IMG)(no{}) parses as a Widget with no children.if/walk/@niche/matchstill require their bodies. - Multi-attr root header is multi-line and accepts trailing commas —
:( parent: root, world: w, :)and the existing newline form both parse; mashing them onto a single line errors out at parse time. - Off-screen renderer tests — new
tests/typed_widget_visual.rs,tests/theme_swap_visual.rs,tests/typed_widget.rs,tests/niche.rs,tests/match_syntax.rs, plusgallery/examples/theme_swap_snapshot.rsfor ad-hoc PPM/PNG dumps. - trybuild fixtures for ui! diagnostics:
unknown_attr_typo,id_must_be_string,root_header_oneline,positional_on_unsupported,button_no_text_attr.
Fixed
App::rendernow wraps its full-screen flush inbegin_flush/end_flush— previously the first frame afterApp::runonly calledSurface::flush, which on SDL leavespending_presentpinned without callingcanvas.copy + canvas.present. Standalone SDL demos that didn't pull inInputFeedbackPlugin(which keeps the dirty path firing every frame) showed a black window until the user moused over a widget. TheApp::render_dirtynon-empty branch had the right wrapper already; this brings the full-render path in line.- Fuzzy hint for unknown ui! attrs — typing
txt:now suggeststext:via Levenshtein-2 lookup against the known attr list.
Changed
Buttonno longer acceptstext:attr — the label belongs in a child Text node now:Button (...) { Text("Click") }. The legacytext:writes the string intoWidgetBuilder::texton Layout-kind widgets only; on Component widgets it routes to the struct'stextfield, which Button doesn't have, so the old form fails to compile with a clear error.Textis a tuple struct (Text(Vec<u8>)); the macro special-casesText(text: "x")andText("x")to emitText(b"x".to_vec())instead of named-struct init.TextInput'stext_color/placeholder_color/cursor_color/focus_border_colorroute to the struct field, not the WidgetBuilder, when the widget is a TextInput.SliderandTabBargainDefaultimpls so the macro's..Default::default()spread can fill in unset fields. Defaults are sentinels (Slider→(0..1),TabBar→count: 0); always overridden in practice by the call site.parse_attrsreturns aParsedAttrsstruct rather than a tuple (avoids clippytype_complexity).
BREAKING
- Demos that wrote
dark_btn (...) [Button::new()...]must rewrite toButton (...) [...] { Text("...") }. The codemod that ran during this development cycle handled the gallery + mirui-examples sweep; user code on the legacy syntax keeps compiling on the lowercase Layout fallback path until a later release tightens that. - xrune's
DsAttr.namebecomesOption<syn::Ident>(positional args havename = None). EveryDsRuneimpl gainsinscribe_nicheandinscribe_match. xrune workspace depends onxrune = "1.4"(see xrune CHANGELOG). mirui-macrosCargo.toml requiressynwithvisit-mutfeature.