<?xml version="1.0" encoding="utf-8" standalone="yes"?><rss version="2.0" xmlns:atom="http://www.w3.org/2005/Atom"><channel><title>Build a custom Compose toolkit on Staticvar Learn</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/</link><description>Recent content in Build a custom Compose toolkit on Staticvar Learn</description><generator>Hugo -- gohugo.io</generator><language>en-us</language><atom:link href="https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/index.xml" rel="self" type="application/rss+xml"/><item><title>Find the Compose boundary</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/01-compose-boundary/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/01-compose-boundary/</guid><description>Learning goal Separate the machinery supplied by Compose from the UI policy that a new surface requires.
Start with the split “Compose” is often used to mean several layers at once. For a new surface, that shortcut hides the most important design decision.
DiagramExpandmindmap root((Compose ecosystem)) Compiler transforms composable calls inserts group calls and source-derived keys passes a Composer Runtime Composer records positional information slot table remember snapshot state recomposition effects tree changes Compose UI LayoutNode measurement drawing modifiers input semantics Foundation and Material Text scrolling controls design system Atlas UI reuses the first two branches.</description></item><item><title>Create a multiplatform foundation</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/02-multiplatform-foundation/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/02-multiplatform-foundation/</guid><description>Learning goal Place the reusable toolkit engine in Kotlin Multiplatform commonMain and keep platform integration behind narrow contracts.
Source-set map DiagramExpandflowchart TD commonMain[commonMain&amp;lt;br/&amp;gt;nodes · Applier · composables&amp;lt;br/&amp;gt;layout · frames · semantics] commonTest[commonTest&amp;lt;br/&amp;gt;tree · layout · frame tests] mac[macosArm64Main&amp;lt;br/&amp;gt;optional adapter] linux[linuxX64Main&amp;lt;br/&amp;gt;optional adapter] win[mingwX64Main&amp;lt;br/&amp;gt;optional adapter] mac --&amp;gt;|depends on| commonMain linux --&amp;gt;|depends on| commonMain win --&amp;gt;|depends on| commonMain commonTest -. tests .-&amp;gt; commonMain The arrows follow source-set dependency direction: platform source sets consume shared policy from commonMain.</description></item><item><title>Define the Surface contract</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/03-surface-contract/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/03-surface-contract/</guid><description>Learning goal Create the smallest boundary through which a complete logical frame can reach any surface.
Think in frames, not platform calls The toolkit should not know whether a frame becomes pixels, hardware instructions, remote messages, or test data.
DiagramExpandflowchart LR Painter --&amp;gt; Frame[SurfaceFrame] Frame --&amp;gt; Contract{{Surface.present}} Contract --&amp;gt; Recorder[RecordingSurface] Contract --&amp;gt; Device[Future device adapter] Contract --&amp;gt; Remote[Future remote adapter] Typed surface vocabulary Start with explicit units and commands:
package atlas.</description></item><item><title>Build a retained node tree</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/04-retained-node-tree/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/04-retained-node-tree/</guid><description>Learning goal Define the objects that survive recomposition and carry UI intent into layout and rendering.
Tree versus frame DiagramExpandflowchart LR subgraph Retained[Retained across frames] Root --&amp;gt; Group Group --&amp;gt; Title[&amp;#34;TextNode: Counter&amp;#34;] Group --&amp;gt; Value[&amp;#34;TextNode: Count 0&amp;#34;] end Retained --&amp;gt;|layout &amp;#43; paint| Frame1[Frame 1] Retained --&amp;gt;|property update| Changed[&amp;#34;Value.text = Count 1&amp;#34;] Changed --&amp;gt;|layout &amp;#43; paint| Frame2[Frame 2] Nodes hold identity and current properties. Frames are temporary output snapshots.
Geometry types package atlas.</description></item><item><title>Teach Compose with an Applier</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/05-applier/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/05-applier/</guid><description>Learning goal Implement the structural bridge through which Compose Runtime maintains the Atlas node tree.
The bridge DiagramExpandsequenceDiagram participant Apply as Runtime apply phase participant Applier as AtlasApplier participant Current as current ContainerNode Apply-&amp;gt;&amp;gt;Applier: insertTopDown(0, group) [no-op] Apply-&amp;gt;&amp;gt;Applier: down(group) Applier-&amp;gt;&amp;gt;Applier: current = group Apply-&amp;gt;&amp;gt;Applier: insertTopDown(0, text) [no-op] Apply-&amp;gt;&amp;gt;Applier: down(text) Apply-&amp;gt;&amp;gt;Applier: apply TextNode property updates Apply-&amp;gt;&amp;gt;Applier: up() Apply-&amp;gt;&amp;gt;Applier: insertBottomUp(0, text) Applier-&amp;gt;&amp;gt;Current: attach text to current group Apply-&amp;gt;&amp;gt;Applier: up() Apply-&amp;gt;&amp;gt;Applier: insertBottomUp(0, group) An Applier&amp;lt;N&amp;gt; navigates and structurally edits a tree of N.</description></item><item><title>Emit nodes from composables</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/06-node-emitting-composables/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/06-node-emitting-composables/</guid><description>Learning goal Define a Compose target and connect declarative Atlas components to retained Atlas nodes.
From call to node DiagramExpandflowchart LR Call[&amp;#34;Text(Hello)&amp;#34;] --&amp;gt; Compiler[Compose compiler transformation] Compiler --&amp;gt; CN[ComposeNode] CN --&amp;gt;|new call-site identity| Factory[create TextNode] CN --&amp;gt;|changed parameter| Update[record property update] Factory --&amp;gt; Applier Update --&amp;gt; Node[existing TextNode] Mark the custom target A target marker lets the Compose compiler distinguish Atlas composables from composables intended for another Applier family.
package atlas.</description></item><item><title>Host composition and frames</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/07-composition-host/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/07-composition-host/</guid><description>Learning goal Understand the process owner that coordinates composition, snapshot notifications, frame time, rendering, and cleanup.
The host is an orchestrator DiagramExpandflowchart TD Content[AtlasContent] --&amp;gt; Composition StateWrite[Snapshot state write] --&amp;gt; Notify[Apply notifications] Notify --&amp;gt; Recomposer Clock[MonotonicFrameClock] --&amp;gt; Recomposer Recomposer --&amp;gt;|apply changes| Applier Applier --&amp;gt; Tree Applier --&amp;gt;|onEndChanges| RenderRequest RenderRequest --&amp;gt; Renderer Tree --&amp;gt; Renderer Renderer --&amp;gt; Surface DriverEnd[driver ends or critical work fails] --&amp;gt; TerminalState[terminal-state coordinator] TerminalState --&amp;gt; Lifecycle Lifecycle --&amp;gt;|dispose| Composition Lifecycle --&amp;gt;|cancel/join| Recomposer Lifecycle --&amp;gt;|cancel| Clock Lifecycle --&amp;gt;|cancel/join| OwnerJob[owner job] Lifecycle --&amp;gt;|dispose| Notify The host does not decide layout or component behavior.</description></item><item><title>Understand state and identity</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/08-state-and-identity/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/08-state-and-identity/</guid><description>Learning goal Trace a state mutation into an in-place node update and predict when identity is retained, moved, or replaced.
Counter example @Composable @AtlasComposable fun Counter(count: Int, onIncrement: () -&amp;gt; Unit) { Group { Text(&amp;#34;Count: $count&amp;#34;) Button(label = &amp;#34;Increment&amp;#34;, onActivate = onIncrement) } } @Composable @AtlasComposable fun RememberingCounter() { var count by remember { mutableStateOf(0) } Counter(count = count, onIncrement = { count++ }) } Button is developed fully after events are introduced; for now, focus on its state-writing callback.</description></item><item><title>Add layout and rendering</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/09-layout-and-rendering/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/09-layout-and-rendering/</guid><description>Learning goal Turn retained intent into geometry and then into a complete SurfaceFrame that is not mutated after construction.
Separate the phases DiagramExpandflowchart LR Tree[Retained node tree] --&amp;gt; Measure Measure --&amp;gt; Place Place --&amp;gt; Paint Paint --&amp;gt; Frame[SurfaceFrame] Frame --&amp;gt; Surface Measure: a parent offers constraints; a child chooses a size. Place: a parent assigns child positions. Paint: nodes append commands using final geometry. Present: the adapter receives one complete frame.</description></item><item><title>Add events, focus, and semantics</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/10-events-focus-semantics/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/10-events-focus-semantics/</guid><description>Learning goal Return typed interaction from the surface to component state without coupling components to a platform protocol.
The feedback loop DiagramExpandflowchart LR NativeInput[native or device input] --&amp;gt; Adapter Adapter --&amp;gt;|SurfaceEvent| EventSink EventSink --&amp;gt;|enqueue on owner context| Router Router --&amp;gt; Target[focused or hit-tested node] Target --&amp;gt; Handler Handler --&amp;gt; State State --&amp;gt; Runtime Runtime --&amp;gt; Tree Tree --&amp;gt;|new frame| Surface Output uses Surface.present. Input uses a separate contract because a surface may be output-only, and input capabilities can evolve independently.</description></item><item><title>Package and verify the toolkit</title><link>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/11-library-boundaries/</link><pubDate>Mon, 01 Jan 0001 00:00:00 +0000</pubDate><guid>https://learn.staticvar.dev/courses/compose-for-new-surfaces/01-build-a-custom-compose-toolkit/11-library-boundaries/</guid><description>Learning goal Turn the learning implementation into a reusable library with explicit public boundaries and layered evidence.
Final architecture DiagramExpandflowchart TB subgraph App Content[Atlas composables] State[application state] end subgraph Library[atlas-ui commonMain] Components[public components] Host[AtlasHost] Lifecycle[terminal state &amp;#43; cleanup] RuntimeBridge[ComposeNode &amp;#43; AtlasApplier] Nodes[internal retained nodes] Layout[layout &amp;#43; painter] SurfaceContract[Surface / SurfaceFrame] InputContract[EventSink / SurfaceEvent] DriverContract[FrameDriver] Router[event router] Semantics[semantics &amp;#43; focus] end subgraph Adapters Recording[recording adapters] Concrete[real surface adapters] end TestHarness[test harness] State --&amp;gt; Content --&amp;gt; Components --&amp;gt; RuntimeBridge --&amp;gt; Nodes --&amp;gt; Layout --&amp;gt; SurfaceContract InputContract --&amp;gt; Router --&amp;gt; State DriverContract --&amp;gt; Host --&amp;gt; RuntimeBridge Host --&amp;gt;|owns| Router Host --&amp;gt;|owns| Lifecycle Lifecycle --&amp;gt;|cancel / dispose / join| RuntimeBridge Semantics --&amp;gt; Nodes Semantics --&amp;gt; Router Recording -.</description></item></channel></rss>