Skip to content

Lisp-first UI asset loading, message-driven binding execution, and Example app migration #349

Description

@corepunch

Overview

Add a Lisp-format UI asset loader as a parallel format to XML, route binding evaluation through Binding objects via message dispatch, and migrate the Example application runtime flow from XML assets to Lisp assets.

Motivation

XML is verbose for deeply nested UI layouts. A Lisp S-expression format provides a more concise alternative while keeping the same object model and property system. Additionally, binding evaluation should flow through the component message system rather than direct function calls, enabling future extensibility (e.g., lazy evaluation, dirty tracking).

Changes

1. Lisp UI Asset Loader (source/filesystem/fs_lisp.c)

New 792-line file implementing an S-expression parser and object builder that produces the same in-memory Object tree as the XML loader.

Syntax overview:

; XML equivalent:
; <StackView Name="Card" Direction="Vertical" Spacing=8>
;   <TextBlock Name="Title" Text="{Binding DataContext/Title}"/>
; </StackView>

; Lisp equivalent:
(StackView Name="Card" Direction="Vertical" Spacing=8
  (TextBlock Name="Title" Text="{Card.Title}"))

Key syntax differences from XML:

Feature XML Lisp
Element <Tag ...> (Tag ...)
Text content <TextBlock>text</TextBlock> (TextBlock "text")
Binding {Binding DataContext/Title} {Card.Title}
Binding directive <BindingExpression Target="..."> (:bind Target="..." expr)
Inline expression N/A Key=(if (step 640 (bind "x")) "a" "b")
Nested children <Parent><Child/></Parent> (Parent (Child))
Key=value atoms Name="foo" (always quoted) Name="foo" or Name=foo (unquoted ok)

Binding expression translation:

Lisp binding expressions are translated to the ORCA VM expression string:

(bind "Node.ActualWidth")     →  {Node.ActualWidth}
(if  a b c)                   →  IF(a, b, c)
(step n x)                    →  STEP(n, x)
(vector2 x y)                 →  Vector2(x, y)
(any-other-fn ...)            →  FNAME(...)

2. Binding Evaluate Message (source/core/core.cgen)

Added Binding.Evaluate message to route binding evaluation through the component message system:

<message name="Evaluate">
  <summary>Evaluates the binding token program and imports the result into the target property.</summary>
  <fields>
    <field name="Property" type="Property" pointer="true">Target property to import evaluation result into.</field>
  </fields>
</message>

The Binding class now handles Binding.Compile and Binding.Evaluate messages, enabling message-driven binding lifecycle.

3. Example App Migration

All Example prefab bindings converted from legacy {Binding Path} to non-legacy {Path} syntax:

<!-- Before (legacy) -->
<TextBlock Text="{Binding DataContext/Title}"/>

<!-- After (non-legacy) -->
<TextBlock Text="{DataContext/Title}"/>

New Lisp prefab templates created for all Example prefabs:

XML Prefab Lisp Prefab
Prefabs/SignalCard.xml Prefabs/SignalCard.lisp
Prefabs/IconCard.xml Prefabs/IconCard.lisp
Prefabs/GalleryCard.xml Prefabs/GalleryCard.lisp
Prefabs/Quote.xml Prefabs/Quote.lisp
Prefabs/Mertic.xml Prefabs/Metric.lisp
Prefabs/WorkflowStep.xml Prefabs/WorkflowStep.lisp
Prefabs/TabPanelHeader.xml Prefabs/TabPanelHeader.lisp
Prefabs/XmlModelNode.xml Prefabs/XmlModelNode.lisp
Prefabs/FeatureCard.xml Prefabs/FeatureCard.lisp
Prefabs/FeatureImageCard.xml Prefabs/FeatureImageCard.lisp
Prefabs/ImageCaptionCard.xml Prefabs/ImageCaptionCard.lisp

Lisp prefabs use dot-notation for DataContext paths:

; XML: {Binding DataContext/Title}
; Lisp: {Card.Title}

(TextBlock Name="Title" Text="{Card.Title}")
(TextBlock Name="Value" ForegroundColor="{../Card.PrimaryColor}")

Active Example runtime now points to Lisp assets:

  • StartupScreenExample/Screens/Application.lisp
  • PlaceholderTemplateExample/Prefabs/*.lisp
  • Modal path → Example/Screens/GetStartedPopup.lisp

Files Changed

File Change
source/filesystem/fs_lisp.c New — 792-line Lisp UI loader
source/core/core.cgen Added Binding.Evaluate message
source/core/property/property_program.c 295 lines of binding compile/evaluate changes
source/core/property/property_core.c Binding evaluation routing
samples/Example/Screens/Application.lisp New — 422-line Lisp screen
samples/Example/Screens/GetStartedPopup.lisp New — Lisp popup
samples/Example/Prefabs/*.lisp New — 11 Lisp prefab templates
tests/test_lisp.lua New — 252-line test suite

Validation

  • make unite — C build passes
  • make test-headless — All layout and interaction tests pass
  • tests/test_lisp.lua — 252-line Lisp loader test suite passes

References

Metadata

Metadata

Assignees

No one assigned

    Labels

    No labels
    No labels

    Projects

    No projects

    Milestone

    No milestone

    Relationships

    None yet

    Development

    No branches or pull requests

    Issue actions