selene_xaml

MS-XAML-2017 object-mapping profile and typed View generation for Selene

XAML
UI
ECS
Game Engine
moon add KKKIIO/selene_xaml@0.37.5
Download zip
Author
Version
0.37.5
License
Apache-2.0
Last updated
18 days ago
Downloads
4

Dependencies

README

#Selene XAML

Selene XAML pixel-art inventory example

KKKIIO/selene_xaml compiles a selected MS-XAML-2017 object-mapping profile and a MoonBit ViewModel contract into a generated, typed View package for Selene.

The exact Profile 1 surface and deviations are recorded in docs/ms-xaml-conformance.md.

#Installation

The generated View runtime is part of KKKIIO/selene. Install the native compiler CLI from this source checkout:

git clone https://github.com/kkkiio/selene.git cd selene/selene-xaml moon install ./src/cmd/selene-xaml

Add Selene to the consuming application's moon.mod:

import {
"KKKIIO/selene@0.37.3",
}

#Usage

The complete command reference is available in the docs/cli usage documentation.

Keep domain state and presentation data at an explicit UI boundary. A UI package projects its domain Model into values declared by a small ViewModel package. Selene XAML reads that package's generated .mbti and owns the complete sibling View package:

counter/ ├── moon.pkg ├── counter.mbt ├── counter.xaml ├── view_model/ │ ├── moon.pkg │ ├── types.mbt │ └── pkg.generated.mbti └── view/ ├── moon.pkg ├── view.generated.mbt ├── view.generated.mbt.map.json └── pkg.generated.mbti

flowchart LR Model["Domain Model"] --> UI["UI package"] VM["ViewModel package"] --> UI VM --> MBTI["moon info: .mbti"] XAML["XAML"] --> Generator["selene-xaml"] MBTI --> Generator Generator --> View["Generated View package"] UI -->|"ViewModel value"| View View -->|"Typed Action"| UI View --> Host["Selene xaml_view"]

The ViewModel package contains presentation fields and enums. Formatting, filtering, sorting, layout decisions, and other domain-to-presentation work belong to the UI package and are materialized before calling the generated View. XAML bindings read public ViewModel fields. Small stateless presentation transforms may remain as public unary functions beside the ViewModel:

pub fn emphasize(label : String) -> String {
"[ " + label + " ]"
}

<!-- counter.xaml --> <Flex xmlns="urn:selene:xaml:ui" xmlns:x="http://schemas.microsoft.com/winfx/2006/xaml" xmlns:viewModel="moonbit:your_game/counter/view_model" x:Class="CounterView" DataType="viewModel:CounterViewModel" Direction="Column" Gap="12" Padding="16"> <Text Text="{Binding Path=label, Converter={x:Static viewModel:emphasize}}" FontSize="24" Color="#f0ddbd" /> <Button x:Name="increment" OnClick="increment"> <Text Text="+1" Color="#f0ddbd" /> </Button> </Flex>

moon -C your_game info counter/view_model --target js selene-xaml generate \ your_game/counter/counter.xaml \ --mbti your_game/counter/view_model/pkg.generated.mbti \ --out-dir your_game/counter/view

let entity = @entity.Entity()
@counter_view.CounterView::mount(
entity,
@view_model.CounterViewModel::{ label: "0" },
)

for envelope in @event.EventReader().read(@counter_view.action_event_bus) {
match envelope.action {
Increment => {
let model = @counter_view.CounterView::view_models()[entity]
model.label = "1"
@counter_view.CounterView::refresh_label(entity)
}
}
}
@visibility.visibilities()[entity] = @visibility.Hidden
@visibility.visibilities()[entity] = @visibility.Visible
@counter_view.CounterView::unmount(entity)
entity.destroy()

Here CounterViewModel.label is declared mut, so the generator emits refresh_label. The generated package exposes its ViewModel ECS component Map, full and dependency-directed refresh, mount, unmount, replace, a typed action_event_bus, and a plugin that routes Selene UI events. Visibility is caller-owned Selene component state: setting the mounted root to Hidden or Visible preserves the mounted layout and View state without a View-specific lifecycle call. unmount releases View-owned descendants without destroying the caller-owned root. Destroying that root also ends the generated View lifetime. Its source set is fixed: moon.pkg, view.generated.mbt, and view.generated.mbt.map.json. pkg.generated.mbti is produced later by moon info; Selene XAML does not write MoonBit interfaces. Converter={x:Static package:function} emits a direct unary MoonBit call and imports its moonbit: namespace package. Selene does not register converter objects or implement ConvertBack; the generated package's MoonBit compilation checks the function signature and result type. The exact component ownership, refresh generation, keyed collection, and nested View semantics are documented in docs/view-refresh-api-proposal.md.

Package-based XAML elements can reference either another generated View or a handwritten Custom View. A Custom View implements mount, replace, and component_plugin, owns its private components and descendants, and receives ordinary XAML layout, clipping, focus, activity, and action properties on its caller-owned root. It may use ordinary Selene Entity components, package-owned component maps, and systems without adopting a framework-defined View state container. See docs/selene-xaml-vocabulary.md for the complete contract.

examples/inventory is a complete WebGPU demo with responsive layout, item lists, and equipment slots.