From d1a7e90fc48560a51819e34424168e853e7661c5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 18:37:28 +0000 Subject: [PATCH 01/11] Add Lisp UI asset format loader as parallel format alongside XML Agent-Logs-Url: https://github.com/corepunch/orca/sessions/57120e7a-e214-405c-86f8-5333ff5d653b Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- Makefile | 4 + samples/Example/Screens/Application.lisp | 422 +++++++++++++ source/filesystem/fs_lisp.c | 763 +++++++++++++++++++++++ source/filesystem/fs_local.h | 6 + source/filesystem/w_filesystem.c | 15 + tests/test_lisp.lua | 248 ++++++++ 6 files changed, 1458 insertions(+) create mode 100644 samples/Example/Screens/Application.lisp create mode 100644 source/filesystem/fs_lisp.c create mode 100644 tests/test_lisp.lua diff --git a/Makefile b/Makefile index fb64096b..81a78460 100644 --- a/Makefile +++ b/Makefile @@ -338,6 +338,9 @@ test-tab-interaction: app copyshare test-node: app copyshare $(TARGET) -test=tests/test_node.lua +test-lisp: app copyshare + $(TARGET) -test=tests/test_lisp.lua + test: test-headless test-properties test-styles test-filesystem test-editor test-text-layout test-stack-layout test-grid-layout test-interaction test-tab-interaction test-node $(TARGET) -test=tests/test.xml @@ -367,5 +370,6 @@ test-headless: unite test-properties test-styles test-filesystem test-message-re $(TARGET) -test=tests/test_parsers.lua $(TARGET) -test=tests/test_object_hierarchy.lua $(TARGET) -test=tests/test_tabbar.lua + $(TARGET) -test=tests/test_lisp.lua include Makefile.webgl diff --git a/samples/Example/Screens/Application.lisp b/samples/Example/Screens/Application.lisp new file mode 100644 index 00000000..c5f9d81a --- /dev/null +++ b/samples/Example/Screens/Application.lisp @@ -0,0 +1,422 @@ +; Application.lisp — Lisp-format equivalent of Application.xml +; Converted from XML to validate the Lisp loader at feature parity. +; +; Binding syntax: Key=(if (step 640 (bind "Node.ActualWidth")) "auto" "wide") +; Directive syntax: (:bind Target="Prop" expr) + +(Screen Name="Application" Width="1280" Height="800" ClearColor="$surface" + (Grid Name="Root" Rows="64px auto" Spacing="0" + (StackView Name="NavBar" BackgroundColor="$panel-background" + Direction="Horizontal" Spacing="32" AlignItems="Center" + HorizontalPadding="40" + (StackView Name="BrandMark" Direction="Horizontal" Spacing="10" AlignItems="Center" + (ImageView Name="BrandIcon" + Source="Example/Icons/blocks.svg?width=22&type=mask" + Width="22" Height="22" ForegroundColor="$accent") + (TextBlock Name="Brand" FontSize="20" ForegroundColor="$accent" + "ORCA")) + (TextBlock Name="NavOverview" FontSize="14" ForegroundColor="$text-secondary" + "Overview") + (TextBlock Name="NavFeatures" FontSize="14" ForegroundColor="$text-secondary" + "Features") + (TextBlock Name="NavGallery" FontSize="14" ForegroundColor="$text-secondary" + "Gallery") + (TextBlock Name="NavDocs" FontSize="14" ForegroundColor="$text-secondary" + "Docs")) + (StackView Name="Body" Direction="Vertical" Spacing="40" OverflowY="Scroll" + ClipChildren="true" Padding="40" BackgroundColor="$surface-deep" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) + (Grid Name="Hero" Columns="auto auto" Spacing="24" BackgroundColor="$surface" + (:bind Target="Node.Visible" + (step 640 (bind "Node.ActualWidth"))) + (:bind Target="Grid.Columns" + (if (step 640 (bind "Node.ActualWidth")) "auto auto" "auto")) + (StackView Name="HeroContent" Direction="Vertical" Spacing="0" Padding="16" + (TextBlock Name="HeadlineLine1" FontSize="48" ForegroundColor="$text-primary" + "Build stunning") + (TextBlock Name="HeadlineLine2" FontSize="48" ForegroundColor="$accent" + "UI in real-time.") + (Node2D Name="HeadlineSpacer" Height="20") + (TextBlock Name="SubHeadline" FontSize="15" ForegroundColor="$text-muted" + WordWrap="true" + "A modern UI engine for games and native apps. Compose layouts from grids, stacks, text, and images — all rendered at 60 fps.") + (Node2D Name="MetricSpacer" Height="40") + (Grid Name="Metrics" Columns="auto auto auto" Spacing="16" + (LayerPrefabPlaceholder Name="MetricUsers" + PlaceholderTemplate="Example/Prefabs/Mertic" + Card.Title="2.4 M" + Card.Subtitle="Active users" + Card.PrimaryColor="$accent") + (LayerPrefabPlaceholder Name="MetricUptime" + PlaceholderTemplate="Example/Prefabs/Mertic" + Card.Title="99.9 %" + Card.Subtitle="Uptime SLA" + Card.PrimaryColor="$accent-green") + (LayerPrefabPlaceholder Name="MetricFrame" + PlaceholderTemplate="Example/Prefabs/Mertic" + Card.Title="1.2 ms" + Card.Subtitle="Frame time" + Card.PrimaryColor="$accent-blue"))) + (ImageView Name="HeroImage" Source="Example/Images/people" + Stretch="UniformToFill")) + (StackView Name="TabbedTechSection" Direction="Vertical" Spacing="20" + BackgroundColor="$surface" Padding="40" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) + (TextBlock Name="TabbedTechHeading" Text="Build the UI, not the ceremony" + FontSize="28" ForegroundColor="$text-primary") + (TextBlock Name="TabbedTechSubheading" + Text="A clean XML surface, Lua where things move, MoonScript when the app grows, and one UI that can travel across targets." + FontSize="14" ForegroundColor="$text-muted") + (Grid Name="OrcaSignals" Columns="auto auto auto" Spacing="14" + (:bind Target="Grid.Columns" + (if (step 640 (bind "Node.ActualWidth")) "auto auto auto" "auto auto")) + (LayerPrefabPlaceholder Name="SignalXml" + PlaceholderTemplate="Example/Prefabs/SignalCard" + Card.Icon="Example/Icons/code.svg?width=28&type=mask" + Card.Title="XML-first screens" + Card.Body="Layouts stay readable." + Card.PrimaryColor="$accent") + (LayerPrefabPlaceholder Name="SignalLua" + PlaceholderTemplate="Example/Prefabs/SignalCard" + Card.Icon="Example/Icons/file.svg?width=28&type=mask" + Card.Title="Lua beside the view" + Card.Body="Behavior stays close." + Card.PrimaryColor="$accent-blue") + (LayerPrefabPlaceholder Name="SignalMoon" + PlaceholderTemplate="Example/Prefabs/SignalCard" + Card.Icon="Example/Icons/moon.svg?width=28&type=mask" + Card.Title="MoonScript when it grows" + Card.Body="Less noise, same runtime." + Card.PrimaryColor="$text-secondary") + (LayerPrefabPlaceholder Name="SignalShip" + PlaceholderTemplate="Example/Prefabs/SignalCard" + Card.Icon="Example/Icons/globe.svg?width=28&type=mask" + Card.Title="Desktop, mobile, web" + Card.Body="One screen, many targets." + Card.PrimaryColor="$accent-green") + (LayerPrefabPlaceholder Name="SignalSpeed" + PlaceholderTemplate="Example/Prefabs/SignalCard" + Card.Icon="Example/Icons/zap.svg?width=28&type=mask" + Card.Title="Fast edit-run-refine" + Card.Body="See changes immediately." + Card.PrimaryColor="$accent-amber") + (LayerPrefabPlaceholder Name="SignalScale" + PlaceholderTemplate="Example/Prefabs/SignalCard" + Card.Icon="Example/Icons/cpu.svg?width=28&type=mask" + Card.Title="Built to scale" + Card.Body="Same UI, larger ambitions." + Card.PrimaryColor="$text-muted")) + (TabView Name="OrcaTabs" SelectedValue="xml" + (TabBar Name="OrcaTabBar" Height="44" BackgroundColor="$panel-background" + Padding="8 8 8 0" Spacing="12" + (Tab Name="XmlTab" Value="xml" Width="140" "XML") + (Tab Name="LuaTab" Value="lua" Width="140" "Lua") + (Tab Name="MoonTab" Value="moon" Width="140" "MoonScript") + (Tab Name="DeployTab" Value="deploy" Width="140" "Deploy")) + (StackView Name="xml" Direction="Vertical" Spacing="24" + BackgroundColor="$surface-deep" Padding="32" + (LayerPrefabPlaceholder Name="XmlPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + Card.Icon="Example/Icons/grid.svg?width=26&type=mask" + Card.Title="XML reads like a blueprint" + Card.PrimaryColor="$accent") + (Grid Name="XmlGrid" Columns="auto auto" Spacing="20" + (StackView Name="XmlCopy" Direction="Vertical" Spacing="14" + (TextBlock Name="XmlBody" + Text="The screen stays legible at a glance, which makes it easy to scan, compare, and reshape as the design evolves." + FontSize="14" ForegroundColor="$text-muted" WordWrap="true") + (StackView Name="XmlNotes" Direction="Vertical" Spacing="8" + BackgroundColor="$panel-background" Padding="20" + (TextBlock Name="XmlNote1" + Text="• Grids and stacks stay visible in the source." + FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + (TextBlock Name="XmlNote2" + Text="• Spacing, padding, and hierarchy remain explicit." + FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + (TextBlock Name="XmlNote3" + Text="• The shape of the screen is easy to read before it ever runs." + FontSize="13" ForegroundColor="$text-secondary" WordWrap="true"))) + (StackView Name="XmlModel" Direction="Vertical" Spacing="10" + BackgroundColor="$card-background" Padding="20" + (TextBlock Name="XmlModelHeading" Text="The structure stays honest" + FontSize="16" ForegroundColor="$text-primary") + (LayerPrefabPlaceholder Name="XmlModelLine1" + PlaceholderTemplate="Example/Prefabs/XmlModelNode" + Card.Icon="Example/Icons/monitor.svg?width=20&type=mask" + Card.Title="Screen" + PaddingLeft="0" + Card.PrimaryColor="$accent") + (LayerPrefabPlaceholder Name="XmlModelLine2" + PlaceholderTemplate="Example/Prefabs/XmlModelNode" + Card.Icon="Example/Icons/panel-top.svg?width=20&type=mask" + Card.Title="TabView" + PaddingLeft="16" + Card.PrimaryColor="$text-secondary") + (LayerPrefabPlaceholder Name="XmlModelLine3" + PlaceholderTemplate="Example/Prefabs/XmlModelNode" + Card.Icon="Example/Icons/grid.svg?width=20&type=mask" + Card.Title="Grid" + PaddingLeft="32" + Card.PrimaryColor="$text-secondary") + (LayerPrefabPlaceholder Name="XmlModelLine4" + PlaceholderTemplate="Example/Prefabs/XmlModelNode" + Card.Icon="Example/Icons/layers.svg?width=20&type=mask" + Card.Title="StackView" + PaddingLeft="32" + Card.PrimaryColor="$text-secondary") + (LayerPrefabPlaceholder Name="XmlModelLine5" + PlaceholderTemplate="Example/Prefabs/XmlModelNode" + Card.Icon="Example/Icons/image.svg?width=20&type=mask" + Card.Title="ImageView" + PaddingLeft="48" + Card.PrimaryColor="$text-secondary") + (LayerPrefabPlaceholder Name="XmlModelLine6" + PlaceholderTemplate="Example/Prefabs/XmlModelNode" + Card.Icon="Example/Icons/type.svg?width=20&type=mask" + Card.Title="TextBlock" + PaddingLeft="48" + Card.PrimaryColor="$text-secondary")))) + (StackView Name="lua" Direction="Vertical" Spacing="24" + BackgroundColor="$surface-deep" Padding="32" + (LayerPrefabPlaceholder Name="LuaPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + Card.Icon="Example/Icons/code.svg?width=26&type=mask" + Card.Title="Lua stays close to the surface" + Card.PrimaryColor="$accent-blue") + (TextBlock Name="LuaBody" + Text="The parts that move stay beside the screen that uses them, so interaction and state feel close and easy to change." + FontSize="14" ForegroundColor="$text-muted" WordWrap="true") + (Grid Name="LuaGrid" Columns="auto auto auto" Spacing="16" + (LayerPrefabPlaceholder Name="LuaCard1" + PlaceholderTemplate="Example/Prefabs/IconCard" + Card.Icon="Example/Icons/mouse-pointer-click.svg?width=40&type=mask" + Card.Title="Input" + Card.Body="Tap, click, and selection handling can sit right beside the view that needs it." + Card.PrimaryColor="$accent" + Card.IconBackground="$icon-bg-purple") + (LayerPrefabPlaceholder Name="LuaCard2" + PlaceholderTemplate="Example/Prefabs/IconCard" + Card.Icon="Example/Icons/database.svg?width=40&type=mask" + Card.Title="State" + Card.Body="Small state objects stay local, so the screen remains easy to reason about." + Card.PrimaryColor="$accent-blue" + Card.IconBackground="$icon-bg-blue") + (LayerPrefabPlaceholder Name="LuaCard3" + PlaceholderTemplate="Example/Prefabs/IconCard" + Card.Icon="Example/Icons/link-2.svg?width=40&type=mask" + Card.Title="Binding" + Card.Body="Let Lua connect the moving pieces while XML keeps the hierarchy in view." + Card.PrimaryColor="$accent-green" + Card.IconBackground="$icon-bg-green"))) + (StackView Name="moon" Direction="Vertical" Spacing="24" + BackgroundColor="$surface-deep" Padding="32" + (LayerPrefabPlaceholder Name="MoonPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + Card.Icon="Example/Icons/moon.svg?width=26&type=mask" + Card.Title="MoonScript trims the noise" + Card.PrimaryColor="$text-secondary") + (Grid Name="MoonGrid" Columns="auto auto" Spacing="20" + (StackView Name="MoonCopy" Direction="Vertical" Spacing="14" + (TextBlock Name="MoonBody" + Text="MoonScript keeps larger app code compact and expressive, while still compiling to the same Lua runtime." + FontSize="14" ForegroundColor="$text-muted" WordWrap="true") + (StackView Name="MoonNotes" Direction="Vertical" Spacing="8" + BackgroundColor="$panel-background" Padding="20" + (TextBlock Name="MoonNote1" + Text="• Helpful when app modules get a little larger." + FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + (TextBlock Name="MoonNote2" + Text="• The syntax stays concise, but the runtime stays Lua." + FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + (TextBlock Name="MoonNote3" + Text="• It pairs naturally with XML screens and Lua helpers." + FontSize="13" ForegroundColor="$text-secondary" WordWrap="true"))) + (StackView Name="MoonModel" Direction="Vertical" Spacing="10" + BackgroundColor="$card-background" Padding="20" + (TextBlock Name="MoonModelHeading" Text="Same runtime, less ceremony" + FontSize="16" ForegroundColor="$text-primary") + (TextBlock Name="MoonModelLine1" Text="Cleaner class bodies" + FontSize="14" ForegroundColor="$text-secondary") + (TextBlock Name="MoonModelLine2" Text="Readable app controllers" + FontSize="14" ForegroundColor="$text-secondary") + (TextBlock Name="MoonModelLine3" Text="Plain Lua at runtime" + FontSize="14" ForegroundColor="$text-secondary") + (TextBlock Name="MoonModelLine4" Text="Comfortable beside XML layouts" + FontSize="14" ForegroundColor="$text-secondary")))) + (StackView Name="deploy" Direction="Vertical" Spacing="24" + BackgroundColor="$surface-deep" Padding="32" + (LayerPrefabPlaceholder Name="DeployPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + Card.Icon="Example/Icons/globe.svg?width=26&type=mask" + Card.Title="One UI, many targets" + Card.PrimaryColor="$accent-green") + (TextBlock Name="DeployBody" + Text="Build once, tune the spacing, and carry the same design language across desktop, mobile, and web-oriented targets." + FontSize="14" ForegroundColor="$text-muted" WordWrap="true") + (Grid Name="DeployGrid" Columns="auto auto auto" Spacing="16" + (LayerPrefabPlaceholder Name="DeployCard1" + PlaceholderTemplate="Example/Prefabs/IconCard" + Card.Icon="Example/Icons/monitor.svg?width=40&type=mask" + Card.Title="Desktop" + Card.Body="Best when you want denser information, more breathing room, and precision input." + Card.PrimaryColor="$accent" + Card.IconBackground="$icon-bg-purple") + (LayerPrefabPlaceholder Name="DeployCard2" + PlaceholderTemplate="Example/Prefabs/IconCard" + Card.Icon="Example/Icons/smartphone.svg?width=40&type=mask" + Card.Title="Mobile" + Card.Body="Responsive spacing keeps the same interface calm and touch-friendly on smaller screens." + Card.PrimaryColor="$accent-green" + Card.IconBackground="$icon-bg-green") + (LayerPrefabPlaceholder Name="DeployCard3" + PlaceholderTemplate="Example/Prefabs/IconCard" + Card.Icon="Example/Icons/globe.svg?width=40&type=mask" + Card.Title="Web" + Card.Body="The same screen model can carry into browser workflows without being rewritten." + Card.PrimaryColor="$accent-blue" + Card.IconBackground="$icon-bg-blue")) + (Grid Name="DeployImages" Columns="auto auto" Spacing="12" + (LayerPrefabPlaceholder Name="DeployImage1" + PlaceholderTemplate="Example/Prefabs/ImageCaptionCard" + Card.Image="Example/Images/orca-tab-city" + Card.Title="City glow") + (LayerPrefabPlaceholder Name="DeployImage2" + PlaceholderTemplate="Example/Prefabs/ImageCaptionCard" + Card.Image="Example/Images/orca-tab-lights" + Card.Title="Night light")) + (StackView Name="DeployCommandLink" Direction="Horizontal" Spacing="12" + AlignItems="Center" BackgroundColor="$panel-background" Padding="16" + (ImageView Name="DeployCommandIcon" + Source="Example/Icons/monitor.svg?width=20&type=mask" + Width="20" Height="20" ForegroundColor="$accent-green") + (StackView Name="DeployCommandCopy" Direction="Vertical" Spacing="2" + (TextBlock Name="DeployCommandLabel" Text="Desktop build" + FontSize="13" ForegroundColor="$text-secondary") + (TextBlock Name="DeployCommandValue" + Text="make clean && make -j8 && build/bin/orca samples/Example" + FontSize="14" ForegroundColor="$text-primary" WordWrap="true")))))) + (Grid Name="FeatureSection" Columns="auto auto auto auto" Spacing="20" + (LayerPrefabPlaceholder Name="FeatureXmlCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + Card.Icon="Example/Icons/layers.svg?width=40&type=mask" + Card.Title="XML stays readable" + Card.Body="The layout tree is right there in the page, so structure and spacing stay easy to tune." + Card.PrimaryColor="$accent") + (LayerPrefabPlaceholder Name="FeatureLuaCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + Card.Icon="Example/Icons/monitor.svg?width=40&type=mask" + Card.Title="Lua stays close" + Card.Body="Interactions, bindings, and state can live next to the screen that owns them." + Card.PrimaryColor="$accent-blue") + (LayerPrefabPlaceholder Name="FeatureShipCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + Card.Icon="Example/Icons/globe.svg?width=40&type=mask" + Card.Title="Ships everywhere" + Card.Body="The same UI can move across desktop, mobile, and web-friendly targets." + Card.PrimaryColor="$accent-green") + (LayerPrefabPlaceholder Name="FeatureSpeedCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + Card.Icon="Example/Icons/zap.svg?width=40&type=mask" + Card.Title="Fast iteration" + Card.Body="Edit, reload, and refine quickly without losing the overall shape of the interface." + Card.PrimaryColor="$accent-amber")) + (StackView Name="GallerySection" Direction="Vertical" Spacing="20" + BackgroundColor="$surface" Padding="40" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) + (TextBlock Name="GalleryHeading" Text="Gallery" + FontSize="28" ForegroundColor="$text-primary") + (TextBlock Name="GallerySubheading" + Text="Screenshots and scenes rendered entirely by the ORCA engine." + FontSize="14" ForegroundColor="$text-muted") + (Grid Name="GalleryGrid" Columns="auto auto" Spacing="16" + (LayerPrefabPlaceholder Name="GalleryCard1" + PlaceholderTemplate="Example/Prefabs/GalleryCard" + Card.Image="Example/Images/lake" + Card.Title="Lake Scene" + Card.Subtitle="Real-time reflections and water surface rendered at 60 fps.") + (LayerPrefabPlaceholder Name="GalleryCard2" + PlaceholderTemplate="Example/Prefabs/GalleryCard" + Card.Image="Example/Images/peacock" + Card.Title="Peacock" + Card.Subtitle="High-fidelity texture sampling with anisotropic filtering."))) + (StackView Name="WorkflowSection" Direction="Vertical" Spacing="24" + BackgroundColor="$surface-deep" Padding="40" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) + (TextBlock Name="WorkflowHeading" Text="How It Works" + FontSize="28" ForegroundColor="$text-primary") + (TextBlock Name="WorkflowSubheading" + Text="From XML layout to a live rendered scene in four simple steps." + FontSize="14" ForegroundColor="$text-muted") + (Grid Name="WorkflowSteps" Columns="auto auto auto auto" Spacing="16" + (:bind Target="Grid.Columns" + (if (step 640 (bind "Node.ActualWidth")) + "auto auto auto auto" "auto auto")) + (LayerPrefabPlaceholder Name="Step1" + PlaceholderTemplate="Example/Prefabs/WorkflowStep" + Card.Title="01" Card.Subtitle="Define Layout" + Card.PrimaryColor="$accent" + Card.Body="Write your screen as an XML file with familiar Grid and StackView containers.") + (LayerPrefabPlaceholder Name="Step2" + PlaceholderTemplate="Example/Prefabs/WorkflowStep" + Card.Title="02" Card.Subtitle="Load Assets" + Card.PrimaryColor="$accent-blue" + Card.Body="Images, fonts, and data files are streamed from packages at runtime.") + (LayerPrefabPlaceholder Name="Step3" + PlaceholderTemplate="Example/Prefabs/WorkflowStep" + Card.Title="03" Card.Subtitle="Script Logic" + Card.PrimaryColor="$accent-green" + Card.Body="Attach Lua scripts to any component for interactivity and data binding.") + (LayerPrefabPlaceholder Name="Step4" + PlaceholderTemplate="Example/Prefabs/WorkflowStep" + Card.Title="04" Card.Subtitle="Ship" + Card.PrimaryColor="$accent-amber" + Card.Body="Deploy to desktop, mobile, or WebGL with a single build command."))) + (StackView Name="TestimonialsSection" Direction="Vertical" Spacing="24" + BackgroundColor="$surface" Padding="40" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) + (TextBlock Name="TestimonialsHeading" Text="What Developers Say" + FontSize="28" ForegroundColor="$text-primary") + (Grid Name="TestimonialsGrid" Columns="auto auto auto" Spacing="20" + (LayerPrefabPlaceholder Name="Quote1" + PlaceholderTemplate="Example/Prefabs/Quote" + Card.PrimaryColor="$accent" + Card.Title="— Alex R." Card.Subtitle="Lead Engineer" + Card.Body="ORCA lets us ship native-quality UI on every platform from a single XML source. It changed how we build products.") + (LayerPrefabPlaceholder Name="Quote2" + PlaceholderTemplate="Example/Prefabs/Quote" + Card.PrimaryColor="$accent-green" + Card.Title="— Sam T." Card.Subtitle="UI Architect" + Card.Body="The declarative layout system is a joy to work with. Our designers write XML and developers hook up the logic — perfect split.") + (LayerPrefabPlaceholder Name="Quote3" + PlaceholderTemplate="Example/Prefabs/Quote" + Card.PrimaryColor="$accent-blue" + Card.Title="— Jordan M." Card.Subtitle="Game Developer" + Card.Body="Sub-millisecond frame times even on mid-range hardware. We ditched our old HTML renderer the day we tried ORCA."))) + (StackView Name="CtaSection" Direction="Vertical" Spacing="24" + BackgroundColor="$panel-background" Padding="60" AlignItems="Center" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 60 60) (vector2 16 16))) + (TextBlock Name="CtaHeading" Text="Ready to build something great?" + FontSize="36" ForegroundColor="$text-primary" + TextHorizontalAlignment="Center") + (TextBlock Name="CtaBody" + Text="Download ORCA, open a sample project, and have your first screen running in minutes." + FontSize="15" ForegroundColor="$text-muted" WordWrap="true" + TextHorizontalAlignment="Center") + (StackView Name="CtaButtons" Direction="Horizontal" Spacing="16" + (TextBlock Name="CtaButtonPrimary" Text="Get Started" + FontSize="14" ForegroundColor="$white" + BackgroundColor="$accent" Padding="16" Width="140" Height="44" + TextHorizontalAlignment="Center" TextVerticalAlignment="Center" + LeftButtonUp="{ShowModalAction Example/Screens/GetStartedPopup}") + (TextBlock Name="CtaButtonSecondary" Text="Learn more" + FontSize="14" ForegroundColor="$text-secondary" + BackgroundColor="$card-background" Padding="16" + Width="140" Height="44" + TextHorizontalAlignment="Center" + TextVerticalAlignment="Center")))))) diff --git a/source/filesystem/fs_lisp.c b/source/filesystem/fs_lisp.c new file mode 100644 index 00000000..fbcdf3b6 --- /dev/null +++ b/source/filesystem/fs_lisp.c @@ -0,0 +1,763 @@ +/* + * fs_lisp.c — Lisp-format UI asset loader. + * + * Syntax overview: + * (TypeName Key="val" Key=(binding-expr) + * (:bind Target="Prop" expr) + * (ChildType ...) + * "text content") + * + * Binding expressions are translated from Lisp to the ORCA expression string + * recognised by Token_Create / OBJ_AttachPropertyProgram: + * (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 (fname ...) → FNAME(...) (uppercased) + */ + +#include +#include +#include +#include "fs_xml_inline.h" + +#include +#include +#include +#include + +/* Forward declaration of the property parser from parsers module */ +extern int parse_property(const char *str, + struct PropertyType const *prop, + void *valueptr); + +/* ========================================================================= + * String buffer + * ====================================================================== */ + +struct sbuf { char *s; size_t n, cap; }; + +static void +sbuf_put(struct sbuf *b, const char *s) +{ + if (!s || !*s) return; + size_t len = strlen(s); + if (b->n + len + 1 > b->cap) { + size_t need = b->n + len + 1; + b->cap = b->cap ? b->cap * 2 : 256; + if (b->cap < need) b->cap = need; + b->s = realloc(b->s, b->cap); + } + if (!b->s) return; + memcpy(b->s + b->n, s, len); + b->n += len; + b->s[b->n] = '\0'; +} + +static void +sbuf_putc(struct sbuf *b, char c) +{ + char tmp[2] = {c, '\0'}; + sbuf_put(b, tmp); +} + +/* ========================================================================= + * Lexer + * ====================================================================== */ + +enum lsp_tok { + TOK_EOF = 0, + TOK_LPAREN, /* ( */ + TOK_RPAREN, /* ) */ + TOK_STRING, /* "..." */ + TOK_ATOM, /* anything else */ +}; + +struct lsp_lex { + const char *p; + char *str; /* heap-allocated text for current STRING / ATOM token */ + int tok; + int line; +}; + +static void +lsp_skip(struct lsp_lex *l) +{ + for (;;) { + while (*l->p && isspace((unsigned char)*l->p)) { + if (*l->p == '\n') l->line++; + l->p++; + } + if (*l->p == ';') { + while (*l->p && *l->p != '\n') l->p++; + } else { + break; + } + } +} + +static void +lsp_next(struct lsp_lex *l) +{ + free(l->str); + l->str = NULL; + lsp_skip(l); + + if (!*l->p) { l->tok = TOK_EOF; return; } + if (*l->p == '(') { l->tok = TOK_LPAREN; l->p++; return; } + if (*l->p == ')') { l->tok = TOK_RPAREN; l->p++; return; } + + if (*l->p == '"') { + l->p++; + struct sbuf b = {0}; + while (*l->p && *l->p != '"') { + if (*l->p == '\\' && l->p[1]) { + l->p++; + switch (*l->p) { + case 'n': sbuf_putc(&b, '\n'); break; + case 't': sbuf_putc(&b, '\t'); break; + case '"': sbuf_putc(&b, '"'); break; + case '\\': sbuf_putc(&b, '\\'); break; + default: sbuf_putc(&b, '\\'); sbuf_putc(&b, *l->p); break; + } + } else { + sbuf_putc(&b, *l->p); + } + l->p++; + } + if (*l->p == '"') l->p++; + l->str = b.s ? b.s : strdup(""); + if (!l->str) { l->tok = TOK_EOF; return; } + l->tok = TOK_STRING; + return; + } + + /* Atom: up to whitespace, parens, quotes, semicolons */ + const char *start = l->p; + while (*l->p && !isspace((unsigned char)*l->p) && + *l->p != '(' && *l->p != ')' && *l->p != '"' && *l->p != ';') { + l->p++; + } + size_t n = (size_t)(l->p - start); + l->str = malloc(n + 1); + if (!l->str) { l->tok = TOK_EOF; return; } + memcpy(l->str, start, n); + l->str[n] = '\0'; + l->tok = TOK_ATOM; +} + +/* ========================================================================= + * Binding expression translation: Lisp form → ORCA expression string + * + * translate_expr(l, out): + * Pre: l->tok is the first token of an expression. + * Post: l->tok is the token AFTER the expression. + * + * translate_list_body(l, out): + * Pre: '(' was just consumed and lsp_next was called → l->tok is the + * function-name atom. + * Post: l->tok is the token AFTER the closing ')'. + * ====================================================================== */ + +static void translate_expr(struct lsp_lex *l, struct sbuf *out); + +static void +translate_list_body(struct lsp_lex *l, struct sbuf *out) +{ + if (l->tok != TOK_ATOM) { + Con_Error("Lisp binding: expected function name in list (line %d)", l->line); + return; + } + + char fname[64]; + snprintf(fname, sizeof(fname), "%s", l->str); + lsp_next(l); + + /* (bind "PropPath") → {PropPath} */ + if (!strcasecmp(fname, "bind")) { + if (l->tok == TOK_STRING) { + sbuf_putc(out, '{'); + sbuf_put(out, l->str); + sbuf_putc(out, '}'); + lsp_next(l); + } else { + Con_Error("Lisp (bind) expects a string path (line %d)", l->line); + } + if (l->tok == TOK_RPAREN) lsp_next(l); + return; + } + + /* General function: UPPERCASE(arg, arg, ...) */ + char orca_name[64]; + size_t flen = strlen(fname); + size_t cap = sizeof(orca_name) - 1; + for (size_t i = 0; i < flen && i < cap; i++) + orca_name[i] = (char)toupper((unsigned char)fname[i]); + orca_name[flen < cap ? flen : cap] = '\0'; + + sbuf_put(out, orca_name); + sbuf_putc(out, '('); + + bool_t first = TRUE; + while (l->tok != TOK_RPAREN && l->tok != TOK_EOF) { + if (!first) sbuf_put(out, ", "); + first = FALSE; + translate_expr(l, out); + } + + sbuf_putc(out, ')'); + if (l->tok == TOK_RPAREN) lsp_next(l); +} + +static void +translate_expr(struct lsp_lex *l, struct sbuf *out) +{ + if (l->tok == TOK_LPAREN) { + lsp_next(l); /* advance to function name */ + translate_list_body(l, out); + return; + } + if (l->tok == TOK_STRING) { + sbuf_putc(out, '"'); + for (const char *p = l->str; *p; p++) { + if (*p == '"') sbuf_put(out, "\\\""); + else sbuf_putc(out, *p); + } + sbuf_putc(out, '"'); + lsp_next(l); + return; + } + if (l->tok == TOK_ATOM) { + sbuf_put(out, l->str); + lsp_next(l); + return; + } + Con_Error("Lisp: unexpected token %d in binding expression (line %d)", l->tok, l->line); +} + +/* Translate the binding expression starting at l->tok, return heap-alloc string + * in ORCA expression format. Caller must free(). */ +static char * +lsp_translate_binding(struct lsp_lex *l) +{ + struct sbuf out = {0}; + translate_expr(l, &out); + return out.s ? out.s : strdup(""); +} + +/* ========================================================================= + * AST: parsed node tree + * ====================================================================== */ + +struct lisp_attr { + char *key; /* property name */ + char *val; /* string value, or NULL for binding attrs */ + char *binding; /* ORCA expression string, or NULL for string attrs */ + struct lisp_attr *next; +}; + +struct lisp_node { + char *type; /* element class name, e.g. "Grid" */ + struct lisp_attr *attrs; /* singly-linked list of attribute k/v pairs */ + struct lisp_node *children; /* singly-linked list of child elements */ + struct lisp_node *next; /* next sibling */ + char *text; /* text content, if any */ +}; + +static void +lisp_attr_free(struct lisp_attr *a) +{ + while (a) { + struct lisp_attr *nx = a->next; + free(a->key); + free(a->val); + free(a->binding); + free(a); + a = nx; + } +} + +static void +lisp_node_free(struct lisp_node *n) +{ + while (n) { + struct lisp_node *nx = n->next; + free(n->type); + free(n->text); + lisp_attr_free(n->attrs); + lisp_node_free(n->children); + free(n); + n = nx; + } +} + +/* ========================================================================= + * Parser: Lisp text → lisp_node AST + * ====================================================================== */ + +static struct lisp_node *parse_node(struct lsp_lex *l); + +/* parse_element_body: l->tok is positioned after reading the element type. + * Reads attributes, :bind directives, children, and optional text until ')'. + * Consumes the closing ')' and advances l to the next token. */ +static void +parse_element_body(struct lsp_lex *l, struct lisp_node *node) +{ + struct lisp_attr **attr_tail = &node->attrs; + struct lisp_node **child_tail = &node->children; + + while (l->tok != TOK_RPAREN && l->tok != TOK_EOF) { + + /* --- Parenthesised form: child element or :bind directive --- */ + if (l->tok == TOK_LPAREN) { + lsp_next(l); /* read head of the list */ + + if (l->tok == TOK_ATOM && l->str[0] == ':') { + /* Directive form: (:bind Target="Prop" expr) */ + if (!strcmp(l->str, ":bind")) { + lsp_next(l); /* advance to the Target= atom or bare "prop.name" */ + char *target = NULL; + + if (l->tok == TOK_ATOM) { + size_t klen = strlen(l->str); + if (klen > 1 && l->str[klen - 1] == '=') { + /* Key="Value" form: consume the Key= atom, then read the value */ + lsp_next(l); /* advance past "Key=" to the string/atom value */ + if (l->tok == TOK_STRING || l->tok == TOK_ATOM) { + target = strdup(l->str); /* the property path */ + lsp_next(l); /* advance past value to the expression */ + } + } else { + /* Bare atom (no trailing '=') is the target path directly */ + target = strdup(l->str); + lsp_next(l); + } + } else if (l->tok == TOK_STRING) { + /* Bare quoted string as target path */ + target = strdup(l->str); + lsp_next(l); + } + + char *expr = lsp_translate_binding(l); + if (target && expr) { + struct lisp_attr *a = calloc(1, sizeof(*a)); + a->key = target; + a->binding = expr; + *attr_tail = a; + attr_tail = &a->next; + } else { + free(target); + free(expr); + } + + /* Drain any remaining tokens to the closing ')' of :bind */ + while (l->tok != TOK_RPAREN && l->tok != TOK_EOF) lsp_next(l); + if (l->tok == TOK_RPAREN) lsp_next(l); + + } else { + /* Unknown directive — skip */ + Con_Error("Lisp: unknown directive '%s' (line %d)", l->str, l->line); + int depth = 1; + while (depth > 0 && l->tok != TOK_EOF) { + lsp_next(l); + if (l->tok == TOK_LPAREN) depth++; + else if (l->tok == TOK_RPAREN) depth--; + } + if (l->tok == TOK_RPAREN) lsp_next(l); + } + + } else { + /* Child element — l->tok is the type name atom */ + struct lisp_node *child = parse_node(l); + if (child) { *child_tail = child; child_tail = &child->next; } + } + continue; + } + + /* --- Bare string: text content --- */ + if (l->tok == TOK_STRING) { + free(node->text); + node->text = strdup(l->str); + lsp_next(l); + continue; + } + + /* --- Atom: key="val", key=(binding), or bare key (unquoted value) --- */ + if (l->tok == TOK_ATOM) { + size_t alen = strlen(l->str); + if (alen > 1 && l->str[alen - 1] == '=') { + char *key = strndup(l->str, alen - 1); + lsp_next(l); + + if (l->tok == TOK_STRING) { + struct lisp_attr *a = calloc(1, sizeof(*a)); + a->key = key; + a->val = strdup(l->str); + *attr_tail = a; + attr_tail = &a->next; + lsp_next(l); + + } else if (l->tok == TOK_LPAREN) { + /* Inline binding expression: Key=(lisp-expr) */ + char *expr = lsp_translate_binding(l); + struct lisp_attr *a = calloc(1, sizeof(*a)); + a->key = key; + a->binding = expr; + *attr_tail = a; + attr_tail = &a->next; + + } else if (l->tok == TOK_ATOM) { + /* Bare-word value, e.g. a number or enum without quotes */ + struct lisp_attr *a = calloc(1, sizeof(*a)); + a->key = key; + a->val = strdup(l->str); + *attr_tail = a; + attr_tail = &a->next; + lsp_next(l); + + } else { + Con_Error("Lisp: expected value after '%s=' (line %d)", key, l->line); + free(key); + } + } else { + Con_Error("Lisp: unexpected atom '%s' in element body (line %d)", l->str, l->line); + lsp_next(l); + } + continue; + } + + /* Unexpected token — skip */ + Con_Error("Lisp: unexpected token %d in element body (line %d)", l->tok, l->line); + lsp_next(l); + } + + /* Consume closing ')' */ + if (l->tok == TOK_RPAREN) lsp_next(l); +} + +/* parse_node: l->tok is the element type name (ATOM), after '(' was consumed. + * Returns the new node; l->tok points past the closing ')' on return. */ +static struct lisp_node * +parse_node(struct lsp_lex *l) +{ + if (l->tok != TOK_ATOM) { + Con_Error("Lisp: expected element type name (line %d), got tok=%d", l->line, l->tok); + /* Drain the malformed list */ + int depth = 1; + while (depth > 0 && l->tok != TOK_EOF) { + lsp_next(l); + if (l->tok == TOK_LPAREN) depth++; + else if (l->tok == TOK_RPAREN) depth--; + } + if (l->tok == TOK_RPAREN) lsp_next(l); + return NULL; + } + + struct lisp_node *node = calloc(1, sizeof(*node)); + node->type = strdup(l->str); + lsp_next(l); + parse_element_body(l, node); + return node; +} + +/* parse_file: parse a complete Lisp UI source string and return the root node. */ +static struct lisp_node * +parse_file(const char *src) +{ + struct lsp_lex l = { .p = src, .line = 1 }; + lsp_next(&l); + if (l.tok != TOK_LPAREN) { + Con_Error("Lisp: expected '(' at start of file"); + free(l.str); + return NULL; + } + lsp_next(&l); /* advance to element type name */ + struct lisp_node *root = parse_node(&l); + free(l.str); + return root; +} + +/* ========================================================================= + * Object factory: lisp_node → ORCA Object + * ====================================================================== */ + +static struct Object *apply_node(struct lisp_node *node); + +/* Look up a property descriptor by "implicit" (short name) or explicit (Ns.Name). */ +static struct PropertyType const * +lisp_propdesc(struct Object *o, const char *name) +{ + struct PropertyType const *pd = OBJ_FindImplicitProperty(o, name); + return pd ? pd : OBJ_FindExplicitProperty(o, name); +} + +/* Handle special property names that need engine-level calls. */ +static bool_t +apply_special_attr(struct Object *o, const char *key, const char *val) +{ + if (!strcmp(key, "ClassName") || !strcmp(key, "PlaceholderTemplate")) return TRUE; + if (!strcmp(key, "Name") || !strcmp(key, "id")) { + OBJ_SetName(o, val); + return TRUE; + } + if (!strcmp(key, "class")) { + _SendMessage(o, StyleController, AddClasses, .ClassNames = val); + return TRUE; + } + return FALSE; +} + +/* Append a trigger object to the object's Triggers array. */ +static void +lisp_append_trigger(struct Property *triggers_prop, struct Object *trigger) +{ + struct PropertyType const *pd = PROP_GetDesc(triggers_prop); + void *old_items = NULL; + int old_count = 0; + + if (!pd || !pd->IsArray || pd->DataType != kDataTypeObject) { + OBJ_ReleaseRef(trigger); + return; + } + if (PROP_GetValue(triggers_prop)) { + memcpy(&old_items, PROP_GetValue(triggers_prop), sizeof(old_items)); + memcpy(&old_count, + (char *)PROP_GetValue(triggers_prop) + sizeof(old_items), + sizeof(old_count)); + } + int count = old_count + 1; + struct Object **items = calloc((size_t)count, sizeof(struct Object *)); + if (!items) { OBJ_ReleaseRef(trigger); return; } + if (old_items && old_count > 0) + memcpy(items, old_items, (size_t)old_count * sizeof(struct Object *)); + items[old_count] = trigger; + + struct { void *items; int count; } value = { items, count }; + PROP_SetValue(triggers_prop, &value); +} + +/* Create an inline EventTrigger from a {ShorthandObject ...} string value. */ +static void +lisp_inline_event(struct Object *o, + struct PropertyType const *pd, + const char *value) +{ + struct Property *triggers = NULL; + struct PropertyType const *td = OBJ_FindImplicitProperty(o, "Triggers"); + struct Object *action = _LoadObjectFromXmlFragment(value, 0); + struct Object *trigger = action ? OBJ_Create(ID_EventTrigger) : NULL; + char routed[MAX_PROPERTY_STRING] = {0}; + + if (!td || FAILED(OBJ_FindLongProperty(o, td->FullIdentifier, &triggers)) || !trigger) { + if (action) OBJ_ReleaseRef(action); + Con_Error("Lisp: property '%s' does not support inline trigger shorthand", pd->Name); + return; + } + + snprintf(routed, sizeof(routed), + (pd->Category && *pd->Category) ? "%s.%s" : "%s", + (pd->Category && *pd->Category) ? pd->Category : pd->Name, + pd->Name); + const char *rv = routed; + if (FAILED(OBJ_SetPropertyValue(trigger, "RoutedEvent", &rv))) { + OBJ_ReleaseRef(trigger); + OBJ_ReleaseRef(action); + return; + } + + OBJ_AddChild(trigger, action, FALSE); + lisp_append_trigger(triggers, trigger); +} + +/* Apply a string-valued attribute: theme lookup, object refs, inline objects, + * event triggers, binding shorthands, and typed property values. */ +static void +apply_str_attr(struct Object *o, const char *key, const char *val) +{ + struct PropertyType const *pd = lisp_propdesc(o, key); + if (!pd) { + Con_Error("Lisp: unknown property '%s' on '%s'", key, OBJ_GetClassName(o)); + return; + } + + /* {Binding path} shorthand → expression program */ + size_t vlen = strlen(val); + if (vlen > 10 && !strncmp(val, "{Binding ", 9) && val[vlen - 1] == '}') { + char expr[MAX_PROPERTY_STRING]; + snprintf(expr, sizeof(expr), "{%.*s}", (int)(vlen - 10), val + 9); + OBJ_AttachPropertyProgram(o, key, expr, kBindingModeExpression, TRUE); + return; + } + + /* Theme lookup: $key → resolved string */ + const char *resolved = val; + char theme_buf[MAX_PROPERTY_STRING] = {0}; + if (val[0] == '$') { + const char *tv = FS_GetThemeValue(val); + if (!tv) { Con_Error("Lisp: missing theme key '%s'", val); return; } + strncpy(theme_buf, tv, sizeof(theme_buf) - 1); + resolved = theme_buf; + } + + struct Property *p = NULL; + if (FAILED(OBJ_FindLongProperty(o, pd->FullIdentifier, &p))) { + Con_Error("Lisp: could not get property slot '%s'", key); + return; + } + + /* @path → object reference */ + if (resolved[0] == '@') { + struct Object *ref = OBJ_FindByPath(o, resolved + 1); + if (ref && pd->DataType == kDataTypeObject) PROP_SetValue(p, &ref); + else Con_Error("Lisp: could not resolve object reference '%s'", resolved + 1); + return; + } + + /* { or < → inline object / event trigger */ + if (resolved[0] == '{' || resolved[0] == '<') { + if (pd->DataType == kDataTypeEvent) { + lisp_inline_event(o, pd, resolved); + } else if (pd->DataType == kDataTypeStruct) { + _LoadStructFromXmlFragment(p, pd, resolved); + } else { + struct Object *child = _LoadObjectFromXmlFragment(resolved, 0); + if (child) { + if (GetBinding(child) || GetBindingExpression(child)) { + OBJ_SendMessageW(child, ID_Binding_Compile, 0, p); + OBJ_ReleaseRef(child); + } else if (pd->DataType == kDataTypeObject) { + PROP_SetValue(p, &child); + } else { + OBJ_ReleaseRef(child); + Con_Error("Lisp: unsupported inline object for property '%s'", key); + } + } + } + return; + } + + /* Default: parse as typed property value */ + char tmp[MAX_PROPERTY_STRING] = {0}; + if (!parse_property(resolved, pd, tmp)) return; + PROP_SetValue(p, tmp); + if (pd->DataType == kDataTypeString) free(*(char **)tmp); +} + +static const char * const PREFAB_TYPES[] = { + "LayerPrefabPlaceholder", + "ObjectPrefabPlaceholder", + "LibraryPlaceholder", + NULL +}; + +static bool_t +is_prefab_type(const char *type) +{ + for (int i = 0; PREFAB_TYPES[i]; i++) + if (!strcmp(type, PREFAB_TYPES[i])) return TRUE; + return FALSE; +} + +/* Convert a parsed AST node to an ORCA Object. */ +static struct Object * +apply_node(struct lisp_node *node) +{ + if (!node) return NULL; + + bool_t is_prefab = is_prefab_type(node->type); + struct Object *o = NULL; + + if (is_prefab) { + const char *tmpl = NULL; + for (struct lisp_attr *a = node->attrs; a; a = a->next) { + if (!strcmp(a->key, "PlaceholderTemplate") && a->val) { tmpl = a->val; break; } + } + if (tmpl) { + o = FS_LoadObject(tmpl); + if (o) OBJ_SetFlags(o, OBJ_GetFlags(o) | OF_TEMPLATE); + } + if (!o) { + Con_Error("Lisp: prefab placeholder missing PlaceholderTemplate"); + return NULL; + } + } else { + struct ClassDesc const *cls = OBJ_FindClass(node->type); + if (!cls) { + Con_Error("Lisp: unknown element type '%s'", node->type); + return NULL; + } + o = OBJ_Create(cls->ClassID); + } + + if (!o) return NULL; + + /* Apply attributes */ + for (struct lisp_attr *a = node->attrs; a; a = a->next) { + if (!strcmp(a->key, "PlaceholderTemplate")) continue; + + if (a->binding) { + OBJ_AttachPropertyProgram(o, a->key, a->binding, kBindingModeExpression, TRUE); + } else if (a->val) { + if (!apply_special_attr(o, a->key, a->val)) + apply_str_attr(o, a->key, a->val); + } + } + + /* Text content: replicate XML loader behaviour (short-circuit, no children) */ + if (node->text) { + OBJ_SetTextContent(o, node->text); + if (!is_prefab) OBJ_SendMessageW(o, ID_Object_Start, 0, NULL); + return o; + } + + /* Child elements */ + for (struct lisp_node *child = node->children; child; child = child->next) { + struct Object *child_obj = apply_node(child); + if (child_obj) OBJ_AddChild(o, child_obj, FALSE); + } + + if (!is_prefab) OBJ_SendMessageW(o, ID_Object_Start, 0, NULL); + return o; +} + +/* ========================================================================= + * Public API + * ====================================================================== */ + +ORCA_API struct Object * +FS_LoadObjectFromLispString(const char *lisp_string) +{ + if (!lisp_string || !*lisp_string) { + Con_Error("FS_LoadObjectFromLispString: empty input"); + return NULL; + } + struct lisp_node *root = parse_file(lisp_string); + if (!root) { + Con_Error("FS_LoadObjectFromLispString: parse failed"); + return NULL; + } + struct Object *o = apply_node(root); + lisp_node_free(root); + if (!o) Con_Error("FS_LoadObjectFromLispString: object creation failed"); + return o; +} + +ORCA_API struct Object * +FS_LoadObjectFromLisp(const char *path) +{ + struct file *fp = FS_LoadFile(path); + if (!fp) { + Con_Error("Lisp: failed to load file '%s'", path); + return NULL; + } + char *src = malloc(fp->size + 1); + if (!src) { FS_FreeFile(fp); return NULL; } + memcpy(src, fp->data, fp->size); + src[fp->size] = '\0'; + FS_FreeFile(fp); + + struct Object *o = FS_LoadObjectFromLispString(src); + free(src); + if (o) OBJ_SetSourceFile(o, path); + return o; +} diff --git a/source/filesystem/fs_local.h b/source/filesystem/fs_local.h index 7c0de564..a472e114 100644 --- a/source/filesystem/fs_local.h +++ b/source/filesystem/fs_local.h @@ -34,4 +34,10 @@ struct file *_ReadOnDisk(FILE *fp); struct Object * FS_LoadObjectFromXml(lpcString_t path); +ORCA_API struct Object * +FS_LoadObjectFromLisp(const char *path); + +ORCA_API struct Object * +FS_LoadObjectFromLispString(const char *lisp_string); + #endif diff --git a/source/filesystem/w_filesystem.c b/source/filesystem/w_filesystem.c index b59d0e55..8f4eae4c 100644 --- a/source/filesystem/w_filesystem.c +++ b/source/filesystem/w_filesystem.c @@ -378,10 +378,22 @@ static struct Object* _css_file_loader(int argc, const char* argv[]) { return (argc > 0) ? FS_LoadObjectFromCss(argv[0]) : NULL; } +static struct Object* _lisp_file_loader(int argc, const char* argv[]) { + return (argc > 0) ? FS_LoadObjectFromLisp(argv[0]) : NULL; +} + +static int f_loadObjectFromLispString(lua_State* L) { + const char* lisp_string = luaL_checkstring(L, 1); + struct Object* result = FS_LoadObjectFromLispString(lisp_string); + luaX_pushObject(L, result); + return 1; +} + void on_filesystem_module_registered(lua_State* L) { OBJ_RegisterFileLoader(".xml", _xml_file_loader); OBJ_RegisterFileLoader(".css", _css_file_loader); + OBJ_RegisterFileLoader(".lisp", _lisp_file_loader); lua_register(L, "fs_findmodule", f_find_module); luaL_dostring(L, "table.insert(package.searchers, fs_findmodule)"); @@ -404,6 +416,9 @@ void on_filesystem_module_registered(lua_State* L) lua_pushcfunction(L, f_init); lua_setfield(L, -2, "init"); + + lua_pushcfunction(L, f_loadObjectFromLispString); + lua_setfield(L, -2, "loadObjectFromLispString"); luaopen_orca_pipe(L); lua_setfield(L, -2, "pipe"); diff --git a/tests/test_lisp.lua b/tests/test_lisp.lua new file mode 100644 index 00000000..bf8142a5 --- /dev/null +++ b/tests/test_lisp.lua @@ -0,0 +1,248 @@ +local test = require "orca.test" +-- Tests for the Lisp UI asset loader. +-- +-- Covers: +-- * S-expression parser: nested elements, text content, bare string children. +-- * Attribute binding (Key=(expr)) and :bind directives are attached as programs. +-- * Theme-value attributes ($key) resolve without error. +-- * Prefab placeholders (LayerPrefabPlaceholder) load templates correctly. +-- * Full parity check: Application.lisp produces the same named objects as +-- Application.xml when parsed. +-- +-- All tests are headless (no display required). +-- Run with: $(TARGET) -test=tests/test_lisp.lua + +local core = require "orca.core" +local ui = require "orca.UIKit" +local filesystem = require "orca.filesystem" + +local filesystem_initialized = false +local function ensure_fs() + if filesystem_initialized then return end + filesystem.init("samples/Example") + filesystem_initialized = true +end + +-- --------------------------------------------------------------------------- +-- Helpers +-- --------------------------------------------------------------------------- + +local function find_object(root, name) + if root:getName() == name then return root end + local child = root:getFirstChild() + while child do + local found = find_object(child, name) + if found then return found end + child = child:getNext() + end + return nil +end + +-- --------------------------------------------------------------------------- +-- test_basic_elements: minimal Lisp → object tree +-- --------------------------------------------------------------------------- +local function test_basic_elements() + ensure_fs() + local obj = filesystem.loadObjectFromLispString([[ + (Screen Name="TestScreen" Width="100" Height="100" + (StackView Name="Inner" Direction="Vertical")) + ]]) + test.expect(obj ~= nil, "basic: root object should be created") + if not obj then return end + test.expect(obj:getName() == "TestScreen", "basic: root name should be TestScreen, got " .. tostring(obj:getName())) + local inner = find_object(obj, "Inner") + test.expect(inner ~= nil, "basic: child StackView should exist") + print("PASS: test_basic_elements") +end + +-- --------------------------------------------------------------------------- +-- test_text_content: bare string → OBJ_SetTextContent +-- --------------------------------------------------------------------------- +local function test_text_content() + ensure_fs() + local obj = filesystem.loadObjectFromLispString([[ + (StackView Name="Row" + (TextBlock Name="Label" FontSize="14" ForegroundColor="$text-primary" + "Hello, world")) + ]]) + test.expect(obj ~= nil, "text: root should exist") + if not obj then return end + local label = find_object(obj, "Label") + test.expect(label ~= nil, "text: TextBlock should exist") + if label then + local text = label:getTextContent() + test.expect(text == "Hello, world", + "text: content should be 'Hello, world', got '" .. tostring(text) .. "'") + end + print("PASS: test_text_content") +end + +-- --------------------------------------------------------------------------- +-- test_binding_attr: inline binding on a property attribute +-- --------------------------------------------------------------------------- +local function test_binding_attr() + ensure_fs() + local ok, err = pcall(function() + local obj = filesystem.loadObjectFromLispString([[ + (Grid Name="TestGrid" Spacing="0" + Columns=(if (step 640 (bind "Node.ActualWidth")) "auto auto" "auto")) + ]]) + test.expect(obj ~= nil, "binding_attr: object should be created") + end) + test.expect(ok, "binding_attr: inline binding should not raise an error: " .. tostring(err)) + print("PASS: test_binding_attr") +end + +-- --------------------------------------------------------------------------- +-- test_bind_directive: (:bind Target="Prop" expr) directive +-- --------------------------------------------------------------------------- +local function test_bind_directive() + ensure_fs() + local ok, err = pcall(function() + local obj = filesystem.loadObjectFromLispString([[ + (StackView Name="Adaptive" Direction="Vertical" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8)))) + ]]) + test.expect(obj ~= nil, "bind_directive: object should be created") + end) + test.expect(ok, "bind_directive: :bind directive should not raise an error: " .. tostring(err)) + print("PASS: test_bind_directive") +end + +-- --------------------------------------------------------------------------- +-- test_theme_values: $key attributes resolve to theme colors +-- --------------------------------------------------------------------------- +local function test_theme_values() + ensure_fs() + local obj = filesystem.loadObjectFromLispString([[ + (StackView Name="Themed" BackgroundColor="$surface" + (TextBlock Name="Lbl" ForegroundColor="$text-primary" "Text")) + ]]) + test.expect(obj ~= nil, "theme: root should be created") + print("PASS: test_theme_values") +end + +-- --------------------------------------------------------------------------- +-- test_comments: Lisp semicolon comments are ignored +-- --------------------------------------------------------------------------- +local function test_comments() + ensure_fs() + local obj = filesystem.loadObjectFromLispString([[ + ; Root node + (StackView Name="WithComment" ; inline comment + (TextBlock Name="T" "text")) ; trailing comment + ]]) + test.expect(obj ~= nil, "comments: object should be created despite comments") + if not obj then return end + test.expect(obj:getName() == "WithComment", "comments: name should be WithComment") + print("PASS: test_comments") +end + +-- --------------------------------------------------------------------------- +-- test_application_lisp_readable: Application.lisp file can be read +-- --------------------------------------------------------------------------- +local function test_application_lisp_readable() + ensure_fs() + local src = filesystem.readTextFile("samples/Example/Screens/Application.lisp") + test.expect(src ~= nil and src ~= "", "application_lisp: file should be readable") + local has_screen = src:find('(Screen ', 1, true) ~= nil + local has_tabview = src:find('(TabView ', 1, true) ~= nil + local has_brandmark = src:find('Name="BrandMark"', 1, true) ~= nil + local has_bind = src:find('(:bind ', 1, true) ~= nil + local has_prefab = src:find('LayerPrefabPlaceholder', 1, true) ~= nil + local no_xml_entities = src:find('&', 1, true) == nil + test.expect(has_screen, "application_lisp: should contain (Screen ...") + test.expect(has_tabview, "application_lisp: should contain (TabView ...") + test.expect(has_brandmark, "application_lisp: should contain BrandMark element") + test.expect(has_bind, "application_lisp: should contain :bind directives") + test.expect(has_prefab, "application_lisp: should contain LayerPrefabPlaceholder") + test.expect(no_xml_entities, "application_lisp: should NOT contain & XML entities (use & directly)") + print("PASS: test_application_lisp_readable") +end + +-- --------------------------------------------------------------------------- +-- test_application_lisp_parity: Application.lisp produces the same key +-- named objects as Application.xml after parsing. +-- --------------------------------------------------------------------------- +local function test_application_lisp_parity() + ensure_fs() + + -- Key object names that must be present in both XML and Lisp trees. + local key_names = { + "Application", "Root", "NavBar", "BrandMark", "BrandIcon", "Brand", + "Body", "Hero", "HeroContent", "HeroImage", + "TabbedTechSection", "OrcaTabs", "OrcaTabBar", + "XmlTab", "LuaTab", "MoonTab", "DeployTab", + "FeatureSection", "GallerySection", "GalleryGrid", + "WorkflowSection", "TestimonialsSection", "CtaSection", "CtaButtons", + "CtaButtonPrimary", + } + + local src = filesystem.readTextFile("samples/Example/Screens/Application.lisp") + test.expect(src ~= nil and src ~= "", "parity: Application.lisp should be readable") + if not src or src == "" then return end + + local ok, obj_or_err = pcall(filesystem.loadObjectFromLispString, src) + test.expect(ok, "parity: Application.lisp should parse without error: " .. tostring(obj_or_err)) + if not ok then return end + local obj = obj_or_err + + for _, name in ipairs(key_names) do + local found = find_object(obj, name) + test.expect(found ~= nil, "parity: object '" .. name .. "' should exist in Lisp tree") + end + + -- CTA modal action: CtaButtonPrimary should exist and have a BackgroundColor set. + local cta = find_object(obj, "CtaButtonPrimary") + test.expect(cta ~= nil, "parity: CtaButtonPrimary should exist in Lisp tree") + + print("PASS: test_application_lisp_parity") +end + +-- --------------------------------------------------------------------------- +-- test_binding_translation: specific expression shapes round-trip correctly +-- --------------------------------------------------------------------------- +local function test_binding_translation() + ensure_fs() + + -- Each fixture is a minimal Lisp snippet whose binding must not crash. + local fixtures = { + -- STEP scalar binding + [[(StackView Name="T1" + (:bind Target="Node.Visible" (step 640 (bind "Node.ActualWidth")))) + ]], + -- IF / STEP / Vector2 + [[(StackView Name="T2" + (:bind Target="Node.HorizontalPadding" + (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8)))) + ]], + -- IF with string branches on column definition + [[(Grid Name="T3" Spacing="0" + (:bind Target="Grid.Columns" + (if (step 640 (bind "Node.ActualWidth")) "auto auto" "auto"))) + ]], + } + + for i, src in ipairs(fixtures) do + local ok, err = pcall(function() + local obj = filesystem.loadObjectFromLispString(src) + test.expect(obj ~= nil, "binding_translation[" .. i .. "]: object should be created") + end) + test.expect(ok, "binding_translation[" .. i .. "]: should not raise: " .. tostring(err)) + end + print("PASS: test_binding_translation") +end + +-- --------------------------------------------------------------------------- +-- Run all tests +-- --------------------------------------------------------------------------- +test_basic_elements() +test_text_content() +test_binding_attr() +test_bind_directive() +test_theme_values() +test_comments() +test_application_lisp_readable() +test_binding_translation() +test_application_lisp_parity() From 95f2e3937ca2f53faad8a9e606f1653be345eb18 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 19:17:11 +0000 Subject: [PATCH 02/11] Switch binding Token_Create to Lisp-compatible expressions and drop .xml file-loader registration Agent-Logs-Url: https://github.com/corepunch/orca/sessions/c1a79acc-0829-49bb-bc49-027298f41560 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- samples/Example/Screens/GetStartedPopup.lisp | 32 ++ source/core/property/property_program.c | 292 +++++++++++++++++++ source/filesystem/w_filesystem.c | 1 - tests/test_properties.c | 16 + 4 files changed, 340 insertions(+), 1 deletion(-) create mode 100644 samples/Example/Screens/GetStartedPopup.lisp diff --git a/samples/Example/Screens/GetStartedPopup.lisp b/samples/Example/Screens/GetStartedPopup.lisp new file mode 100644 index 00000000..6cb1c30c --- /dev/null +++ b/samples/Example/Screens/GetStartedPopup.lisp @@ -0,0 +1,32 @@ +(Popup Name="GetStartedPopup" BackgroundColor="#00000088" + (StackView Name="GetStartedPopupOverlay" + Direction="Vertical" + AlignItems="Center" + JustifyContent="Center" + Padding="40" + (StackView Name="GetStartedPopupCard" + Direction="Vertical" + Spacing="14" + Width="420" + BackgroundColor="$surface-deep" + Padding="24" + BorderWidth="2" + BorderColor="$accent" + (TextBlock Name="GetStartedPopupTitle" + Text="What ORCA gives you" + FontSize="20" + ForegroundColor="$text-primary") + (TextBlock Name="GetStartedPopupBody" + Text="XML keeps layout honest, Lua keeps behavior close, and MoonScript gives you a tighter way to grow the surface. The same codebase can ship to desktop, mobile, and the web." + FontSize="14" + ForegroundColor="$text-muted" + WordWrap="true") + (TextBlock Name="GetStartedPopupClose" + Text="Close" + FontSize="14" + Height="34" + BackgroundColor="$accent" + Padding="8" + TextHorizontalAlignment="Center" + TextVerticalAlignment="Center" + LeftButtonUp="{SendMessageAction Popup.ClosePopup ../../..}")))) diff --git a/source/core/property/property_program.c b/source/core/property/property_program.c index 6dbc4b8c..4a56e98c 100644 --- a/source/core/property/property_program.c +++ b/source/core/property/property_program.c @@ -358,9 +358,301 @@ _compile(lpcString_t code, lpcString_t filename) return token; } +struct _lisp_buf { + char *s; + size_t n; + size_t cap; +}; + +static bool_t +_lisp_buf_putn(struct _lisp_buf *b, lpcString_t s, size_t len) +{ + if (!len) return TRUE; + if (b->n + len + 1 > b->cap) { + size_t need = b->n + len + 1; + size_t cap = b->cap ? b->cap * 2 : 128; + if (cap < need) cap = need; + char *next = realloc(b->s, cap); + if (!next) return FALSE; + b->s = next; + b->cap = cap; + } + memcpy(b->s + b->n, s, len); + b->n += len; + b->s[b->n] = '\0'; + return TRUE; +} + +static bool_t +_lisp_buf_put(struct _lisp_buf *b, lpcString_t s) +{ + return _lisp_buf_putn(b, s, strlen(s)); +} + +static bool_t +_lisp_buf_putc(struct _lisp_buf *b, char c) +{ + return _lisp_buf_putn(b, &c, 1); +} + +static lpcString_t +_lisp_skip(lpcString_t p) +{ + while (*p && isspace((unsigned char)*p)) p++; + return p; +} + +static char * +_lisp_read_atom(lpcString_t *pp) +{ + lpcString_t p = _lisp_skip(*pp); + lpcString_t start = p; + while (*p && !isspace((unsigned char)*p) && *p != '(' && *p != ')') p++; + size_t len = (size_t)(p - start); + char *out = malloc(len + 1); + if (!out) return NULL; + memcpy(out, start, len); + out[len] = '\0'; + *pp = p; + return out; +} + +static char * +_lisp_parse_expr(lpcString_t *pp, bool_t *ok); + +static char * +_lisp_parse_string(lpcString_t *pp, bool_t *ok) +{ + lpcString_t p = _lisp_skip(*pp); + struct _lisp_buf out = {0}; + if (*p != '"') { + *ok = FALSE; + return NULL; + } + p++; + if (!_lisp_buf_putc(&out, '"')) { + *ok = FALSE; + free(out.s); + return NULL; + } + while (*p && *p != '"') { + char c = *p++; + if (c == '\\' && *p) { + char esc = *p++; + if (esc == '"' || esc == '\\') { + if (!_lisp_buf_putc(&out, '\\') || !_lisp_buf_putc(&out, esc)) { + *ok = FALSE; + free(out.s); + return NULL; + } + } else if (esc == 'n') { + if (!_lisp_buf_put(&out, "\\n")) { + *ok = FALSE; + free(out.s); + return NULL; + } + } else if (esc == 't') { + if (!_lisp_buf_put(&out, "\\t")) { + *ok = FALSE; + free(out.s); + return NULL; + } + } else { + if (!_lisp_buf_putc(&out, '\\') || !_lisp_buf_putc(&out, esc)) { + *ok = FALSE; + free(out.s); + return NULL; + } + } + } else { + if (c == '"' && !_lisp_buf_putc(&out, '\\')) { + *ok = FALSE; + free(out.s); + return NULL; + } + if (!_lisp_buf_putc(&out, c)) { + *ok = FALSE; + free(out.s); + return NULL; + } + } + } + if (*p != '"') { + *ok = FALSE; + free(out.s); + return NULL; + } + p++; + if (!_lisp_buf_putc(&out, '"')) { + *ok = FALSE; + free(out.s); + return NULL; + } + *pp = p; + return out.s ? out.s : strdup("\"\""); +} + +static char * +_lisp_parse_list(lpcString_t *pp, bool_t *ok) +{ + lpcString_t p = _lisp_skip(*pp); + struct _lisp_buf out = {0}; + if (*p != '(') { + *ok = FALSE; + return NULL; + } + p++; + + char *fn = _lisp_read_atom(&p); + if (!fn || !*fn) { + free(fn); + *ok = FALSE; + return NULL; + } + + if (!strcasecmp(fn, "bind")) { + free(fn); + char *arg = _lisp_parse_expr(&p, ok); + if (!*ok || !arg) { + free(arg); + return NULL; + } + size_t len = strlen(arg); + bool_t quoted = len >= 2 && arg[0] == '"' && arg[len - 1] == '"'; + if (!quoted) { + free(arg); + *ok = FALSE; + return NULL; + } + if (!_lisp_buf_putc(&out, '{') || + !_lisp_buf_putn(&out, arg + 1, len - 2) || + !_lisp_buf_putc(&out, '}')) { + free(arg); + free(out.s); + *ok = FALSE; + return NULL; + } + free(arg); + p = _lisp_skip(p); + if (*p != ')') { + *ok = FALSE; + free(out.s); + return NULL; + } + p++; + *pp = p; + return out.s; + } + + for (char *c = fn; *c; c++) { + *c = (char)toupper((unsigned char)*c); + } + if (!_lisp_buf_put(&out, fn) || !_lisp_buf_putc(&out, '(')) { + free(fn); + free(out.s); + *ok = FALSE; + return NULL; + } + free(fn); + + bool_t first = TRUE; + for (;;) { + p = _lisp_skip(p); + if (*p == ')') { + p++; + break; + } + if (!*p) { + free(out.s); + *ok = FALSE; + return NULL; + } + char *arg = _lisp_parse_expr(&p, ok); + if (!*ok || !arg) { + free(arg); + free(out.s); + return NULL; + } + if (!first && !_lisp_buf_put(&out, ", ")) { + free(arg); + free(out.s); + *ok = FALSE; + return NULL; + } + if (!_lisp_buf_put(&out, arg)) { + free(arg); + free(out.s); + *ok = FALSE; + return NULL; + } + free(arg); + first = FALSE; + } + + if (!_lisp_buf_putc(&out, ')')) { + free(out.s); + *ok = FALSE; + return NULL; + } + *pp = p; + return out.s; +} + +static char * +_lisp_parse_expr(lpcString_t *pp, bool_t *ok) +{ + lpcString_t p = _lisp_skip(*pp); + if (!*p) { + *ok = FALSE; + return NULL; + } + if (*p == '(') { + char *list = _lisp_parse_list(&p, ok); + if (!*ok) return NULL; + *pp = p; + return list; + } + if (*p == '"') { + char *s = _lisp_parse_string(&p, ok); + if (!*ok) return NULL; + *pp = p; + return s; + } + char *atom = _lisp_read_atom(&p); + if (!atom) { + *ok = FALSE; + return NULL; + } + *pp = p; + return atom; +} + +ORCA_API char * +PROP_TranslateLispBinding(lpcString_t code) +{ + lpcString_t p = _lisp_skip(code); + bool_t ok = TRUE; + if (*p != '(') { + return NULL; + } + char *translated = _lisp_parse_expr(&p, &ok); + p = _lisp_skip(p); + if (!ok || !translated || *p) { + free(translated); + return NULL; + } + return translated; +} + ORCA_API struct token* Token_Create(lpcString_t code) { struct token* _compile(lpcString_t code, lpcString_t filename); + char *lisp = PROP_TranslateLispBinding(code); + if (lisp) { + struct token *result = _compile(lisp, "binding"); + free(lisp); + return result; + } return _compile(code, "binding"); } diff --git a/source/filesystem/w_filesystem.c b/source/filesystem/w_filesystem.c index 8f4eae4c..b413fddc 100644 --- a/source/filesystem/w_filesystem.c +++ b/source/filesystem/w_filesystem.c @@ -391,7 +391,6 @@ static int f_loadObjectFromLispString(lua_State* L) { void on_filesystem_module_registered(lua_State* L) { - OBJ_RegisterFileLoader(".xml", _xml_file_loader); OBJ_RegisterFileLoader(".css", _css_file_loader); OBJ_RegisterFileLoader(".lisp", _lisp_file_loader); diff --git a/tests/test_properties.c b/tests/test_properties.c index 7d1eab7e..c12f09bb 100644 --- a/tests/test_properties.c +++ b/tests/test_properties.c @@ -686,6 +686,12 @@ static void test_runtime_token_create_arithmetic(void) { } } +static void test_runtime_token_create_lisp_if(void) { + WITH(struct token, prog, Token_Create("(if 1 10 20)"), Token_Release) { + EXPECT(prog != NULL); + } +} + static void test_runtime_run_int_constant(void) { WITH(struct Object, obj, make_rt_object(), destroy_object) { struct vm_register r = {0}; @@ -857,6 +863,14 @@ static void test_runtime_if_false_branch(void) { } } +static void test_runtime_if_lisp_branch(void) { + WITH(struct Object, obj, make_rt_object(), destroy_object) { + struct vm_register r = {0}; + RUN_PROG(obj, "(if 0 10 20)", &r); + EXPECT((int)r.value[0] == 20); + } +} + static void test_runtime_if_string_branch(void) { WITH(struct Object, obj, make_rt_object(), destroy_object) { struct vm_register r = {0}; @@ -1354,6 +1368,7 @@ int main(void) { DECL_TEST(test_runtime_token_create_float), DECL_TEST(test_runtime_token_create_string), DECL_TEST(test_runtime_token_create_arithmetic), + DECL_TEST(test_runtime_token_create_lisp_if), DECL_TEST(test_runtime_run_int_constant), DECL_TEST(test_runtime_run_float_constant), DECL_TEST(test_runtime_run_string_constant), @@ -1367,6 +1382,7 @@ int main(void) { DECL_TEST(test_runtime_string_concat_program), DECL_TEST(test_runtime_if_true_branch), DECL_TEST(test_runtime_if_false_branch), + DECL_TEST(test_runtime_if_lisp_branch), DECL_TEST(test_runtime_if_string_branch), DECL_TEST(test_object_refcount_direct), DECL_TEST(test_object_property_holds_reference), From 4fb6b664781eff6eb87b9b591489a43555cc5d2e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 19:19:46 +0000 Subject: [PATCH 03/11] Optimize Token_Create with Lisp fast-path check Agent-Logs-Url: https://github.com/corepunch/orca/sessions/c1a79acc-0829-49bb-bc49-027298f41560 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- source/core/property/property_program.c | 13 ++++++++----- 1 file changed, 8 insertions(+), 5 deletions(-) diff --git a/source/core/property/property_program.c b/source/core/property/property_program.c index 4a56e98c..ea565566 100644 --- a/source/core/property/property_program.c +++ b/source/core/property/property_program.c @@ -648,11 +648,14 @@ ORCA_API struct token* Token_Create(lpcString_t code) { struct token* _compile(lpcString_t code, lpcString_t filename); - char *lisp = PROP_TranslateLispBinding(code); - if (lisp) { - struct token *result = _compile(lisp, "binding"); - free(lisp); - return result; + lpcString_t p = _lisp_skip(code); + if (*p == '(') { + char *lisp = PROP_TranslateLispBinding(code); + if (lisp) { + struct token *result = _compile(lisp, "binding"); + free(lisp); + return result; + } } return _compile(code, "binding"); } From 0bdb6c702481f615e56f606b62d31a4127424c8b Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 19:25:45 +0000 Subject: [PATCH 04/11] Support inline key=value atoms in Lisp parser and switch numeric attrs to bare literals Agent-Logs-Url: https://github.com/corepunch/orca/sessions/26175dcf-d875-4d38-8302-ce38bf303643 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- samples/Example/Screens/Application.lisp | 224 +++++++++---------- samples/Example/Screens/GetStartedPopup.lisp | 20 +- source/filesystem/fs_lisp.c | 13 +- tests/test_lisp.lua | 12 +- 4 files changed, 142 insertions(+), 127 deletions(-) diff --git a/samples/Example/Screens/Application.lisp b/samples/Example/Screens/Application.lisp index c5f9d81a..e19daf06 100644 --- a/samples/Example/Screens/Application.lisp +++ b/samples/Example/Screens/Application.lisp @@ -4,45 +4,45 @@ ; Binding syntax: Key=(if (step 640 (bind "Node.ActualWidth")) "auto" "wide") ; Directive syntax: (:bind Target="Prop" expr) -(Screen Name="Application" Width="1280" Height="800" ClearColor="$surface" - (Grid Name="Root" Rows="64px auto" Spacing="0" +(Screen Name="Application" Width=1280 Height=800 ClearColor="$surface" + (Grid Name="Root" Rows="64px auto" Spacing=0 (StackView Name="NavBar" BackgroundColor="$panel-background" - Direction="Horizontal" Spacing="32" AlignItems="Center" - HorizontalPadding="40" - (StackView Name="BrandMark" Direction="Horizontal" Spacing="10" AlignItems="Center" + Direction="Horizontal" Spacing=32 AlignItems="Center" + HorizontalPadding=40 + (StackView Name="BrandMark" Direction="Horizontal" Spacing=10 AlignItems="Center" (ImageView Name="BrandIcon" Source="Example/Icons/blocks.svg?width=22&type=mask" - Width="22" Height="22" ForegroundColor="$accent") - (TextBlock Name="Brand" FontSize="20" ForegroundColor="$accent" + Width=22 Height=22 ForegroundColor="$accent") + (TextBlock Name="Brand" FontSize=20 ForegroundColor="$accent" "ORCA")) - (TextBlock Name="NavOverview" FontSize="14" ForegroundColor="$text-secondary" + (TextBlock Name="NavOverview" FontSize=14 ForegroundColor="$text-secondary" "Overview") - (TextBlock Name="NavFeatures" FontSize="14" ForegroundColor="$text-secondary" + (TextBlock Name="NavFeatures" FontSize=14 ForegroundColor="$text-secondary" "Features") - (TextBlock Name="NavGallery" FontSize="14" ForegroundColor="$text-secondary" + (TextBlock Name="NavGallery" FontSize=14 ForegroundColor="$text-secondary" "Gallery") - (TextBlock Name="NavDocs" FontSize="14" ForegroundColor="$text-secondary" + (TextBlock Name="NavDocs" FontSize=14 ForegroundColor="$text-secondary" "Docs")) - (StackView Name="Body" Direction="Vertical" Spacing="40" OverflowY="Scroll" - ClipChildren="true" Padding="40" BackgroundColor="$surface-deep" + (StackView Name="Body" Direction="Vertical" Spacing=40 OverflowY="Scroll" + ClipChildren="true" Padding=40 BackgroundColor="$surface-deep" (:bind Target="Node.HorizontalPadding" (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) - (Grid Name="Hero" Columns="auto auto" Spacing="24" BackgroundColor="$surface" + (Grid Name="Hero" Columns="auto auto" Spacing=24 BackgroundColor="$surface" (:bind Target="Node.Visible" (step 640 (bind "Node.ActualWidth"))) (:bind Target="Grid.Columns" (if (step 640 (bind "Node.ActualWidth")) "auto auto" "auto")) - (StackView Name="HeroContent" Direction="Vertical" Spacing="0" Padding="16" - (TextBlock Name="HeadlineLine1" FontSize="48" ForegroundColor="$text-primary" + (StackView Name="HeroContent" Direction="Vertical" Spacing=0 Padding=16 + (TextBlock Name="HeadlineLine1" FontSize=48 ForegroundColor="$text-primary" "Build stunning") - (TextBlock Name="HeadlineLine2" FontSize="48" ForegroundColor="$accent" + (TextBlock Name="HeadlineLine2" FontSize=48 ForegroundColor="$accent" "UI in real-time.") - (Node2D Name="HeadlineSpacer" Height="20") - (TextBlock Name="SubHeadline" FontSize="15" ForegroundColor="$text-muted" + (Node2D Name="HeadlineSpacer" Height=20) + (TextBlock Name="SubHeadline" FontSize=15 ForegroundColor="$text-muted" WordWrap="true" "A modern UI engine for games and native apps. Compose layouts from grids, stacks, text, and images — all rendered at 60 fps.") - (Node2D Name="MetricSpacer" Height="40") - (Grid Name="Metrics" Columns="auto auto auto" Spacing="16" + (Node2D Name="MetricSpacer" Height=40) + (Grid Name="Metrics" Columns="auto auto auto" Spacing=16 (LayerPrefabPlaceholder Name="MetricUsers" PlaceholderTemplate="Example/Prefabs/Mertic" Card.Title="2.4 M" @@ -60,16 +60,16 @@ Card.PrimaryColor="$accent-blue"))) (ImageView Name="HeroImage" Source="Example/Images/people" Stretch="UniformToFill")) - (StackView Name="TabbedTechSection" Direction="Vertical" Spacing="20" - BackgroundColor="$surface" Padding="40" + (StackView Name="TabbedTechSection" Direction="Vertical" Spacing=20 + BackgroundColor="$surface" Padding=40 (:bind Target="Node.HorizontalPadding" (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) (TextBlock Name="TabbedTechHeading" Text="Build the UI, not the ceremony" - FontSize="28" ForegroundColor="$text-primary") + FontSize=28 ForegroundColor="$text-primary") (TextBlock Name="TabbedTechSubheading" Text="A clean XML surface, Lua where things move, MoonScript when the app grows, and one UI that can travel across targets." - FontSize="14" ForegroundColor="$text-muted") - (Grid Name="OrcaSignals" Columns="auto auto auto" Spacing="14" + FontSize=14 ForegroundColor="$text-muted") + (Grid Name="OrcaSignals" Columns="auto auto auto" Spacing=14 (:bind Target="Grid.Columns" (if (step 640 (bind "Node.ActualWidth")) "auto auto auto" "auto auto")) (LayerPrefabPlaceholder Name="SignalXml" @@ -109,77 +109,77 @@ Card.Body="Same UI, larger ambitions." Card.PrimaryColor="$text-muted")) (TabView Name="OrcaTabs" SelectedValue="xml" - (TabBar Name="OrcaTabBar" Height="44" BackgroundColor="$panel-background" - Padding="8 8 8 0" Spacing="12" - (Tab Name="XmlTab" Value="xml" Width="140" "XML") - (Tab Name="LuaTab" Value="lua" Width="140" "Lua") - (Tab Name="MoonTab" Value="moon" Width="140" "MoonScript") - (Tab Name="DeployTab" Value="deploy" Width="140" "Deploy")) - (StackView Name="xml" Direction="Vertical" Spacing="24" - BackgroundColor="$surface-deep" Padding="32" + (TabBar Name="OrcaTabBar" Height=44 BackgroundColor="$panel-background" + Padding="8 8 8 0" Spacing=12 + (Tab Name="XmlTab" Value="xml" Width=140 "XML") + (Tab Name="LuaTab" Value="lua" Width=140 "Lua") + (Tab Name="MoonTab" Value="moon" Width=140 "MoonScript") + (Tab Name="DeployTab" Value="deploy" Width=140 "Deploy")) + (StackView Name="xml" Direction="Vertical" Spacing=24 + BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="XmlPanelHeader" PlaceholderTemplate="Example/Prefabs/TabPanelHeader" Card.Icon="Example/Icons/grid.svg?width=26&type=mask" Card.Title="XML reads like a blueprint" Card.PrimaryColor="$accent") - (Grid Name="XmlGrid" Columns="auto auto" Spacing="20" - (StackView Name="XmlCopy" Direction="Vertical" Spacing="14" + (Grid Name="XmlGrid" Columns="auto auto" Spacing=20 + (StackView Name="XmlCopy" Direction="Vertical" Spacing=14 (TextBlock Name="XmlBody" Text="The screen stays legible at a glance, which makes it easy to scan, compare, and reshape as the design evolves." - FontSize="14" ForegroundColor="$text-muted" WordWrap="true") - (StackView Name="XmlNotes" Direction="Vertical" Spacing="8" - BackgroundColor="$panel-background" Padding="20" + FontSize=14 ForegroundColor="$text-muted" WordWrap="true") + (StackView Name="XmlNotes" Direction="Vertical" Spacing=8 + BackgroundColor="$panel-background" Padding=20 (TextBlock Name="XmlNote1" Text="• Grids and stacks stay visible in the source." - FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + FontSize=13 ForegroundColor="$text-secondary" WordWrap="true") (TextBlock Name="XmlNote2" Text="• Spacing, padding, and hierarchy remain explicit." - FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + FontSize=13 ForegroundColor="$text-secondary" WordWrap="true") (TextBlock Name="XmlNote3" Text="• The shape of the screen is easy to read before it ever runs." - FontSize="13" ForegroundColor="$text-secondary" WordWrap="true"))) - (StackView Name="XmlModel" Direction="Vertical" Spacing="10" - BackgroundColor="$card-background" Padding="20" + FontSize=13 ForegroundColor="$text-secondary" WordWrap="true"))) + (StackView Name="XmlModel" Direction="Vertical" Spacing=10 + BackgroundColor="$card-background" Padding=20 (TextBlock Name="XmlModelHeading" Text="The structure stays honest" - FontSize="16" ForegroundColor="$text-primary") + FontSize=16 ForegroundColor="$text-primary") (LayerPrefabPlaceholder Name="XmlModelLine1" PlaceholderTemplate="Example/Prefabs/XmlModelNode" Card.Icon="Example/Icons/monitor.svg?width=20&type=mask" Card.Title="Screen" - PaddingLeft="0" + PaddingLeft=0 Card.PrimaryColor="$accent") (LayerPrefabPlaceholder Name="XmlModelLine2" PlaceholderTemplate="Example/Prefabs/XmlModelNode" Card.Icon="Example/Icons/panel-top.svg?width=20&type=mask" Card.Title="TabView" - PaddingLeft="16" + PaddingLeft=16 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine3" PlaceholderTemplate="Example/Prefabs/XmlModelNode" Card.Icon="Example/Icons/grid.svg?width=20&type=mask" Card.Title="Grid" - PaddingLeft="32" + PaddingLeft=32 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine4" PlaceholderTemplate="Example/Prefabs/XmlModelNode" Card.Icon="Example/Icons/layers.svg?width=20&type=mask" Card.Title="StackView" - PaddingLeft="32" + PaddingLeft=32 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine5" PlaceholderTemplate="Example/Prefabs/XmlModelNode" Card.Icon="Example/Icons/image.svg?width=20&type=mask" Card.Title="ImageView" - PaddingLeft="48" + PaddingLeft=48 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine6" PlaceholderTemplate="Example/Prefabs/XmlModelNode" Card.Icon="Example/Icons/type.svg?width=20&type=mask" Card.Title="TextBlock" - PaddingLeft="48" + PaddingLeft=48 Card.PrimaryColor="$text-secondary")))) - (StackView Name="lua" Direction="Vertical" Spacing="24" - BackgroundColor="$surface-deep" Padding="32" + (StackView Name="lua" Direction="Vertical" Spacing=24 + BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="LuaPanelHeader" PlaceholderTemplate="Example/Prefabs/TabPanelHeader" Card.Icon="Example/Icons/code.svg?width=26&type=mask" @@ -187,8 +187,8 @@ Card.PrimaryColor="$accent-blue") (TextBlock Name="LuaBody" Text="The parts that move stay beside the screen that uses them, so interaction and state feel close and easy to change." - FontSize="14" ForegroundColor="$text-muted" WordWrap="true") - (Grid Name="LuaGrid" Columns="auto auto auto" Spacing="16" + FontSize=14 ForegroundColor="$text-muted" WordWrap="true") + (Grid Name="LuaGrid" Columns="auto auto auto" Spacing=16 (LayerPrefabPlaceholder Name="LuaCard1" PlaceholderTemplate="Example/Prefabs/IconCard" Card.Icon="Example/Icons/mouse-pointer-click.svg?width=40&type=mask" @@ -210,43 +210,43 @@ Card.Body="Let Lua connect the moving pieces while XML keeps the hierarchy in view." Card.PrimaryColor="$accent-green" Card.IconBackground="$icon-bg-green"))) - (StackView Name="moon" Direction="Vertical" Spacing="24" - BackgroundColor="$surface-deep" Padding="32" + (StackView Name="moon" Direction="Vertical" Spacing=24 + BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="MoonPanelHeader" PlaceholderTemplate="Example/Prefabs/TabPanelHeader" Card.Icon="Example/Icons/moon.svg?width=26&type=mask" Card.Title="MoonScript trims the noise" Card.PrimaryColor="$text-secondary") - (Grid Name="MoonGrid" Columns="auto auto" Spacing="20" - (StackView Name="MoonCopy" Direction="Vertical" Spacing="14" + (Grid Name="MoonGrid" Columns="auto auto" Spacing=20 + (StackView Name="MoonCopy" Direction="Vertical" Spacing=14 (TextBlock Name="MoonBody" Text="MoonScript keeps larger app code compact and expressive, while still compiling to the same Lua runtime." - FontSize="14" ForegroundColor="$text-muted" WordWrap="true") - (StackView Name="MoonNotes" Direction="Vertical" Spacing="8" - BackgroundColor="$panel-background" Padding="20" + FontSize=14 ForegroundColor="$text-muted" WordWrap="true") + (StackView Name="MoonNotes" Direction="Vertical" Spacing=8 + BackgroundColor="$panel-background" Padding=20 (TextBlock Name="MoonNote1" Text="• Helpful when app modules get a little larger." - FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + FontSize=13 ForegroundColor="$text-secondary" WordWrap="true") (TextBlock Name="MoonNote2" Text="• The syntax stays concise, but the runtime stays Lua." - FontSize="13" ForegroundColor="$text-secondary" WordWrap="true") + FontSize=13 ForegroundColor="$text-secondary" WordWrap="true") (TextBlock Name="MoonNote3" Text="• It pairs naturally with XML screens and Lua helpers." - FontSize="13" ForegroundColor="$text-secondary" WordWrap="true"))) - (StackView Name="MoonModel" Direction="Vertical" Spacing="10" - BackgroundColor="$card-background" Padding="20" + FontSize=13 ForegroundColor="$text-secondary" WordWrap="true"))) + (StackView Name="MoonModel" Direction="Vertical" Spacing=10 + BackgroundColor="$card-background" Padding=20 (TextBlock Name="MoonModelHeading" Text="Same runtime, less ceremony" - FontSize="16" ForegroundColor="$text-primary") + FontSize=16 ForegroundColor="$text-primary") (TextBlock Name="MoonModelLine1" Text="Cleaner class bodies" - FontSize="14" ForegroundColor="$text-secondary") + FontSize=14 ForegroundColor="$text-secondary") (TextBlock Name="MoonModelLine2" Text="Readable app controllers" - FontSize="14" ForegroundColor="$text-secondary") + FontSize=14 ForegroundColor="$text-secondary") (TextBlock Name="MoonModelLine3" Text="Plain Lua at runtime" - FontSize="14" ForegroundColor="$text-secondary") + FontSize=14 ForegroundColor="$text-secondary") (TextBlock Name="MoonModelLine4" Text="Comfortable beside XML layouts" - FontSize="14" ForegroundColor="$text-secondary")))) - (StackView Name="deploy" Direction="Vertical" Spacing="24" - BackgroundColor="$surface-deep" Padding="32" + FontSize=14 ForegroundColor="$text-secondary")))) + (StackView Name="deploy" Direction="Vertical" Spacing=24 + BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="DeployPanelHeader" PlaceholderTemplate="Example/Prefabs/TabPanelHeader" Card.Icon="Example/Icons/globe.svg?width=26&type=mask" @@ -254,8 +254,8 @@ Card.PrimaryColor="$accent-green") (TextBlock Name="DeployBody" Text="Build once, tune the spacing, and carry the same design language across desktop, mobile, and web-oriented targets." - FontSize="14" ForegroundColor="$text-muted" WordWrap="true") - (Grid Name="DeployGrid" Columns="auto auto auto" Spacing="16" + FontSize=14 ForegroundColor="$text-muted" WordWrap="true") + (Grid Name="DeployGrid" Columns="auto auto auto" Spacing=16 (LayerPrefabPlaceholder Name="DeployCard1" PlaceholderTemplate="Example/Prefabs/IconCard" Card.Icon="Example/Icons/monitor.svg?width=40&type=mask" @@ -277,7 +277,7 @@ Card.Body="The same screen model can carry into browser workflows without being rewritten." Card.PrimaryColor="$accent-blue" Card.IconBackground="$icon-bg-blue")) - (Grid Name="DeployImages" Columns="auto auto" Spacing="12" + (Grid Name="DeployImages" Columns="auto auto" Spacing=12 (LayerPrefabPlaceholder Name="DeployImage1" PlaceholderTemplate="Example/Prefabs/ImageCaptionCard" Card.Image="Example/Images/orca-tab-city" @@ -286,18 +286,18 @@ PlaceholderTemplate="Example/Prefabs/ImageCaptionCard" Card.Image="Example/Images/orca-tab-lights" Card.Title="Night light")) - (StackView Name="DeployCommandLink" Direction="Horizontal" Spacing="12" - AlignItems="Center" BackgroundColor="$panel-background" Padding="16" + (StackView Name="DeployCommandLink" Direction="Horizontal" Spacing=12 + AlignItems="Center" BackgroundColor="$panel-background" Padding=16 (ImageView Name="DeployCommandIcon" Source="Example/Icons/monitor.svg?width=20&type=mask" - Width="20" Height="20" ForegroundColor="$accent-green") - (StackView Name="DeployCommandCopy" Direction="Vertical" Spacing="2" + Width=20 Height=20 ForegroundColor="$accent-green") + (StackView Name="DeployCommandCopy" Direction="Vertical" Spacing=2 (TextBlock Name="DeployCommandLabel" Text="Desktop build" - FontSize="13" ForegroundColor="$text-secondary") + FontSize=13 ForegroundColor="$text-secondary") (TextBlock Name="DeployCommandValue" Text="make clean && make -j8 && build/bin/orca samples/Example" - FontSize="14" ForegroundColor="$text-primary" WordWrap="true")))))) - (Grid Name="FeatureSection" Columns="auto auto auto auto" Spacing="20" + FontSize=14 ForegroundColor="$text-primary" WordWrap="true")))))) + (Grid Name="FeatureSection" Columns="auto auto auto auto" Spacing=20 (LayerPrefabPlaceholder Name="FeatureXmlCard" PlaceholderTemplate="Example/Prefabs/FeatureImageCard" Card.Icon="Example/Icons/layers.svg?width=40&type=mask" @@ -322,16 +322,16 @@ Card.Title="Fast iteration" Card.Body="Edit, reload, and refine quickly without losing the overall shape of the interface." Card.PrimaryColor="$accent-amber")) - (StackView Name="GallerySection" Direction="Vertical" Spacing="20" - BackgroundColor="$surface" Padding="40" + (StackView Name="GallerySection" Direction="Vertical" Spacing=20 + BackgroundColor="$surface" Padding=40 (:bind Target="Node.HorizontalPadding" (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) (TextBlock Name="GalleryHeading" Text="Gallery" - FontSize="28" ForegroundColor="$text-primary") + FontSize=28 ForegroundColor="$text-primary") (TextBlock Name="GallerySubheading" Text="Screenshots and scenes rendered entirely by the ORCA engine." - FontSize="14" ForegroundColor="$text-muted") - (Grid Name="GalleryGrid" Columns="auto auto" Spacing="16" + FontSize=14 ForegroundColor="$text-muted") + (Grid Name="GalleryGrid" Columns="auto auto" Spacing=16 (LayerPrefabPlaceholder Name="GalleryCard1" PlaceholderTemplate="Example/Prefabs/GalleryCard" Card.Image="Example/Images/lake" @@ -342,46 +342,46 @@ Card.Image="Example/Images/peacock" Card.Title="Peacock" Card.Subtitle="High-fidelity texture sampling with anisotropic filtering."))) - (StackView Name="WorkflowSection" Direction="Vertical" Spacing="24" - BackgroundColor="$surface-deep" Padding="40" + (StackView Name="WorkflowSection" Direction="Vertical" Spacing=24 + BackgroundColor="$surface-deep" Padding=40 (:bind Target="Node.HorizontalPadding" (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) (TextBlock Name="WorkflowHeading" Text="How It Works" - FontSize="28" ForegroundColor="$text-primary") + FontSize=28 ForegroundColor="$text-primary") (TextBlock Name="WorkflowSubheading" Text="From XML layout to a live rendered scene in four simple steps." - FontSize="14" ForegroundColor="$text-muted") - (Grid Name="WorkflowSteps" Columns="auto auto auto auto" Spacing="16" + FontSize=14 ForegroundColor="$text-muted") + (Grid Name="WorkflowSteps" Columns="auto auto auto auto" Spacing=16 (:bind Target="Grid.Columns" (if (step 640 (bind "Node.ActualWidth")) "auto auto auto auto" "auto auto")) (LayerPrefabPlaceholder Name="Step1" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title="01" Card.Subtitle="Define Layout" + Card.Title=01 Card.Subtitle="Define Layout" Card.PrimaryColor="$accent" Card.Body="Write your screen as an XML file with familiar Grid and StackView containers.") (LayerPrefabPlaceholder Name="Step2" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title="02" Card.Subtitle="Load Assets" + Card.Title=02 Card.Subtitle="Load Assets" Card.PrimaryColor="$accent-blue" Card.Body="Images, fonts, and data files are streamed from packages at runtime.") (LayerPrefabPlaceholder Name="Step3" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title="03" Card.Subtitle="Script Logic" + Card.Title=03 Card.Subtitle="Script Logic" Card.PrimaryColor="$accent-green" Card.Body="Attach Lua scripts to any component for interactivity and data binding.") (LayerPrefabPlaceholder Name="Step4" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title="04" Card.Subtitle="Ship" + Card.Title=04 Card.Subtitle="Ship" Card.PrimaryColor="$accent-amber" Card.Body="Deploy to desktop, mobile, or WebGL with a single build command."))) - (StackView Name="TestimonialsSection" Direction="Vertical" Spacing="24" - BackgroundColor="$surface" Padding="40" + (StackView Name="TestimonialsSection" Direction="Vertical" Spacing=24 + BackgroundColor="$surface" Padding=40 (:bind Target="Node.HorizontalPadding" (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8))) (TextBlock Name="TestimonialsHeading" Text="What Developers Say" - FontSize="28" ForegroundColor="$text-primary") - (Grid Name="TestimonialsGrid" Columns="auto auto auto" Spacing="20" + FontSize=28 ForegroundColor="$text-primary") + (Grid Name="TestimonialsGrid" Columns="auto auto auto" Spacing=20 (LayerPrefabPlaceholder Name="Quote1" PlaceholderTemplate="Example/Prefabs/Quote" Card.PrimaryColor="$accent" @@ -397,26 +397,26 @@ Card.PrimaryColor="$accent-blue" Card.Title="— Jordan M." Card.Subtitle="Game Developer" Card.Body="Sub-millisecond frame times even on mid-range hardware. We ditched our old HTML renderer the day we tried ORCA."))) - (StackView Name="CtaSection" Direction="Vertical" Spacing="24" - BackgroundColor="$panel-background" Padding="60" AlignItems="Center" + (StackView Name="CtaSection" Direction="Vertical" Spacing=24 + BackgroundColor="$panel-background" Padding=60 AlignItems="Center" (:bind Target="Node.HorizontalPadding" (if (step 640 (bind "Node.ActualWidth")) (vector2 60 60) (vector2 16 16))) (TextBlock Name="CtaHeading" Text="Ready to build something great?" - FontSize="36" ForegroundColor="$text-primary" + FontSize=36 ForegroundColor="$text-primary" TextHorizontalAlignment="Center") (TextBlock Name="CtaBody" Text="Download ORCA, open a sample project, and have your first screen running in minutes." - FontSize="15" ForegroundColor="$text-muted" WordWrap="true" + FontSize=15 ForegroundColor="$text-muted" WordWrap="true" TextHorizontalAlignment="Center") - (StackView Name="CtaButtons" Direction="Horizontal" Spacing="16" + (StackView Name="CtaButtons" Direction="Horizontal" Spacing=16 (TextBlock Name="CtaButtonPrimary" Text="Get Started" - FontSize="14" ForegroundColor="$white" - BackgroundColor="$accent" Padding="16" Width="140" Height="44" + FontSize=14 ForegroundColor="$white" + BackgroundColor="$accent" Padding=16 Width=140 Height=44 TextHorizontalAlignment="Center" TextVerticalAlignment="Center" LeftButtonUp="{ShowModalAction Example/Screens/GetStartedPopup}") (TextBlock Name="CtaButtonSecondary" Text="Learn more" - FontSize="14" ForegroundColor="$text-secondary" - BackgroundColor="$card-background" Padding="16" - Width="140" Height="44" + FontSize=14 ForegroundColor="$text-secondary" + BackgroundColor="$card-background" Padding=16 + Width=140 Height=44 TextHorizontalAlignment="Center" TextVerticalAlignment="Center")))))) diff --git a/samples/Example/Screens/GetStartedPopup.lisp b/samples/Example/Screens/GetStartedPopup.lisp index 6cb1c30c..9a857ac4 100644 --- a/samples/Example/Screens/GetStartedPopup.lisp +++ b/samples/Example/Screens/GetStartedPopup.lisp @@ -3,30 +3,30 @@ Direction="Vertical" AlignItems="Center" JustifyContent="Center" - Padding="40" + Padding=40 (StackView Name="GetStartedPopupCard" Direction="Vertical" - Spacing="14" - Width="420" + Spacing=14 + Width=420 BackgroundColor="$surface-deep" - Padding="24" - BorderWidth="2" + Padding=24 + BorderWidth=2 BorderColor="$accent" (TextBlock Name="GetStartedPopupTitle" Text="What ORCA gives you" - FontSize="20" + FontSize=20 ForegroundColor="$text-primary") (TextBlock Name="GetStartedPopupBody" Text="XML keeps layout honest, Lua keeps behavior close, and MoonScript gives you a tighter way to grow the surface. The same codebase can ship to desktop, mobile, and the web." - FontSize="14" + FontSize=14 ForegroundColor="$text-muted" WordWrap="true") (TextBlock Name="GetStartedPopupClose" Text="Close" - FontSize="14" - Height="34" + FontSize=14 + Height=34 BackgroundColor="$accent" - Padding="8" + Padding=8 TextHorizontalAlignment="Center" TextVerticalAlignment="Center" LeftButtonUp="{SendMessageAction Popup.ClosePopup ../../..}")))) diff --git a/source/filesystem/fs_lisp.c b/source/filesystem/fs_lisp.c index fbcdf3b6..52c1a311 100644 --- a/source/filesystem/fs_lisp.c +++ b/source/filesystem/fs_lisp.c @@ -385,7 +385,18 @@ parse_element_body(struct lsp_lex *l, struct lisp_node *node) /* --- Atom: key="val", key=(binding), or bare key (unquoted value) --- */ if (l->tok == TOK_ATOM) { size_t alen = strlen(l->str); - if (alen > 1 && l->str[alen - 1] == '=') { + const char *eq = strchr(l->str, '='); + if (eq && eq > l->str && eq[1] != '\0') { + /* Inline atom assignment, e.g. Width=1280 */ + char *key = strndup(l->str, (size_t)(eq - l->str)); + struct lisp_attr *a = calloc(1, sizeof(*a)); + a->key = key; + a->val = strdup(eq + 1); + *attr_tail = a; + attr_tail = &a->next; + lsp_next(l); + + } else if (alen > 1 && l->str[alen - 1] == '=') { char *key = strndup(l->str, alen - 1); lsp_next(l); diff --git a/tests/test_lisp.lua b/tests/test_lisp.lua index bf8142a5..fbc56fba 100644 --- a/tests/test_lisp.lua +++ b/tests/test_lisp.lua @@ -44,7 +44,7 @@ end local function test_basic_elements() ensure_fs() local obj = filesystem.loadObjectFromLispString([[ - (Screen Name="TestScreen" Width="100" Height="100" + (Screen Name="TestScreen" Width=100 Height=100 (StackView Name="Inner" Direction="Vertical")) ]]) test.expect(obj ~= nil, "basic: root object should be created") @@ -62,7 +62,7 @@ local function test_text_content() ensure_fs() local obj = filesystem.loadObjectFromLispString([[ (StackView Name="Row" - (TextBlock Name="Label" FontSize="14" ForegroundColor="$text-primary" + (TextBlock Name="Label" FontSize=14 ForegroundColor="$text-primary" "Hello, world")) ]]) test.expect(obj ~= nil, "text: root should exist") @@ -84,7 +84,7 @@ local function test_binding_attr() ensure_fs() local ok, err = pcall(function() local obj = filesystem.loadObjectFromLispString([[ - (Grid Name="TestGrid" Spacing="0" + (Grid Name="TestGrid" Spacing=0 Columns=(if (step 640 (bind "Node.ActualWidth")) "auto auto" "auto")) ]]) test.expect(obj ~= nil, "binding_attr: object should be created") @@ -151,12 +151,16 @@ local function test_application_lisp_readable() local has_brandmark = src:find('Name="BrandMark"', 1, true) ~= nil local has_bind = src:find('(:bind ', 1, true) ~= nil local has_prefab = src:find('LayerPrefabPlaceholder', 1, true) ~= nil + local has_numeric_atoms = src:find('Width=1280', 1, true) ~= nil + local no_numeric_strings = src:find('Width="1280"', 1, true) == nil local no_xml_entities = src:find('&', 1, true) == nil test.expect(has_screen, "application_lisp: should contain (Screen ...") test.expect(has_tabview, "application_lisp: should contain (TabView ...") test.expect(has_brandmark, "application_lisp: should contain BrandMark element") test.expect(has_bind, "application_lisp: should contain :bind directives") test.expect(has_prefab, "application_lisp: should contain LayerPrefabPlaceholder") + test.expect(has_numeric_atoms, "application_lisp: numeric properties should use bare atoms (e.g. Width=1280)") + test.expect(no_numeric_strings, "application_lisp: numeric properties should not use quoted numeric strings") test.expect(no_xml_entities, "application_lisp: should NOT contain & XML entities (use & directly)") print("PASS: test_application_lisp_readable") end @@ -218,7 +222,7 @@ local function test_binding_translation() (if (step 640 (bind "Node.ActualWidth")) (vector2 40 40) (vector2 8 8)))) ]], -- IF with string branches on column definition - [[(Grid Name="T3" Spacing="0" + [[(Grid Name="T3" Spacing=0 (:bind Target="Grid.Columns" (if (step 640 (bind "Node.ActualWidth")) "auto auto" "auto"))) ]], From 4446bdd8630aa46b85df055e0a9877567171d4e4 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Fri, 22 May 2026 19:28:44 +0000 Subject: [PATCH 05/11] Accept key=value atom attributes in Lisp parser and preserve workflow step titles Agent-Logs-Url: https://github.com/corepunch/orca/sessions/26175dcf-d875-4d38-8302-ce38bf303643 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- samples/Example/Screens/Application.lisp | 8 ++++---- source/filesystem/fs_lisp.c | 18 ++++++++++++++++++ 2 files changed, 22 insertions(+), 4 deletions(-) diff --git a/samples/Example/Screens/Application.lisp b/samples/Example/Screens/Application.lisp index e19daf06..b132d5b0 100644 --- a/samples/Example/Screens/Application.lisp +++ b/samples/Example/Screens/Application.lisp @@ -357,22 +357,22 @@ "auto auto auto auto" "auto auto")) (LayerPrefabPlaceholder Name="Step1" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title=01 Card.Subtitle="Define Layout" + Card.Title="01" Card.Subtitle="Define Layout" Card.PrimaryColor="$accent" Card.Body="Write your screen as an XML file with familiar Grid and StackView containers.") (LayerPrefabPlaceholder Name="Step2" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title=02 Card.Subtitle="Load Assets" + Card.Title="02" Card.Subtitle="Load Assets" Card.PrimaryColor="$accent-blue" Card.Body="Images, fonts, and data files are streamed from packages at runtime.") (LayerPrefabPlaceholder Name="Step3" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title=03 Card.Subtitle="Script Logic" + Card.Title="03" Card.Subtitle="Script Logic" Card.PrimaryColor="$accent-green" Card.Body="Attach Lua scripts to any component for interactivity and data binding.") (LayerPrefabPlaceholder Name="Step4" PlaceholderTemplate="Example/Prefabs/WorkflowStep" - Card.Title=04 Card.Subtitle="Ship" + Card.Title="04" Card.Subtitle="Ship" Card.PrimaryColor="$accent-amber" Card.Body="Deploy to desktop, mobile, or WebGL with a single build command."))) (StackView Name="TestimonialsSection" Direction="Vertical" Spacing=24 diff --git a/source/filesystem/fs_lisp.c b/source/filesystem/fs_lisp.c index 52c1a311..d1ca5120 100644 --- a/source/filesystem/fs_lisp.c +++ b/source/filesystem/fs_lisp.c @@ -389,9 +389,27 @@ parse_element_body(struct lsp_lex *l, struct lisp_node *node) if (eq && eq > l->str && eq[1] != '\0') { /* Inline atom assignment, e.g. Width=1280 */ char *key = strndup(l->str, (size_t)(eq - l->str)); + if (!key) { + Con_Error("Lisp: out of memory parsing attribute key (line %d)", l->line); + lsp_next(l); + continue; + } struct lisp_attr *a = calloc(1, sizeof(*a)); + if (!a) { + Con_Error("Lisp: out of memory creating attribute node (line %d)", l->line); + free(key); + lsp_next(l); + continue; + } a->key = key; a->val = strdup(eq + 1); + if (!a->val) { + Con_Error("Lisp: out of memory parsing attribute value (line %d)", l->line); + free(a->key); + free(a); + lsp_next(l); + continue; + } *attr_tail = a; attr_tail = &a->next; lsp_next(l); From 991913a9c071fdfaebf5be43ddc4c8e152841e66 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 11:55:18 +0000 Subject: [PATCH 06/11] refactor: route binding evaluation through Binding object message Agent-Logs-Url: https://github.com/corepunch/orca/sessions/9cff1947-d103-4217-8c22-747a887980c8 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- source/core/components/Trigger.c | 38 ++++++++++++++++++++++++++ source/core/core.cgen | 8 ++++++ source/core/core.h | 9 ++++++ source/core/core_export.c | 11 ++++++++ source/core/core_properties.h | 7 ++++- source/core/property/property_core.c | 11 +++++++- source/core/property/property_events.c | 21 +++++++++++++- 7 files changed, 102 insertions(+), 3 deletions(-) diff --git a/source/core/components/Trigger.c b/source/core/components/Trigger.c index 5951b8f2..6105c3e9 100644 --- a/source/core/components/Trigger.c +++ b/source/core/components/Trigger.c @@ -64,6 +64,36 @@ _BindingGetIntProperty(struct Object *obj, lpcString_t name, int fallback) return *(int*)PROP_GetValue(prop); } +static bool_t +_BindingEvaluate(struct Property *property, struct Binding *binding) +{ + if (!property || !binding) { + return FALSE; + } + if (!binding->token) { + return TRUE; + } + if (binding->updateFrame == core.frame) { + return TRUE; + } + binding->updateFrame = core.frame; + + struct vm_register r = { 0 }; + if (!OBJ_RunProgram(property->object, binding->token, &r)) { + Con_Error("Binding update failed while running program for %s/%s", + OBJ_GetName(property->object), + property->pdesc->Name); + return FALSE; + } + if (!PROP_Import(property, &r)) { + Con_Error("Binding update failed while importing into %s/%s", + OBJ_GetName(property->object), + property->pdesc->Name); + return FALSE; + } + return TRUE; +} + static LRESULT _BindingCompileToProperty(struct Object *hObject, @@ -168,6 +198,14 @@ HANDLER(BindingExpression, Binding, Compile) FALSE); } +HANDLER(Binding, Binding, Evaluate) +{ + struct Property *property = pEvaluate && pEvaluate->Property + ? pEvaluate->Property + : pBinding->property; + return _BindingEvaluate(property, pBinding); +} + HANDLER(Trigger, Object, PropertyChanged) { if (!pPropertyChanged || !pPropertyChanged->Property || diff --git a/source/core/core.cgen b/source/core/core.cgen index 7ecb03ae..fa975665 100644 --- a/source/core/core.cgen +++ b/source/core/core.cgen @@ -800,6 +800,7 @@ This is the idiomatic way to build object hierarchies in Lua scripts and tests.< http://schemas.corepunch.com/orca/2006/xml/presentation + Binding expression text. If omitted, object text content is used. @@ -808,6 +809,7 @@ This is the idiomatic way to build object hierarchies in Lua scripts and tests.< Resolved target property slot. + Binding holder object used for message-driven evaluation (optional in raw fallback mode). Compiled VM token tree for this binding expression. Last frame this binding executed. @@ -815,6 +817,12 @@ This is the idiomatic way to build object hierarchies in Lua scripts and tests.< Compiles and attaches this binding to the target property passed in lParam. + + Evaluates the binding token program and imports the result into the target property. + + Target property to import evaluation result into. + + diff --git a/source/core/core.h b/source/core/core.h index 55d33a64..d0dec40c 100644 --- a/source/core/core.h +++ b/source/core/core.h @@ -40,6 +40,7 @@ typedef struct StyleController_AddClassEventArgs StyleController_AddClassMsg_t,* typedef struct StyleController_AddClassesEventArgs StyleController_AddClassesMsg_t,* StyleController_AddClassesMsgPtr; typedef struct StateManagerController_ControllerChangedEventArgs StateManagerController_ControllerChangedMsg_t,* StateManagerController_ControllerChangedMsgPtr; typedef struct Binding_CompileEventArgs Binding_CompileMsg_t,* Binding_CompileMsgPtr; +typedef struct Binding_EvaluateEventArgs Binding_EvaluateMsg_t,* Binding_EvaluateMsgPtr; typedef struct Trigger_TriggeredEventArgs Trigger_TriggeredMsg_t,* Trigger_TriggeredMsgPtr; typedef struct Node_UpdateMatrixEventArgs Node_UpdateMatrixMsg_t,* Node_UpdateMatrixMsgPtr; typedef struct Node_LoadViewEventArgs Node_LoadViewMsg_t,* Node_LoadViewMsgPtr; @@ -766,6 +767,12 @@ struct Binding_CompileEventArgs { }; ORCA_API void luaX_pushBinding_CompileEventArgs(lua_State *L, struct Binding_CompileEventArgs const* data); ORCA_API struct Binding_CompileEventArgs* luaX_checkBinding_CompileEventArgs(lua_State *L, int idx); +/** Binding_EvaluateEventArgs struct */ +struct Binding_EvaluateEventArgs { + struct Property* Property; ///< Target property to import evaluation result into. +}; +ORCA_API void luaX_pushBinding_EvaluateEventArgs(lua_State *L, struct Binding_EvaluateEventArgs const* data); +ORCA_API struct Binding_EvaluateEventArgs* luaX_checkBinding_EvaluateEventArgs(lua_State *L, int idx); /** Trigger_TriggeredEventArgs struct */ struct Trigger_TriggeredEventArgs { struct Trigger* Trigger; @@ -1002,9 +1009,11 @@ struct Binding { enum BindingMode Mode; ///< Binding mode (Expression, OneWay, TwoWay, OneWayToSource). bool_t Enabled; ///< When false, stores the expression but does not compile/execute it. struct Property* property; ///< Resolved target property slot. + struct Object* owner; ///< Binding holder object used for message-driven evaluation (optional in raw fallback mode). struct token* token; ///< Compiled VM token tree for this binding expression. uint32_t updateFrame; ///< Last frame this binding executed. event_t Compile; + event_t Evaluate; }; ORCA_API void luaX_pushBinding(lua_State *L, struct Binding const* Binding); ORCA_API struct Binding* luaX_checkBinding(lua_State *L, int idx); diff --git a/source/core/core_export.c b/source/core/core_export.c index 7ee05b2f..1ea18d08 100644 --- a/source/core/core_export.c +++ b/source/core/core_export.c @@ -709,6 +709,11 @@ static luaL_Reg _Binding_CompileEventArgs_Methods[] = { { NULL, NULL } }; struct PropertyType _Binding_CompileEventArgs_Properties[] = { }; #define _Binding_CompileEventArgs _Binding_CompileEventArgs_Properties +static luaL_Reg _Binding_EvaluateEventArgs_Methods[] = { { NULL, NULL } }; +struct PropertyType _Binding_EvaluateEventArgs_Properties[] = { + DECL(0x5221f9e8, Binding_EvaluateEventArgs, Property, Property, kDataTypeStruct, .TypeString = "Property"), // Binding_EvaluateEventArgs.Property +}; +#define _Binding_EvaluateEventArgs _Binding_EvaluateEventArgs_Properties static luaL_Reg _Trigger_TriggeredEventArgs_Methods[] = { { NULL, NULL } }; struct PropertyType _Trigger_TriggeredEventArgs_Properties[] = { DECL(0xa5ea0da3, Trigger_TriggeredEventArgs, Trigger, Trigger, kDataTypeObject, .TypeString = "Trigger"), // Trigger_TriggeredEventArgs.Trigger @@ -985,6 +990,7 @@ STRUCT(StyleController_AddClassEventArgs, StyleController_AddClassEventArgs); STRUCT(StyleController_AddClassesEventArgs, StyleController_AddClassesEventArgs); STRUCT(StateManagerController_ControllerChangedEventArgs, StateManagerController_ControllerChangedEventArgs); STRUCT(Binding_CompileEventArgs, Binding_CompileEventArgs); +STRUCT(Binding_EvaluateEventArgs, Binding_EvaluateEventArgs); STRUCT(Trigger_TriggeredEventArgs, Trigger_TriggeredEventArgs); STRUCT(Node_UpdateMatrixEventArgs, Node_UpdateMatrixEventArgs); STRUCT(Node_LoadViewEventArgs, Node_LoadViewEventArgs); @@ -1327,11 +1333,13 @@ struct DataObject* luaX_checkDataObject(lua_State *L, int idx) { } REGISTER_CLASS(DataObject, 0); HANDLER(Binding, Binding, Compile); +HANDLER(Binding, Binding, Evaluate); static struct PropertyType const BindingProperties[kBindingNumProperties] = { DECL(0xeb81bd0b, Binding, Expression, Expression, kDataTypeString), // Binding.Expression DECL(0x534e7732, Binding, Mode, Mode, kDataTypeEnum, .EnumValues = _BindingMode), // Binding.Mode DECL(0x9c86e43e, Binding, Enabled, Enabled, kDataTypeBool), // Binding.Enabled DECL(0x52fafc3e, Binding, Compile, Compile, kDataTypeEvent, .TypeString = "Binding_CompileEventArgs"), // Binding.Compile + DECL(0xa35bdf2a, Binding, Evaluate, Evaluate, kDataTypeEvent, .TypeString = "Binding_EvaluateEventArgs"), // Binding.Evaluate }; static struct Binding BindingDefaults = { @@ -1340,6 +1348,7 @@ static struct Binding BindingDefaults = { LRESULT BindingProc(struct Object* object, void* cmp, uint32_t message, wParam_t wparm, lParam_t lparm) { switch (message) { case ID_Binding_Compile: return Binding_Compile(object, cmp, wparm, lparm); // Binding.Compile + case ID_Binding_Evaluate: return Binding_Evaluate(object, cmp, wparm, lparm); // Binding.Evaluate } return FALSE; } @@ -1730,6 +1739,7 @@ ORCA_API int luaopen_orca_core(lua_State *L) { REGISTER_MESSAGE_TYPE(ID_StyleController_AddClasses, "StyleController.AddClasses", StyleController_AddClassesEventArgs); REGISTER_MESSAGE_TYPE(ID_StateManagerController_ControllerChanged, "StateManagerController.ControllerChanged", StateManagerController_ControllerChangedEventArgs); REGISTER_MESSAGE_TYPE(ID_Binding_Compile, "Binding.Compile", Binding_CompileEventArgs); + REGISTER_MESSAGE_TYPE(ID_Binding_Evaluate, "Binding.Evaluate", Binding_EvaluateEventArgs); REGISTER_MESSAGE_TYPE(ID_Trigger_Triggered, "Trigger.Triggered", Trigger_TriggeredEventArgs); REGISTER_MESSAGE_TYPE(ID_Node_UpdateMatrix, "Node.UpdateMatrix", Node_UpdateMatrixEventArgs); REGISTER_MESSAGE_TYPE(ID_Node_LoadView, "Node.LoadView", Node_LoadViewEventArgs); @@ -1792,6 +1802,7 @@ ORCA_API int luaopen_orca_core(lua_State *L) { lua_setfield(L, ((void)luaopen_orca_StyleController_AddClassesEventArgs(L), -2), "StyleController_AddClassesEventArgs"); lua_setfield(L, ((void)luaopen_orca_StateManagerController_ControllerChangedEventArgs(L), -2), "StateManagerController_ControllerChangedEventArgs"); lua_setfield(L, ((void)luaopen_orca_Binding_CompileEventArgs(L), -2), "Binding_CompileEventArgs"); + lua_setfield(L, ((void)luaopen_orca_Binding_EvaluateEventArgs(L), -2), "Binding_EvaluateEventArgs"); lua_setfield(L, ((void)luaopen_orca_Trigger_TriggeredEventArgs(L), -2), "Trigger_TriggeredEventArgs"); lua_setfield(L, ((void)luaopen_orca_Node_UpdateMatrixEventArgs(L), -2), "Node_UpdateMatrixEventArgs"); lua_setfield(L, ((void)luaopen_orca_Node_LoadViewEventArgs(L), -2), "Node_LoadViewEventArgs"); diff --git a/source/core/core_properties.h b/source/core/core_properties.h index aa7865db..fd339044 100644 --- a/source/core/core_properties.h +++ b/source/core/core_properties.h @@ -197,12 +197,14 @@ enum LocaleProperties { #define ID_Binding_Mode 0xe206dce7 // Binding.Mode #define ID_Binding_Enabled 0x223361a5 // Binding.Enabled #define ID_Binding_Compile 0xf02fb971 // Binding.Compile -#define kBindingNumProperties 4 +#define ID_Binding_Evaluate 0x9d397507 // Binding.Evaluate +#define kBindingNumProperties 5 enum BindingProperties { kBindingExpression, kBindingMode, kBindingEnabled, kBindingCompile, + kBindingEvaluate, }; // BindingExpression #define ID_BindingExpression 0xddf6d2b0 @@ -651,6 +653,9 @@ enum NodeProperties { #define ID_StateManagerController_ControllerChangedEventArgs_Property 0x1ed8c3b6 // StateManagerController_ControllerChangedEventArgs.Property #define ID_Binding_CompileEventArgs 0x068eacf9 +#define ID_Binding_EvaluateEventArgs 0x712631ad + +#define ID_Binding_EvaluateEventArgs_Property 0xf93f76b4 // Binding_EvaluateEventArgs.Property #define ID_Trigger_TriggeredEventArgs 0x86976046 #define ID_Trigger_TriggeredEventArgs_Trigger 0xa8841074 // Trigger_TriggeredEventArgs.Trigger diff --git a/source/core/property/property_core.c b/source/core/property/property_core.c index b05ec8ab..31386149 100644 --- a/source/core/property/property_core.c +++ b/source/core/property/property_core.c @@ -58,13 +58,22 @@ _ReleaseBindingNode(struct Binding *binding) return; } SafeDelete(binding->token, Token_Release); - free(binding); + struct Object *binding_object = binding->owner; + if (binding_object) { + OBJ_ReleaseRef(binding_object); + } else { + free(binding); + } } static bool_t _RunBinding(struct Property *property, struct Binding *binding) { if (!binding->token) return TRUE; + struct Object *binding_object = binding->owner; + if (binding_object) { + return _SendMessage(binding_object, Binding, Evaluate, .Property = property); + } if (binding->updateFrame == core.frame) return TRUE; binding->updateFrame = core.frame; diff --git a/source/core/property/property_events.c b/source/core/property/property_events.c index d5d799ac..ef8c7354 100644 --- a/source/core/property/property_events.c +++ b/source/core/property/property_events.c @@ -66,8 +66,27 @@ PROP_AttachProgram(struct Property *p, SafeSet(p->binding->token, program, Token_Release); return; } - p->binding = ZeroAlloc(sizeof(struct Binding)); + struct Object *binding_object = NULL; + if (OBJ_FindClassW(ID_Binding)) { + binding_object = OBJ_Create(ID_Binding); + if (binding_object) { + OBJ_AddRef(binding_object); + p->binding = GetBinding(binding_object); + } + } + if (!p->binding) { + p->binding = ZeroAlloc(sizeof(struct Binding)); + if (!p->binding) { + if (binding_object) { + OBJ_ReleaseRef(binding_object); + } + SafeDelete(program, Token_Release); + return; + } + } + p->binding->property = p; + p->binding->owner = binding_object; p->binding->token = program; if (!p->inBindingIndex) { From b95fe0873e980c7b01a2fefbbd335b56a60b3ad5 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 12:00:36 +0000 Subject: [PATCH 07/11] fix: harden binding evaluate path and fallback ownership Agent-Logs-Url: https://github.com/corepunch/orca/sessions/9cff1947-d103-4217-8c22-747a887980c8 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- source/core/components/Trigger.c | 35 ++---------------------- source/core/property/property_core.c | 7 +++++ source/core/property/property_events.c | 7 +++-- source/core/property/property_internal.h | 1 + 4 files changed, 14 insertions(+), 36 deletions(-) diff --git a/source/core/components/Trigger.c b/source/core/components/Trigger.c index 6105c3e9..ff2ed43b 100644 --- a/source/core/components/Trigger.c +++ b/source/core/components/Trigger.c @@ -64,37 +64,6 @@ _BindingGetIntProperty(struct Object *obj, lpcString_t name, int fallback) return *(int*)PROP_GetValue(prop); } -static bool_t -_BindingEvaluate(struct Property *property, struct Binding *binding) -{ - if (!property || !binding) { - return FALSE; - } - if (!binding->token) { - return TRUE; - } - if (binding->updateFrame == core.frame) { - return TRUE; - } - binding->updateFrame = core.frame; - - struct vm_register r = { 0 }; - if (!OBJ_RunProgram(property->object, binding->token, &r)) { - Con_Error("Binding update failed while running program for %s/%s", - OBJ_GetName(property->object), - property->pdesc->Name); - return FALSE; - } - if (!PROP_Import(property, &r)) { - Con_Error("Binding update failed while importing into %s/%s", - OBJ_GetName(property->object), - property->pdesc->Name); - return FALSE; - } - return TRUE; -} - - static LRESULT _BindingCompileToProperty(struct Object *hObject, struct Binding *binding, @@ -202,8 +171,8 @@ HANDLER(Binding, Binding, Evaluate) { struct Property *property = pEvaluate && pEvaluate->Property ? pEvaluate->Property - : pBinding->property; - return _BindingEvaluate(property, pBinding); + : (pBinding ? pBinding->property : NULL); + return PROP_EvaluateBinding(property, pBinding); } HANDLER(Trigger, Object, PropertyChanged) diff --git a/source/core/property/property_core.c b/source/core/property/property_core.c index 31386149..4153a432 100644 --- a/source/core/property/property_core.c +++ b/source/core/property/property_core.c @@ -74,6 +74,13 @@ _RunBinding(struct Property *property, struct Binding *binding) if (binding_object) { return _SendMessage(binding_object, Binding, Evaluate, .Property = property); } + return PROP_EvaluateBinding(property, binding); +} + +bool_t +PROP_EvaluateBinding(struct Property *property, struct Binding *binding) +{ + if (!property || !binding || !binding->token) return TRUE; if (binding->updateFrame == core.frame) return TRUE; binding->updateFrame = core.frame; diff --git a/source/core/property/property_events.c b/source/core/property/property_events.c index ef8c7354..5d324fd9 100644 --- a/source/core/property/property_events.c +++ b/source/core/property/property_events.c @@ -75,11 +75,12 @@ PROP_AttachProgram(struct Property *p, } } if (!p->binding) { + if (binding_object) { + OBJ_ReleaseRef(binding_object); + binding_object = NULL; + } p->binding = ZeroAlloc(sizeof(struct Binding)); if (!p->binding) { - if (binding_object) { - OBJ_ReleaseRef(binding_object); - } SafeDelete(program, Token_Release); return; } diff --git a/source/core/property/property_internal.h b/source/core/property/property_internal.h index 82951a9d..c926bbd4 100644 --- a/source/core/property/property_internal.h +++ b/source/core/property/property_internal.h @@ -42,5 +42,6 @@ PROP_HasHandler(struct Property const *property) struct Object *PROP_GetObjectValue(struct Property const *property); bool_t _AssignCallback(lua_State* L, struct Property *property); void PROP_FireNotification(lua_State* L, struct Property *property, struct Object *object); +bool_t PROP_EvaluateBinding(struct Property *property, struct Binding *binding); #endif /* __PROPERTY_INTERNAL_H__ */ From ccf7bda12b0c1fce40862f5472f63582a408636a Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 12:05:45 +0000 Subject: [PATCH 08/11] refactor: drop legacy binding markup in Example app assets Agent-Logs-Url: https://github.com/corepunch/orca/sessions/d34fcc58-efa7-4920-b82c-b134af06e881 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- samples/Example/Prefabs/FeatureCard.xml | 6 ++--- samples/Example/Prefabs/FeatureImageCard.xml | 6 ++--- samples/Example/Prefabs/GalleryCard.xml | 6 ++--- samples/Example/Prefabs/IconCard.xml | 8 +++--- samples/Example/Prefabs/ImageCaptionCard.xml | 4 +-- samples/Example/Prefabs/Mertic.xml | 4 +-- samples/Example/Prefabs/Quote.xml | 8 +++--- samples/Example/Prefabs/SignalCard.xml | 6 ++--- samples/Example/Prefabs/TabPanelHeader.xml | 4 +-- samples/Example/Prefabs/WorkflowStep.xml | 6 ++--- samples/Example/Prefabs/XmlModelNode.xml | 4 +-- source/core/components/Trigger.c | 26 +++----------------- tests/test_layout.lua | 6 ++--- 13 files changed, 38 insertions(+), 56 deletions(-) diff --git a/samples/Example/Prefabs/FeatureCard.xml b/samples/Example/Prefabs/FeatureCard.xml index a2370b9f..cf8db234 100644 --- a/samples/Example/Prefabs/FeatureCard.xml +++ b/samples/Example/Prefabs/FeatureCard.xml @@ -3,8 +3,8 @@ + Text="{../Card.Icon}" ForegroundColor="{../Card.PrimaryColor}" BackgroundColor="{../Card.IconBackground}"/> - - + + diff --git a/samples/Example/Prefabs/FeatureImageCard.xml b/samples/Example/Prefabs/FeatureImageCard.xml index 27d7be45..9c5e65bf 100644 --- a/samples/Example/Prefabs/FeatureImageCard.xml +++ b/samples/Example/Prefabs/FeatureImageCard.xml @@ -1,7 +1,7 @@ - - - + + + diff --git a/samples/Example/Prefabs/GalleryCard.xml b/samples/Example/Prefabs/GalleryCard.xml index 8da77c21..9a46eb53 100644 --- a/samples/Example/Prefabs/GalleryCard.xml +++ b/samples/Example/Prefabs/GalleryCard.xml @@ -1,9 +1,9 @@ - + - - + + diff --git a/samples/Example/Prefabs/IconCard.xml b/samples/Example/Prefabs/IconCard.xml index 4d4c514f..aa45ab04 100644 --- a/samples/Example/Prefabs/IconCard.xml +++ b/samples/Example/Prefabs/IconCard.xml @@ -2,10 +2,10 @@ - - + + - + - + diff --git a/samples/Example/Prefabs/ImageCaptionCard.xml b/samples/Example/Prefabs/ImageCaptionCard.xml index c293aa76..d6287456 100644 --- a/samples/Example/Prefabs/ImageCaptionCard.xml +++ b/samples/Example/Prefabs/ImageCaptionCard.xml @@ -1,6 +1,6 @@ - - + + diff --git a/samples/Example/Prefabs/Mertic.xml b/samples/Example/Prefabs/Mertic.xml index 0730d83e..4198e504 100644 --- a/samples/Example/Prefabs/Mertic.xml +++ b/samples/Example/Prefabs/Mertic.xml @@ -1,6 +1,6 @@ - - + + diff --git a/samples/Example/Prefabs/Quote.xml b/samples/Example/Prefabs/Quote.xml index 9c4ee457..352802b9 100644 --- a/samples/Example/Prefabs/Quote.xml +++ b/samples/Example/Prefabs/Quote.xml @@ -1,10 +1,10 @@ - - + + - - + + diff --git a/samples/Example/Prefabs/SignalCard.xml b/samples/Example/Prefabs/SignalCard.xml index d58df8e0..1ecb11cd 100644 --- a/samples/Example/Prefabs/SignalCard.xml +++ b/samples/Example/Prefabs/SignalCard.xml @@ -1,9 +1,9 @@ - + - - + + diff --git a/samples/Example/Prefabs/TabPanelHeader.xml b/samples/Example/Prefabs/TabPanelHeader.xml index 8aa55d8e..4b485987 100644 --- a/samples/Example/Prefabs/TabPanelHeader.xml +++ b/samples/Example/Prefabs/TabPanelHeader.xml @@ -1,6 +1,6 @@ - - + + diff --git a/samples/Example/Prefabs/WorkflowStep.xml b/samples/Example/Prefabs/WorkflowStep.xml index 900aa42f..4bb3a811 100644 --- a/samples/Example/Prefabs/WorkflowStep.xml +++ b/samples/Example/Prefabs/WorkflowStep.xml @@ -1,7 +1,7 @@ - - - + + + diff --git a/samples/Example/Prefabs/XmlModelNode.xml b/samples/Example/Prefabs/XmlModelNode.xml index 1b529c7f..cc48ecbd 100644 --- a/samples/Example/Prefabs/XmlModelNode.xml +++ b/samples/Example/Prefabs/XmlModelNode.xml @@ -1,6 +1,6 @@ - - + + diff --git a/source/core/components/Trigger.c b/source/core/components/Trigger.c index ff2ed43b..91325118 100644 --- a/source/core/components/Trigger.c +++ b/source/core/components/Trigger.c @@ -67,8 +67,7 @@ _BindingGetIntProperty(struct Object *obj, lpcString_t name, int fallback) static LRESULT _BindingCompileToProperty(struct Object *hObject, struct Binding *binding, - struct Property *property, - bool_t normalize_markup) + struct Property *property) { if (!property) { Con_Error("Binding.Compile requires a target property in lParam"); @@ -89,23 +88,8 @@ _BindingCompileToProperty(struct Object *hObject, _BindingGetIntProperty(hObject, "Mode", kBindingModeExpression); bool_t enabled = _BindingGetIntProperty(hObject, "Enabled", TRUE) ? TRUE : FALSE; - lpcString_t final_expr = expr; - fixedString_t normalized_expr = {0}; - if (normalize_markup) { - size_t len = strlen(expr); - if (len > 9 && !strncmp(expr, "{Binding ", 9) && expr[len - 1] == '}') { - /* {Binding X} → {X}; runtime default resolves X relative to template root */ - snprintf(normalized_expr, sizeof(normalized_expr), "{%.*s}", (int)(len - 10), expr + 9); - final_expr = normalized_expr; - } else if (*expr != '{') { - /* Bare path X → {X}; runtime default resolves relative to template root */ - snprintf(normalized_expr, sizeof(normalized_expr), "{%s}", expr); - final_expr = normalized_expr; - } - } - binding->property = property; - if (!PROP_SetBinding(property, final_expr, mode, enabled)) { + if (!PROP_SetBinding(property, expr, mode, enabled)) { Con_Error("Binding failed for property '%s' on '%s'", PROP_GetName(property), OBJ_GetClassName(PROP_GetObject(property))); @@ -155,16 +139,14 @@ HANDLER(Binding, Binding, Compile) { return _BindingCompileToProperty(hObject, pBinding, - (struct Property *)pCompile, - TRUE); + (struct Property *)pCompile); } HANDLER(BindingExpression, Binding, Compile) { return _BindingCompileToProperty(hObject, (struct Binding *)pBindingExpression, - (struct Property *)pCompile, - FALSE); + (struct Property *)pCompile); } HANDLER(Binding, Binding, Evaluate) diff --git a/tests/test_layout.lua b/tests/test_layout.lua index bafffc62..0bb62dda 100644 --- a/tests/test_layout.lua +++ b/tests/test_layout.lua @@ -827,12 +827,12 @@ local function test_example_application_xml() local icon_card_uses_image = icon_card:find("{../Card.Icon}", 1, true) local icon_card_header = icon_card:find('{../Card.IconBackground}', 1, true) local icon_property = package_lua:find('Name="Icon", DataType="Object", TypeString="Texture"', 1, true) local icon_bg_blue = package_lua:find('Key = "icon-bg-blue", Value = "#10203A"', 1, true) From c31e9a0b6f21f324a8be222e1a473fe99041826e Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 13:20:13 +0000 Subject: [PATCH 09/11] refactor: switch Example app template paths to Lisp assets Agent-Logs-Url: https://github.com/corepunch/orca/sessions/97c3a778-a987-4569-a8c0-52d51838ffc7 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- samples/Example/Prefabs/FeatureCard.lisp | 7 ++ samples/Example/Prefabs/FeatureImageCard.lisp | 4 + samples/Example/Prefabs/GalleryCard.lisp | 5 ++ samples/Example/Prefabs/IconCard.lisp | 6 ++ samples/Example/Prefabs/ImageCaptionCard.lisp | 3 + samples/Example/Prefabs/Mertic.lisp | 3 + samples/Example/Prefabs/Quote.lisp | 6 ++ samples/Example/Prefabs/SignalCard.lisp | 5 ++ samples/Example/Prefabs/TabPanelHeader.lisp | 3 + samples/Example/Prefabs/WorkflowStep.lisp | 4 + samples/Example/Prefabs/XmlModelNode.lisp | 3 + samples/Example/Screens/Application.lisp | 82 +++++++++---------- samples/Example/package.lua | 2 +- 13 files changed, 91 insertions(+), 42 deletions(-) create mode 100644 samples/Example/Prefabs/FeatureCard.lisp create mode 100644 samples/Example/Prefabs/FeatureImageCard.lisp create mode 100644 samples/Example/Prefabs/GalleryCard.lisp create mode 100644 samples/Example/Prefabs/IconCard.lisp create mode 100644 samples/Example/Prefabs/ImageCaptionCard.lisp create mode 100644 samples/Example/Prefabs/Mertic.lisp create mode 100644 samples/Example/Prefabs/Quote.lisp create mode 100644 samples/Example/Prefabs/SignalCard.lisp create mode 100644 samples/Example/Prefabs/TabPanelHeader.lisp create mode 100644 samples/Example/Prefabs/WorkflowStep.lisp create mode 100644 samples/Example/Prefabs/XmlModelNode.lisp diff --git a/samples/Example/Prefabs/FeatureCard.lisp b/samples/Example/Prefabs/FeatureCard.lisp new file mode 100644 index 00000000..0bd35e5b --- /dev/null +++ b/samples/Example/Prefabs/FeatureCard.lisp @@ -0,0 +1,7 @@ +(StackView Name="FeatureCard" Direction="Vertical" Spacing=10 BackgroundColor="$card-background" Padding=28 + (TextBlock Name="FeatureCardIcon" FontSize=20 Width=44 Height=40 + TextHorizontalAlignment="Center" TextVerticalAlignment="Center" + Text="{../Card.Icon}" ForegroundColor="{../Card.PrimaryColor}" BackgroundColor="{../Card.IconBackground}") + (Node2D Name="FeatureCardSpacer" Height=8) + (TextBlock Name="FeatureCardTitle" FontSize=16 ForegroundColor="$text-primary" Text="{../Card.Title}") + (TextBlock Name="FeatureCardBody" FontSize=13 ForegroundColor="$text-dim" WordWrap="true" Text="{../Card.Body}")) diff --git a/samples/Example/Prefabs/FeatureImageCard.lisp b/samples/Example/Prefabs/FeatureImageCard.lisp new file mode 100644 index 00000000..1cf7a4b6 --- /dev/null +++ b/samples/Example/Prefabs/FeatureImageCard.lisp @@ -0,0 +1,4 @@ +(StackView Name="FeatureImageCard" Direction="Vertical" Spacing=12 BackgroundColor="$card-background" Padding=28 + (ImageView Name="FeatureImageCardIcon" Width=40 Height=40 Source="{Card.Icon}" ForegroundColor="{Card.PrimaryColor}") + (TextBlock Name="FeatureImageCardTitle" Text="{Card.Title}" FontSize=16 ForegroundColor="$text-primary") + (TextBlock Name="FeatureImageCardBody" Text="{Card.Body}" FontSize=13 ForegroundColor="$text-dim" WordWrap="true")) diff --git a/samples/Example/Prefabs/GalleryCard.lisp b/samples/Example/Prefabs/GalleryCard.lisp new file mode 100644 index 00000000..6c1def89 --- /dev/null +++ b/samples/Example/Prefabs/GalleryCard.lisp @@ -0,0 +1,5 @@ +(StackView Name="GalleryCard" Direction="Vertical" Spacing=0 BackgroundColor="$card-background" + (ImageView Name="GalleryCardImage" Height=180 Stretch="UniformToFill" Source="{../Card.Image}") + (StackView Name="GalleryCardCaption" Direction="Vertical" Spacing=4 Padding=16 + (TextBlock Name="GalleryCardTitle" FontSize=15 ForegroundColor="$text-primary" Text="{../../Card.Title}") + (TextBlock Name="GalleryCardBody" FontSize=12 ForegroundColor="$text-dim" WordWrap="true" Text="{../../Card.Subtitle}"))) diff --git a/samples/Example/Prefabs/IconCard.lisp b/samples/Example/Prefabs/IconCard.lisp new file mode 100644 index 00000000..5b4a89b7 --- /dev/null +++ b/samples/Example/Prefabs/IconCard.lisp @@ -0,0 +1,6 @@ +(StackView Name="IconCard" Direction="Vertical" Spacing=10 BackgroundColor="$card-background" Padding=28 + (StackView Name="IconCardHeader" Direction="Horizontal" Spacing=12 AlignItems="Center" + (StackView Name="IconCardBadge" Width=48 Height=48 AlignItems="Center" JustifyContent="Center" BackgroundColor="{../../Card.IconBackground}" + (ImageView Name="IconCardImage" Width=24 Height=24 Stretch="Uniform" Source="{../../../Card.Icon}" ForegroundColor="{../../../Card.PrimaryColor}")) + (TextBlock Name="IconCardTitle" FontSize=16 ForegroundColor="$text-primary" Text="{../../Card.Title}")) + (TextBlock Name="IconCardBody" FontSize=13 ForegroundColor="$text-dim" WordWrap="true" Text="{../Card.Body}")) diff --git a/samples/Example/Prefabs/ImageCaptionCard.lisp b/samples/Example/Prefabs/ImageCaptionCard.lisp new file mode 100644 index 00000000..f6c4bde9 --- /dev/null +++ b/samples/Example/Prefabs/ImageCaptionCard.lisp @@ -0,0 +1,3 @@ +(StackView Name="ImageCaptionCard" Direction="Vertical" Spacing=8 BackgroundColor="$panel-background" Padding=16 + (ImageView Name="ImageCaptionCardImage" Height=160 Stretch="UniformToFill" Source="{Card.Image}") + (TextBlock Name="ImageCaptionCardTitle" Text="{Card.Title}" FontSize=13 ForegroundColor="$text-secondary")) diff --git a/samples/Example/Prefabs/Mertic.lisp b/samples/Example/Prefabs/Mertic.lisp new file mode 100644 index 00000000..8aa2ee5e --- /dev/null +++ b/samples/Example/Prefabs/Mertic.lisp @@ -0,0 +1,3 @@ +(StackView Name="MetricUsers" Direction="Vertical" Spacing=4 BackgroundColor="$panel-background" Padding=20 Width=120 + (TextBlock Name="MetricUsersValue" FontSize=28 ForegroundColor="{../Card.PrimaryColor}" Text="{../Card.Title}") + (TextBlock Name="MetricUsersLabel" FontSize=11 ForegroundColor="$text-dim" Text="{../Card.Subtitle}")) diff --git a/samples/Example/Prefabs/Quote.lisp b/samples/Example/Prefabs/Quote.lisp new file mode 100644 index 00000000..9e199bbe --- /dev/null +++ b/samples/Example/Prefabs/Quote.lisp @@ -0,0 +1,6 @@ +(StackView Name="Quote" Direction="Vertical" Spacing=16 BackgroundColor="$card-background" Padding=28 + (TextBlock Name="QuoteMark" Text="“" FontSize=48 ForegroundColor="{../Card.PrimaryColor}") + (TextBlock Name="QuoteText" FontSize=13 ForegroundColor="$text-secondary" WordWrap="true" Text="{../Card.Body}") + (StackView Name="QuoteAuthor" Direction="Horizontal" Spacing=12 AlignItems="Center" + (TextBlock Name="QuoteAuthorName" FontSize=13 ForegroundColor="$text-primary" Text="{../../Card.Title}") + (TextBlock Name="QuoteAuthorRole" FontSize=11 ForegroundColor="$text-dim" Text="{../../Card.Subtitle}"))) diff --git a/samples/Example/Prefabs/SignalCard.lisp b/samples/Example/Prefabs/SignalCard.lisp new file mode 100644 index 00000000..5ece0d97 --- /dev/null +++ b/samples/Example/Prefabs/SignalCard.lisp @@ -0,0 +1,5 @@ +(StackView Name="SignalCard" Direction="Horizontal" Spacing=12 BackgroundColor="$panel-background" Padding=16 AlignItems="Center" + (ImageView Name="SignalCardIcon" Source="{Card.Icon}" Width=28 Height=28 ForegroundColor="{Card.PrimaryColor}") + (StackView Name="SignalCardCopy" Direction="Vertical" Spacing=2 + (TextBlock Name="SignalCardTitle" Text="{Card.Title}" FontSize=14 ForegroundColor="$text-primary") + (TextBlock Name="SignalCardBody" Text="{Card.Body}" FontSize=12 ForegroundColor="$text-muted"))) diff --git a/samples/Example/Prefabs/TabPanelHeader.lisp b/samples/Example/Prefabs/TabPanelHeader.lisp new file mode 100644 index 00000000..57f3ed15 --- /dev/null +++ b/samples/Example/Prefabs/TabPanelHeader.lisp @@ -0,0 +1,3 @@ +(StackView Name="TabPanelHeader" Direction="Horizontal" Spacing=12 AlignItems="Center" + (ImageView Name="TabPanelHeaderIcon" Width=26 Height=26 Source="{Card.Icon}" ForegroundColor="{Card.PrimaryColor}") + (TextBlock Name="TabPanelHeaderTitle" Text="{Card.Title}" FontSize=24 ForegroundColor="$text-primary")) diff --git a/samples/Example/Prefabs/WorkflowStep.lisp b/samples/Example/Prefabs/WorkflowStep.lisp new file mode 100644 index 00000000..e313fc13 --- /dev/null +++ b/samples/Example/Prefabs/WorkflowStep.lisp @@ -0,0 +1,4 @@ +(StackView Name="WorkflowStep" Direction="Vertical" Spacing=12 BackgroundColor="$card-background" Padding=24 + (TextBlock Name="WorkflowStepNumber" FontSize=32 Text="{../Card.Title}" ForegroundColor="{../Card.PrimaryColor}") + (TextBlock Name="WorkflowStepTitle" FontSize=15 ForegroundColor="$text-primary" Text="{../Card.Subtitle}") + (TextBlock Name="WorkflowStepBody" FontSize=12 ForegroundColor="$text-dim" WordWrap="true" Text="{../Card.Body}")) diff --git a/samples/Example/Prefabs/XmlModelNode.lisp b/samples/Example/Prefabs/XmlModelNode.lisp new file mode 100644 index 00000000..0a1d4fd9 --- /dev/null +++ b/samples/Example/Prefabs/XmlModelNode.lisp @@ -0,0 +1,3 @@ +(StackView Name="XmlModelNode" Direction="Horizontal" Spacing=10 AlignItems="Center" + (ImageView Name="XmlModelNodeIcon" Width=20 Height=20 Source="{Card.Icon}" ForegroundColor="{Card.PrimaryColor}") + (TextBlock Name="XmlModelNodeText" Text="{Card.Title}" FontSize=14 ForegroundColor="{Card.PrimaryColor}")) diff --git a/samples/Example/Screens/Application.lisp b/samples/Example/Screens/Application.lisp index b132d5b0..10446376 100644 --- a/samples/Example/Screens/Application.lisp +++ b/samples/Example/Screens/Application.lisp @@ -44,17 +44,17 @@ (Node2D Name="MetricSpacer" Height=40) (Grid Name="Metrics" Columns="auto auto auto" Spacing=16 (LayerPrefabPlaceholder Name="MetricUsers" - PlaceholderTemplate="Example/Prefabs/Mertic" + PlaceholderTemplate="Example/Prefabs/Mertic.lisp" Card.Title="2.4 M" Card.Subtitle="Active users" Card.PrimaryColor="$accent") (LayerPrefabPlaceholder Name="MetricUptime" - PlaceholderTemplate="Example/Prefabs/Mertic" + PlaceholderTemplate="Example/Prefabs/Mertic.lisp" Card.Title="99.9 %" Card.Subtitle="Uptime SLA" Card.PrimaryColor="$accent-green") (LayerPrefabPlaceholder Name="MetricFrame" - PlaceholderTemplate="Example/Prefabs/Mertic" + PlaceholderTemplate="Example/Prefabs/Mertic.lisp" Card.Title="1.2 ms" Card.Subtitle="Frame time" Card.PrimaryColor="$accent-blue"))) @@ -73,37 +73,37 @@ (:bind Target="Grid.Columns" (if (step 640 (bind "Node.ActualWidth")) "auto auto auto" "auto auto")) (LayerPrefabPlaceholder Name="SignalXml" - PlaceholderTemplate="Example/Prefabs/SignalCard" + PlaceholderTemplate="Example/Prefabs/SignalCard.lisp" Card.Icon="Example/Icons/code.svg?width=28&type=mask" Card.Title="XML-first screens" Card.Body="Layouts stay readable." Card.PrimaryColor="$accent") (LayerPrefabPlaceholder Name="SignalLua" - PlaceholderTemplate="Example/Prefabs/SignalCard" + PlaceholderTemplate="Example/Prefabs/SignalCard.lisp" Card.Icon="Example/Icons/file.svg?width=28&type=mask" Card.Title="Lua beside the view" Card.Body="Behavior stays close." Card.PrimaryColor="$accent-blue") (LayerPrefabPlaceholder Name="SignalMoon" - PlaceholderTemplate="Example/Prefabs/SignalCard" + PlaceholderTemplate="Example/Prefabs/SignalCard.lisp" Card.Icon="Example/Icons/moon.svg?width=28&type=mask" Card.Title="MoonScript when it grows" Card.Body="Less noise, same runtime." Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="SignalShip" - PlaceholderTemplate="Example/Prefabs/SignalCard" + PlaceholderTemplate="Example/Prefabs/SignalCard.lisp" Card.Icon="Example/Icons/globe.svg?width=28&type=mask" Card.Title="Desktop, mobile, web" Card.Body="One screen, many targets." Card.PrimaryColor="$accent-green") (LayerPrefabPlaceholder Name="SignalSpeed" - PlaceholderTemplate="Example/Prefabs/SignalCard" + PlaceholderTemplate="Example/Prefabs/SignalCard.lisp" Card.Icon="Example/Icons/zap.svg?width=28&type=mask" Card.Title="Fast edit-run-refine" Card.Body="See changes immediately." Card.PrimaryColor="$accent-amber") (LayerPrefabPlaceholder Name="SignalScale" - PlaceholderTemplate="Example/Prefabs/SignalCard" + PlaceholderTemplate="Example/Prefabs/SignalCard.lisp" Card.Icon="Example/Icons/cpu.svg?width=28&type=mask" Card.Title="Built to scale" Card.Body="Same UI, larger ambitions." @@ -118,7 +118,7 @@ (StackView Name="xml" Direction="Vertical" Spacing=24 BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="XmlPanelHeader" - PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader.lisp" Card.Icon="Example/Icons/grid.svg?width=26&type=mask" Card.Title="XML reads like a blueprint" Card.PrimaryColor="$accent") @@ -143,37 +143,37 @@ (TextBlock Name="XmlModelHeading" Text="The structure stays honest" FontSize=16 ForegroundColor="$text-primary") (LayerPrefabPlaceholder Name="XmlModelLine1" - PlaceholderTemplate="Example/Prefabs/XmlModelNode" + PlaceholderTemplate="Example/Prefabs/XmlModelNode.lisp" Card.Icon="Example/Icons/monitor.svg?width=20&type=mask" Card.Title="Screen" PaddingLeft=0 Card.PrimaryColor="$accent") (LayerPrefabPlaceholder Name="XmlModelLine2" - PlaceholderTemplate="Example/Prefabs/XmlModelNode" + PlaceholderTemplate="Example/Prefabs/XmlModelNode.lisp" Card.Icon="Example/Icons/panel-top.svg?width=20&type=mask" Card.Title="TabView" PaddingLeft=16 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine3" - PlaceholderTemplate="Example/Prefabs/XmlModelNode" + PlaceholderTemplate="Example/Prefabs/XmlModelNode.lisp" Card.Icon="Example/Icons/grid.svg?width=20&type=mask" Card.Title="Grid" PaddingLeft=32 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine4" - PlaceholderTemplate="Example/Prefabs/XmlModelNode" + PlaceholderTemplate="Example/Prefabs/XmlModelNode.lisp" Card.Icon="Example/Icons/layers.svg?width=20&type=mask" Card.Title="StackView" PaddingLeft=32 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine5" - PlaceholderTemplate="Example/Prefabs/XmlModelNode" + PlaceholderTemplate="Example/Prefabs/XmlModelNode.lisp" Card.Icon="Example/Icons/image.svg?width=20&type=mask" Card.Title="ImageView" PaddingLeft=48 Card.PrimaryColor="$text-secondary") (LayerPrefabPlaceholder Name="XmlModelLine6" - PlaceholderTemplate="Example/Prefabs/XmlModelNode" + PlaceholderTemplate="Example/Prefabs/XmlModelNode.lisp" Card.Icon="Example/Icons/type.svg?width=20&type=mask" Card.Title="TextBlock" PaddingLeft=48 @@ -181,7 +181,7 @@ (StackView Name="lua" Direction="Vertical" Spacing=24 BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="LuaPanelHeader" - PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader.lisp" Card.Icon="Example/Icons/code.svg?width=26&type=mask" Card.Title="Lua stays close to the surface" Card.PrimaryColor="$accent-blue") @@ -190,21 +190,21 @@ FontSize=14 ForegroundColor="$text-muted" WordWrap="true") (Grid Name="LuaGrid" Columns="auto auto auto" Spacing=16 (LayerPrefabPlaceholder Name="LuaCard1" - PlaceholderTemplate="Example/Prefabs/IconCard" + PlaceholderTemplate="Example/Prefabs/IconCard.lisp" Card.Icon="Example/Icons/mouse-pointer-click.svg?width=40&type=mask" Card.Title="Input" Card.Body="Tap, click, and selection handling can sit right beside the view that needs it." Card.PrimaryColor="$accent" Card.IconBackground="$icon-bg-purple") (LayerPrefabPlaceholder Name="LuaCard2" - PlaceholderTemplate="Example/Prefabs/IconCard" + PlaceholderTemplate="Example/Prefabs/IconCard.lisp" Card.Icon="Example/Icons/database.svg?width=40&type=mask" Card.Title="State" Card.Body="Small state objects stay local, so the screen remains easy to reason about." Card.PrimaryColor="$accent-blue" Card.IconBackground="$icon-bg-blue") (LayerPrefabPlaceholder Name="LuaCard3" - PlaceholderTemplate="Example/Prefabs/IconCard" + PlaceholderTemplate="Example/Prefabs/IconCard.lisp" Card.Icon="Example/Icons/link-2.svg?width=40&type=mask" Card.Title="Binding" Card.Body="Let Lua connect the moving pieces while XML keeps the hierarchy in view." @@ -213,7 +213,7 @@ (StackView Name="moon" Direction="Vertical" Spacing=24 BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="MoonPanelHeader" - PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader.lisp" Card.Icon="Example/Icons/moon.svg?width=26&type=mask" Card.Title="MoonScript trims the noise" Card.PrimaryColor="$text-secondary") @@ -248,7 +248,7 @@ (StackView Name="deploy" Direction="Vertical" Spacing=24 BackgroundColor="$surface-deep" Padding=32 (LayerPrefabPlaceholder Name="DeployPanelHeader" - PlaceholderTemplate="Example/Prefabs/TabPanelHeader" + PlaceholderTemplate="Example/Prefabs/TabPanelHeader.lisp" Card.Icon="Example/Icons/globe.svg?width=26&type=mask" Card.Title="One UI, many targets" Card.PrimaryColor="$accent-green") @@ -257,21 +257,21 @@ FontSize=14 ForegroundColor="$text-muted" WordWrap="true") (Grid Name="DeployGrid" Columns="auto auto auto" Spacing=16 (LayerPrefabPlaceholder Name="DeployCard1" - PlaceholderTemplate="Example/Prefabs/IconCard" + PlaceholderTemplate="Example/Prefabs/IconCard.lisp" Card.Icon="Example/Icons/monitor.svg?width=40&type=mask" Card.Title="Desktop" Card.Body="Best when you want denser information, more breathing room, and precision input." Card.PrimaryColor="$accent" Card.IconBackground="$icon-bg-purple") (LayerPrefabPlaceholder Name="DeployCard2" - PlaceholderTemplate="Example/Prefabs/IconCard" + PlaceholderTemplate="Example/Prefabs/IconCard.lisp" Card.Icon="Example/Icons/smartphone.svg?width=40&type=mask" Card.Title="Mobile" Card.Body="Responsive spacing keeps the same interface calm and touch-friendly on smaller screens." Card.PrimaryColor="$accent-green" Card.IconBackground="$icon-bg-green") (LayerPrefabPlaceholder Name="DeployCard3" - PlaceholderTemplate="Example/Prefabs/IconCard" + PlaceholderTemplate="Example/Prefabs/IconCard.lisp" Card.Icon="Example/Icons/globe.svg?width=40&type=mask" Card.Title="Web" Card.Body="The same screen model can carry into browser workflows without being rewritten." @@ -279,11 +279,11 @@ Card.IconBackground="$icon-bg-blue")) (Grid Name="DeployImages" Columns="auto auto" Spacing=12 (LayerPrefabPlaceholder Name="DeployImage1" - PlaceholderTemplate="Example/Prefabs/ImageCaptionCard" + PlaceholderTemplate="Example/Prefabs/ImageCaptionCard.lisp" Card.Image="Example/Images/orca-tab-city" Card.Title="City glow") (LayerPrefabPlaceholder Name="DeployImage2" - PlaceholderTemplate="Example/Prefabs/ImageCaptionCard" + PlaceholderTemplate="Example/Prefabs/ImageCaptionCard.lisp" Card.Image="Example/Images/orca-tab-lights" Card.Title="Night light")) (StackView Name="DeployCommandLink" Direction="Horizontal" Spacing=12 @@ -299,25 +299,25 @@ FontSize=14 ForegroundColor="$text-primary" WordWrap="true")))))) (Grid Name="FeatureSection" Columns="auto auto auto auto" Spacing=20 (LayerPrefabPlaceholder Name="FeatureXmlCard" - PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard.lisp" Card.Icon="Example/Icons/layers.svg?width=40&type=mask" Card.Title="XML stays readable" Card.Body="The layout tree is right there in the page, so structure and spacing stay easy to tune." Card.PrimaryColor="$accent") (LayerPrefabPlaceholder Name="FeatureLuaCard" - PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard.lisp" Card.Icon="Example/Icons/monitor.svg?width=40&type=mask" Card.Title="Lua stays close" Card.Body="Interactions, bindings, and state can live next to the screen that owns them." Card.PrimaryColor="$accent-blue") (LayerPrefabPlaceholder Name="FeatureShipCard" - PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard.lisp" Card.Icon="Example/Icons/globe.svg?width=40&type=mask" Card.Title="Ships everywhere" Card.Body="The same UI can move across desktop, mobile, and web-friendly targets." Card.PrimaryColor="$accent-green") (LayerPrefabPlaceholder Name="FeatureSpeedCard" - PlaceholderTemplate="Example/Prefabs/FeatureImageCard" + PlaceholderTemplate="Example/Prefabs/FeatureImageCard.lisp" Card.Icon="Example/Icons/zap.svg?width=40&type=mask" Card.Title="Fast iteration" Card.Body="Edit, reload, and refine quickly without losing the overall shape of the interface." @@ -333,12 +333,12 @@ FontSize=14 ForegroundColor="$text-muted") (Grid Name="GalleryGrid" Columns="auto auto" Spacing=16 (LayerPrefabPlaceholder Name="GalleryCard1" - PlaceholderTemplate="Example/Prefabs/GalleryCard" + PlaceholderTemplate="Example/Prefabs/GalleryCard.lisp" Card.Image="Example/Images/lake" Card.Title="Lake Scene" Card.Subtitle="Real-time reflections and water surface rendered at 60 fps.") (LayerPrefabPlaceholder Name="GalleryCard2" - PlaceholderTemplate="Example/Prefabs/GalleryCard" + PlaceholderTemplate="Example/Prefabs/GalleryCard.lisp" Card.Image="Example/Images/peacock" Card.Title="Peacock" Card.Subtitle="High-fidelity texture sampling with anisotropic filtering."))) @@ -356,22 +356,22 @@ (if (step 640 (bind "Node.ActualWidth")) "auto auto auto auto" "auto auto")) (LayerPrefabPlaceholder Name="Step1" - PlaceholderTemplate="Example/Prefabs/WorkflowStep" + PlaceholderTemplate="Example/Prefabs/WorkflowStep.lisp" Card.Title="01" Card.Subtitle="Define Layout" Card.PrimaryColor="$accent" Card.Body="Write your screen as an XML file with familiar Grid and StackView containers.") (LayerPrefabPlaceholder Name="Step2" - PlaceholderTemplate="Example/Prefabs/WorkflowStep" + PlaceholderTemplate="Example/Prefabs/WorkflowStep.lisp" Card.Title="02" Card.Subtitle="Load Assets" Card.PrimaryColor="$accent-blue" Card.Body="Images, fonts, and data files are streamed from packages at runtime.") (LayerPrefabPlaceholder Name="Step3" - PlaceholderTemplate="Example/Prefabs/WorkflowStep" + PlaceholderTemplate="Example/Prefabs/WorkflowStep.lisp" Card.Title="03" Card.Subtitle="Script Logic" Card.PrimaryColor="$accent-green" Card.Body="Attach Lua scripts to any component for interactivity and data binding.") (LayerPrefabPlaceholder Name="Step4" - PlaceholderTemplate="Example/Prefabs/WorkflowStep" + PlaceholderTemplate="Example/Prefabs/WorkflowStep.lisp" Card.Title="04" Card.Subtitle="Ship" Card.PrimaryColor="$accent-amber" Card.Body="Deploy to desktop, mobile, or WebGL with a single build command."))) @@ -383,17 +383,17 @@ FontSize=28 ForegroundColor="$text-primary") (Grid Name="TestimonialsGrid" Columns="auto auto auto" Spacing=20 (LayerPrefabPlaceholder Name="Quote1" - PlaceholderTemplate="Example/Prefabs/Quote" + PlaceholderTemplate="Example/Prefabs/Quote.lisp" Card.PrimaryColor="$accent" Card.Title="— Alex R." Card.Subtitle="Lead Engineer" Card.Body="ORCA lets us ship native-quality UI on every platform from a single XML source. It changed how we build products.") (LayerPrefabPlaceholder Name="Quote2" - PlaceholderTemplate="Example/Prefabs/Quote" + PlaceholderTemplate="Example/Prefabs/Quote.lisp" Card.PrimaryColor="$accent-green" Card.Title="— Sam T." Card.Subtitle="UI Architect" Card.Body="The declarative layout system is a joy to work with. Our designers write XML and developers hook up the logic — perfect split.") (LayerPrefabPlaceholder Name="Quote3" - PlaceholderTemplate="Example/Prefabs/Quote" + PlaceholderTemplate="Example/Prefabs/Quote.lisp" Card.PrimaryColor="$accent-blue" Card.Title="— Jordan M." Card.Subtitle="Game Developer" Card.Body="Sub-millisecond frame times even on mid-range hardware. We ditched our old HTML renderer the day we tried ORCA."))) @@ -413,7 +413,7 @@ FontSize=14 ForegroundColor="$white" BackgroundColor="$accent" Padding=16 Width=140 Height=44 TextHorizontalAlignment="Center" TextVerticalAlignment="Center" - LeftButtonUp="{ShowModalAction Example/Screens/GetStartedPopup}") + LeftButtonUp="{ShowModalAction Example/Screens/GetStartedPopup.lisp}") (TextBlock Name="CtaButtonSecondary" Text="Learn more" FontSize=14 ForegroundColor="$text-secondary" BackgroundColor="$card-background" Padding=16 diff --git a/samples/Example/package.lua b/samples/Example/package.lua index 0b849427..68185ee0 100644 --- a/samples/Example/package.lua +++ b/samples/Example/package.lua @@ -1,5 +1,5 @@ Name = "Example" -StartupScreen = "Example/Screens/Application" +StartupScreen = "Example/Screens/Application.lisp" WindowWidth = 1024 WindowHeight = 768 ScreenLibrary = { IsExternal = true } From 6f377e775c032dc91b9ed1699a9fc87c761f3a24 Mon Sep 17 00:00:00 2001 From: "copilot-swe-agent[bot]" <198982749+Copilot@users.noreply.github.com> Date: Sat, 23 May 2026 13:21:09 +0000 Subject: [PATCH 10/11] fix: rename Example metric lisp prefab and references Agent-Logs-Url: https://github.com/corepunch/orca/sessions/97c3a778-a987-4569-a8c0-52d51838ffc7 Co-authored-by: corepunch <83646194+corepunch@users.noreply.github.com> --- samples/Example/Prefabs/{Mertic.lisp => Metric.lisp} | 0 samples/Example/Screens/Application.lisp | 6 +++--- 2 files changed, 3 insertions(+), 3 deletions(-) rename samples/Example/Prefabs/{Mertic.lisp => Metric.lisp} (100%) diff --git a/samples/Example/Prefabs/Mertic.lisp b/samples/Example/Prefabs/Metric.lisp similarity index 100% rename from samples/Example/Prefabs/Mertic.lisp rename to samples/Example/Prefabs/Metric.lisp diff --git a/samples/Example/Screens/Application.lisp b/samples/Example/Screens/Application.lisp index 10446376..10e5c1c4 100644 --- a/samples/Example/Screens/Application.lisp +++ b/samples/Example/Screens/Application.lisp @@ -44,17 +44,17 @@ (Node2D Name="MetricSpacer" Height=40) (Grid Name="Metrics" Columns="auto auto auto" Spacing=16 (LayerPrefabPlaceholder Name="MetricUsers" - PlaceholderTemplate="Example/Prefabs/Mertic.lisp" + PlaceholderTemplate="Example/Prefabs/Metric.lisp" Card.Title="2.4 M" Card.Subtitle="Active users" Card.PrimaryColor="$accent") (LayerPrefabPlaceholder Name="MetricUptime" - PlaceholderTemplate="Example/Prefabs/Mertic.lisp" + PlaceholderTemplate="Example/Prefabs/Metric.lisp" Card.Title="99.9 %" Card.Subtitle="Uptime SLA" Card.PrimaryColor="$accent-green") (LayerPrefabPlaceholder Name="MetricFrame" - PlaceholderTemplate="Example/Prefabs/Mertic.lisp" + PlaceholderTemplate="Example/Prefabs/Metric.lisp" Card.Title="1.2 ms" Card.Subtitle="Frame time" Card.PrimaryColor="$accent-blue"))) From 59cca86a30f4443b00ca3ff66af53c3dec7ea5ab Mon Sep 17 00:00:00 2001 From: Igor Chernakov Date: Tue, 30 Jun 2026 09:31:14 +0200 Subject: [PATCH 11/11] Add (Binding "path") alias for (bind "path") in Lisp loader MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Both now translate to {path}: (bind "Card.Title") → {Card.Title} (Binding "Card.Title") → {Card.Title} (new alias) Example usage: (TextBlock Name="Title" Text=(Binding "Card.Title")) (TextBlock Name="Value" ForegroundColor=(Binding "Card.PrimaryColor")) --- source/filesystem/fs_lisp.c | 5 +++-- tests/test_lisp.lua | 3 +++ 2 files changed, 6 insertions(+), 2 deletions(-) diff --git a/source/filesystem/fs_lisp.c b/source/filesystem/fs_lisp.c index d1ca5120..5a373f9b 100644 --- a/source/filesystem/fs_lisp.c +++ b/source/filesystem/fs_lisp.c @@ -10,6 +10,7 @@ * Binding expressions are translated from Lisp to the ORCA expression string * recognised by Token_Create / OBJ_AttachPropertyProgram: * (bind "Node.ActualWidth") → {Node.ActualWidth} + * (Binding "Node.ActualWidth") → {Node.ActualWidth} (alias) * (if a b c) → IF(a, b, c) * (step n x) → STEP(n, x) * (vector2 x y) → Vector2(x, y) @@ -173,8 +174,8 @@ translate_list_body(struct lsp_lex *l, struct sbuf *out) snprintf(fname, sizeof(fname), "%s", l->str); lsp_next(l); - /* (bind "PropPath") → {PropPath} */ - if (!strcasecmp(fname, "bind")) { + /* (bind "PropPath") or (Binding "PropPath") → {PropPath} */ + if (!strcasecmp(fname, "bind") || !strcasecmp(fname, "Binding")) { if (l->tok == TOK_STRING) { sbuf_putc(out, '{'); sbuf_put(out, l->str); diff --git a/tests/test_lisp.lua b/tests/test_lisp.lua index fbc56fba..cc66bc31 100644 --- a/tests/test_lisp.lua +++ b/tests/test_lisp.lua @@ -226,6 +226,9 @@ local function test_binding_translation() (:bind Target="Grid.Columns" (if (step 640 (bind "Node.ActualWidth")) "auto auto" "auto"))) ]], + -- (Binding "path") alias + [[(TextBlock Name="T4" Text=(Binding "Card.Title")) + ]], } for i, src in ipairs(fixtures) do