Skip to main content

compose_rust/
schema.rs

1//! Closed Rust-side schema and metadata used for Kotlin code generation.
2
3#[derive(Clone, Copy, Debug, Eq, PartialEq)]
4pub struct EnumVariantSchema {
5    pub name: &'static str,
6    pub tag: u16,
7}
8
9#[derive(Clone, Copy, Debug, Eq, PartialEq)]
10pub enum FieldType {
11    Float,
12    U32,
13    U64,
14    /// A `Paint`, either a `ColorRole` or a literal ARGB `Color`.
15    Paint,
16    /// A role enum, named so codegen can emit the matching Kotlin type.
17    Role(&'static str),
18}
19
20/// Which half of which `(first, second)` word a modifier field occupies.
21///
22/// Two `f32` share one `u64`: the low 32 bits hold the first value.
23#[derive(Clone, Copy, Debug, Eq, PartialEq)]
24pub enum FieldSlot {
25    FirstLow,
26    FirstHigh,
27    First,
28    SecondLow,
29    SecondHigh,
30    Second,
31}
32
33#[derive(Clone, Copy, Debug, Eq, PartialEq)]
34pub struct FieldSchema {
35    pub name: &'static str,
36    pub ty: FieldType,
37    pub slot: FieldSlot,
38}
39
40#[derive(Clone, Copy, Debug, Eq, PartialEq)]
41pub struct VariantSchema {
42    pub name: &'static str,
43    pub tag: u16,
44    pub fields: &'static [FieldSchema],
45}
46
47#[derive(Clone, Copy, Debug, Eq, PartialEq)]
48pub enum EventPayloadType {
49    None,
50    Text,
51    ProtocolError,
52    KeyDown,
53    Range,
54    /// One `f64`. Every value-carrying widget shares it, so the same concept has one
55    /// name on the wire whether the value counts days or slides between two ends.
56    Double,
57    WindowSize,
58    /// One `u16`: the tag of the design system the Renderer resolved the theme to.
59    DesignSystem,
60    /// A `u32` saying what was pressed (0 for the body, 1 or 2 for an action button), then
61    /// the notification's key as a string reference.
62    NotificationActivation,
63    /// One `u32`: the notification permission the Renderer last saw.
64    NotificationPermission,
65}
66
67#[derive(Clone, Copy, Debug, Eq, PartialEq)]
68pub struct EventSchema {
69    pub name: &'static str,
70    pub tag: u16,
71    pub payload: EventPayloadType,
72}
73
74/// Canonical schema text. Variant order is wire-significant and must only be appended to.
75pub const SCHEMA_DESCRIPTOR: &str = concat!(
76    "compose-rust/v1;",
77    "widgets=Column,Row,Box,Text,TextField,Button,Spacer,LazyColumn,ScrollColumn,Image,Icon,Checkbox,RadioButton,Switch,Slider,ProgressIndicator,Divider,Card,Surface,Dialog,Menu,Tabs,TopAppBar,LazyRow,Tooltip,Canvas,DatePicker,TimePicker,Dropdown,Navigation,NavigationItem,Sheet,Scaffold,ScaffoldSlot,LazyGrid,FileDropTarget,ScrollRow,Chip,FloatingAction,Badge,SelectionContainer,SplitPane;",
78    "properties=text,placeholder,enabled,multiline,on_click,on_value_change,on_submit,on_focus_lost,on_key_down,item_count,item_key,on_range_requested,type_role,font_size,font_weight,line_height,letter_spacing,color,text_align,max_lines,overflow,arrangement,spacing,space_role,alignment,variant,asset,checked,steps,determinate,circular,vertical,open,on_dismiss,selected_index,commands,value,min,max,icon,slot,columns,min_column_width,spans,on_files_entered,on_files_dropped,section,count,collapsible;",
79    "modifiers=Empty,Padding,FillMaxWidth,FillMaxHeight,Width,Height,Size,Background,Clickable,PaddingRole,PaddingEach,Weight,Shape,ShapeRole,Border,Elevation,ObserveSize,Motion,Material;",
80    "keys=Enter;",
81    "events=Clicked,TextChanged,TextSubmitted,FocusLost,ProtocolError,KeyDown,RangeRequested,ValueChanged,WindowSizeChanged,DesignSystemResolved,FilesEntered,FilesDropped,NotificationActivated,NotificationPermissionChanged;",
82    "windowsizeclasses=Compact,Medium,Expanded;",
83    "windowheightclasses=Compact,Medium,Expanded;",
84    "commands=Create,SetProp,SetModifier,Insert,Move,Remove,SetText,AppendText,SetTheme,SetWindow,RegisterAsset,ReleaseAsset,ShowMessage,PostNotification,WithdrawNotification,RequestNotificationPermission"
85);
86
87const fn hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
88    let mut index = 0;
89    while index < bytes.len() {
90        hash ^= bytes[index] as u64;
91        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
92        index += 1;
93    }
94    hash
95}
96
97const fn hash_enum_schema(mut hash: u64, schema: &[EnumVariantSchema]) -> u64 {
98    let mut index = 0;
99    while index < schema.len() {
100        hash = hash_bytes(hash, schema[index].name.as_bytes());
101        hash = hash_bytes(hash, &schema[index].tag.to_le_bytes());
102        index += 1;
103    }
104    hash
105}
106
107const fn schema_hash() -> u64 {
108    let mut hash = hash_bytes(0xcbf2_9ce4_8422_2325_u64, SCHEMA_DESCRIPTOR.as_bytes());
109    hash = hash_bytes(hash, crate::extensions::SCHEMA_DESCRIPTOR.as_bytes());
110    hash = hash_enum_schema(hash, WIDGET_SCHEMA);
111    hash = hash_enum_schema(hash, PROPERTY_SCHEMA);
112    hash = hash_enum_schema(hash, KEY_SCHEMA);
113    hash = hash_enum_schema(hash, WINDOW_SIZE_CLASS_SCHEMA);
114    hash = hash_enum_schema(hash, WINDOW_HEIGHT_CLASS_SCHEMA);
115    let mut role_index = 0;
116    while role_index < ROLE_ENUM_SCHEMA.len() {
117        hash = hash_bytes(hash, ROLE_ENUM_SCHEMA[role_index].name.as_bytes());
118        hash = hash_enum_schema(hash, ROLE_ENUM_SCHEMA[role_index].variants);
119        role_index += 1;
120    }
121    let mut index = 0;
122    while index < MODIFIER_SCHEMA.len() {
123        let modifier = MODIFIER_SCHEMA[index];
124        hash = hash_bytes(hash, modifier.name.as_bytes());
125        hash = hash_bytes(hash, &modifier.tag.to_le_bytes());
126        let mut field_index = 0;
127        while field_index < modifier.fields.len() {
128            let field = modifier.fields[field_index];
129            hash = hash_bytes(hash, field.name.as_bytes());
130            hash = hash_bytes(
131                hash,
132                &[match field.ty {
133                    FieldType::Float => 1,
134                    FieldType::U32 => 2,
135                    FieldType::U64 => 3,
136                    FieldType::Paint => 4,
137                    FieldType::Role(_) => 5,
138                }],
139            );
140            if let FieldType::Role(role) = field.ty {
141                hash = hash_bytes(hash, role.as_bytes());
142            }
143            hash = hash_bytes(
144                hash,
145                &[match field.slot {
146                    FieldSlot::FirstLow => 0,
147                    FieldSlot::FirstHigh => 1,
148                    FieldSlot::First => 2,
149                    FieldSlot::SecondLow => 3,
150                    FieldSlot::SecondHigh => 4,
151                    FieldSlot::Second => 5,
152                }],
153            );
154            field_index += 1;
155        }
156        index += 1;
157    }
158    index = 0;
159    while index < crate::drawing::DRAW_COMMAND_SCHEMA.len() {
160        let command = crate::drawing::DRAW_COMMAND_SCHEMA[index];
161        hash = hash_bytes(hash, command.name.as_bytes());
162        hash = hash_bytes(hash, &command.tag.to_le_bytes());
163        let mut field_index = 0;
164        while field_index < command.fields.len() {
165            let field = command.fields[field_index];
166            hash = hash_bytes(hash, field.name.as_bytes());
167            hash = hash_bytes(hash, &[field.word]);
168            hash = hash_bytes(
169                hash,
170                &[match field.ty {
171                    crate::drawing::DrawFieldType::Float => 1,
172                    crate::drawing::DrawFieldType::U32 => 2,
173                    crate::drawing::DrawFieldType::Role(_) => 3,
174                }],
175            );
176            if let crate::drawing::DrawFieldType::Role(role) = field.ty {
177                hash = hash_bytes(hash, role.as_bytes());
178            }
179            field_index += 1;
180        }
181        index += 1;
182    }
183    index = 0;
184    while index < EVENT_SCHEMA.len() {
185        let event = EVENT_SCHEMA[index];
186        hash = hash_bytes(hash, event.name.as_bytes());
187        hash = hash_bytes(hash, &event.tag.to_le_bytes());
188        hash = hash_bytes(
189            hash,
190            &[match event.payload {
191                EventPayloadType::None => 0,
192                EventPayloadType::Text => 1,
193                EventPayloadType::ProtocolError => 2,
194                EventPayloadType::KeyDown => 3,
195                EventPayloadType::Range => 4,
196                EventPayloadType::Double => 5,
197                EventPayloadType::WindowSize => 6,
198                EventPayloadType::DesignSystem => 7,
199                EventPayloadType::NotificationActivation => 8,
200                EventPayloadType::NotificationPermission => 9,
201            }],
202        );
203        index += 1;
204    }
205    hash
206}
207
208/// Stable handshake hash of the complete ordered schema metadata.
209pub const SCHEMA_HASH: u64 = schema_hash();
210pub const PROTOCOL_VERSION: u16 = 1;
211
212fn wire_name_eq(input: &str, schema_name: &str) -> bool {
213    input
214        .bytes()
215        .filter(|byte| *byte != b'_')
216        .map(|byte| byte.to_ascii_lowercase())
217        .eq(schema_name.bytes().map(|byte| byte.to_ascii_lowercase()))
218}
219
220macro_rules! define_wire_enum {
221    ($schema:ident, $name:ident { $($variant:ident = $tag:literal),+ $(,)? }) => {
222        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
223        #[repr(u16)]
224        pub enum $name {
225            $($variant = $tag),+
226        }
227
228        pub const $schema: &[EnumVariantSchema] = &[
229            $(EnumVariantSchema { name: stringify!($variant), tag: $tag }),+
230        ];
231
232        impl From<$name> for u16 {
233            fn from(value: $name) -> Self {
234                value as Self
235            }
236        }
237
238        impl TryFrom<u16> for $name {
239            type Error = ();
240
241            // Spelled `()` rather than `Self::Error`: a `ColorRole::Error` variant would
242            // otherwise make the associated item ambiguous.
243            fn try_from(value: u16) -> Result<Self, ()> {
244                match value {
245                    $($tag => Ok(Self::$variant),)+
246                    _ => Err(()),
247                }
248            }
249        }
250
251        impl $name {
252            #[allow(dead_code)]
253            #[doc(hidden)]
254            #[allow(clippy::result_unit_err)]
255            pub fn from_name(value: &str) -> Result<Self, ()> {
256                $(if wire_name_eq(value, stringify!($variant)) {
257                    return Ok(Self::$variant);
258                })+
259                Err(())
260            }
261        }
262    };
263}
264
265crate::extensions::define_widget_schema_with_extensions!(define_wire_enum; WIDGET_SCHEMA, WidgetKind {
266    Column = 1,
267    Row = 2,
268    Box = 3,
269    Text = 4,
270    TextField = 5,
271    Button = 6,
272    Spacer = 7,
273    LazyColumn = 8,
274    ScrollColumn = 9,
275    Image = 10,
276    Icon = 11,
277    // The selection controls and the indicators. Each one emits a state and a role and
278    // nothing about how it is drawn: the tick, the track, the thumb and the sweep of an
279    // indeterminate bar are the design system's.
280    Checkbox = 12,
281    RadioButton = 13,
282    Switch = 14,
283    Slider = 15,
284    ProgressIndicator = 16,
285    Divider = 17,
286    Card = 18,
287    Surface = 19,
288    Dialog = 20,
289    Menu = 21,
290    Tabs = 22,
291    TopAppBar = 23,
292    LazyRow = 24,
293    Tooltip = 25,
294    // Drawing commands instead of child nodes. The command list is its only property.
295    Canvas = 26,
296    DatePicker = 27,
297    TimePicker = 28,
298    Dropdown = 29,
299    // One declaration, three presentations. The Renderer picks a bottom bar, a rail or a
300    // permanent drawer from the width it has already measured, so the same tree looks
301    // native on a phone and on a desktop without the Host branching on the size class.
302    Navigation = 30,
303    // One destination. Its label and its icon are properties rather than children,
304    // because a child tree would fix the arrangement the presentations need to differ in.
305    NavigationItem = 31,
306    // A temporary surface that slides in from an edge of the screen. Which edge is the
307    // Renderer's decision, for the same reason the navigation presentation is.
308    Sheet = 32,
309    // The screen's frame. The application fills slots and the Renderer decides what each
310    // one becomes on this platform and at this width, which is the same decision
311    // `Navigation` already makes for itself, widened to the whole frame.
312    Scaffold = 33,
313    // One filled slot. A wrapper rather than a property on the slot's own root, because
314    // an application may put any tree in a slot and the Renderer has to know which slot
315    // it is looking at without reading into it.
316    ScaffoldSlot = 34,
317    // A lazy grid. The windowing protocol is the list's, unchanged: the Renderer asks for a
318    // range of items and the Host materialises exactly that range. What the grid adds is
319    // that the Renderer rounds the range it asks for to whole rows, because a row is what
320    // it lays out, and how many items a row holds is its own decision when the columns
321    // were given as a minimum width rather than a count.
322    LazyGrid = 35,
323    // A place files may be dropped. Being this widget is the willingness: a node that is
324    // not one is never offered as a target, so the platform shows no drop cursor over it
325    // and nothing is reported.
326    FileDropTarget = 36,
327    // The whole content with a horizontal scroll, which is a ScrollColumn turned on its
328    // side. Every child is materialised and the scroll position is the Renderer's, so
329    // nothing about scrolling crosses the boundary; a long horizontal list is a LazyRow.
330    ScrollRow = 37,
331    // A small token that is chosen or filters. It carries its label and whether it is
332    // chosen, and the chosen state is the Host's: it changes only because the Host's own
333    // click handler changed it. The shape is the design system's.
334    Chip = 38,
335    // The one action a screen is about. It carries an icon, a label and a click. Where it
336    // goes and what it is drawn as is the design system's: a disc floating over the page
337    // is one system's answer, a plus at the trailing end of the bar is another's, and an
338    // accent button at the head of the command bar is a third.
339    FloatingAction = 39,
340    // A small mark on something else that says how many, that there is something new, or
341    // one short word. With one child it is attached to that child; with none it stands on
342    // its own, at the end of a row. Where it sits on the child, its shape, and how a large
343    // count is shortened are the design system's: nothing here can ask for a corner or a
344    // ceiling.
345    Badge = 40,
346    // A region whose text can be dragged over and copied, as one selection across every
347    // Text inside it. Text outside one cannot be selected. The selection and the copy are
348    // the Renderer's entirely, so nothing about either crosses the boundary.
349    SelectionContainer = 41,
350    // Two panes side by side, a side pane and a body, with a divider the user drags to
351    // change the side pane's width. Exactly two children, the side pane first.
352    //
353    // The drag is the Renderer's, like a scroll position: nothing crosses the boundary
354    // while it moves, and the width it ends on is reported once through the value change
355    // event. How the divider looks and how far its grip reaches, the side pane's default
356    // width, the body's minimum, and whether a narrow place shows the two panes side by
357    // side, with the side pane laid over the body, or one at a time, are the design
358    // system's answers.
359    SplitPane = 42,
360});
361
362#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
363#[repr(u8)]
364pub enum LoopMode {
365    #[default]
366    Renderer = 0,
367    Platform = 1,
368}
369
370#[derive(Clone, Copy, Debug, Eq, PartialEq)]
371pub struct Selection {
372    pub start: u32,
373    pub end: u32,
374}
375
376// Compose key identities supported by the M0 schema.
377define_wire_enum!(KEY_SCHEMA, Key {
378    Enter = 1,
379});
380
381define_wire_enum!(WINDOW_SIZE_CLASS_SCHEMA, WindowSizeClass {
382    Compact = 0,
383    Medium = 1,
384    Expanded = 2,
385});
386
387impl WindowSizeClass {
388    /// The width in dp at which `Medium` begins, and the width at which `Expanded` does.
389    ///
390    /// These are the Material 3 window size class boundaries. Using different numbers
391    /// would make the Compose components adapt at one width and the Rust layout branch at
392    /// another, and that mismatch is visible on screen.
393    pub const MEDIUM_MIN_WIDTH_DP: f32 = 600.0;
394    pub const EXPANDED_MIN_WIDTH_DP: f32 = 840.0;
395
396    /// The class a window of this width belongs to. Height does not take part.
397    pub fn from_width_dp(width_dp: f32) -> Self {
398        if width_dp >= Self::EXPANDED_MIN_WIDTH_DP {
399            Self::Expanded
400        } else if width_dp >= Self::MEDIUM_MIN_WIDTH_DP {
401            Self::Medium
402        } else {
403            Self::Compact
404        }
405    }
406}
407
408define_wire_enum!(WINDOW_HEIGHT_CLASS_SCHEMA, WindowHeightClass {
409    Compact = 0,
410    Medium = 1,
411    Expanded = 2,
412});
413
414impl WindowHeightClass {
415    /// The height in dp at which `Medium` begins, and the height at which `Expanded` does.
416    ///
417    /// The same published table the width boundaries came from, so a layout that folds
418    /// when it runs out of vertical room folds at the height Compose's own components
419    /// treat as short.
420    pub const MEDIUM_MIN_HEIGHT_DP: f32 = 480.0;
421    pub const EXPANDED_MIN_HEIGHT_DP: f32 = 900.0;
422
423    /// The class a window of this height belongs to. Width does not take part.
424    pub fn from_height_dp(height_dp: f32) -> Self {
425        if height_dp >= Self::EXPANDED_MIN_HEIGHT_DP {
426            Self::Expanded
427        } else if height_dp >= Self::MEDIUM_MIN_HEIGHT_DP {
428            Self::Medium
429        } else {
430            Self::Compact
431        }
432    }
433}
434
435/// A role enum that codegen mirrors into Kotlin.
436#[derive(Clone, Copy, Debug, Eq, PartialEq)]
437pub struct RoleEnumSchema {
438    pub name: &'static str,
439    pub variants: &'static [EnumVariantSchema],
440}
441
442// An opaque ARGB colour. Gradients and image brushes are deliberately absent: they do not
443// fit the two words a modifier variant has, and they would need resources to cross the
444// boundary, which the fixed-layout records cannot carry.
445#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
446#[repr(transparent)]
447pub struct Color(pub u32);
448
449impl Color {
450    /// An opaque colour from a 24-bit `0xRRGGBB` value.
451    pub const fn rgb(value: u32) -> Self {
452        Self(0xff00_0000 | (value & 0x00ff_ffff))
453    }
454
455    /// A colour from a full 32-bit `0xAARRGGBB` value.
456    pub const fn argb(value: u32) -> Self {
457        Self(value)
458    }
459
460    pub const fn to_argb(self) -> u32 {
461        self.0
462    }
463}
464
465// Semantic colour slots. The design system resolves them, inside the Renderer.
466define_wire_enum!(COLOR_ROLE_SCHEMA, ColorRole {
467    Primary = 1,
468    OnPrimary = 2,
469    Secondary = 3,
470    OnSecondary = 4,
471    Surface = 5,
472    OnSurface = 6,
473    SurfaceVariant = 7,
474    OnSurfaceVariant = 8,
475    Background = 9,
476    OnBackground = 10,
477    Outline = 11,
478    OutlineVariant = 12,
479    Error = 13,
480    OnError = 14,
481    // The layer a panel is made of: a thing raised off the page, holding the page's own
482    // reading ink. It exists because `Surface` cannot do that job everywhere. Material 3
483    // gives `Surface` and `Background` one value on purpose and expresses depth through
484    // tonal containers instead, so a panel filled with `Surface` on a page of `Background`
485    // is drawn, in the right colour, and is invisible. `SurfaceVariant` is not the answer
486    // either: its ink pair is the muted secondary ink, so a panel holding primary content
487    // would have to set that content half faded. This is the one role every design system
488    // promises to make visible against the page, and `OnSurface` is what reads on it.
489    SurfaceContainer = 15,
490    // The third accent, and a quiet fill for each of the three.
491    //
492    // Two accents and a page say a button, a bar and a heading. They cannot say a grid of
493    // subject tiles where the subject is the colour, a mood picker, or a panel of costs
494    // beside a panel of totals. Those screens need fills that read as relatives of one
495    // another, are not reading surfaces, and are quiet enough that body text sits on them.
496    //
497    // A container is a colour in the table rather than its accent at a lower opacity.
498    // Opacity only means something once you know what is behind it, and a role has to
499    // answer before anyone knows that. So each container is its own value and carries its
500    // own ink, held to the body-text bound rather than the label bound, because the reason
501    // it exists is that paragraphs land on it.
502    //
503    // The three containers are not required to be told apart from each other. Material 3's
504    // baseline primary and secondary containers are neighbouring tones of one palette, and
505    // that is its published scheme, not a mistake. A caller who needs three fills that
506    // separate at a glance reaches for the tertiary pair.
507    Tertiary = 16,
508    OnTertiary = 17,
509    PrimaryContainer = 18,
510    OnPrimaryContainer = 19,
511    SecondaryContainer = 20,
512    OnSecondaryContainer = 21,
513    TertiaryContainer = 22,
514    OnTertiaryContainer = 23,
515    // What a highlighter paints code with. A conversation shows code blocks and diffs, and
516    // with only literals to paint them the same grey comment would be stamped into seven
517    // systems and two schemes, which is how a light comment grey disappears on a dark page.
518    // So a token kind is a role like any other, and each system answers it from the code
519    // editor of its own platform.
520    //
521    // The split follows what tree-sitter's highlight names, VS Code's semantic token kinds
522    // and TextMate scopes all agree on. Each of those divides more finely, and the finer
523    // names fold into one of these (`keyword.return` is a keyword). A token that fits none
524    // is left in the body ink rather than given a colour of its own.
525    //
526    // The fifteen syntax inks hold the body-text bound on the code block's panel, comments
527    // included: a comment can be quieter in hue and lightness, but one nobody can read is
528    // a comment that is not there.
529    SyntaxKeyword = 24,
530    SyntaxString = 25,
531    SyntaxComment = 26,
532    SyntaxNumber = 27,
533    SyntaxConstant = 28,
534    SyntaxType = 29,
535    SyntaxFunction = 30,
536    SyntaxVariable = 31,
537    SyntaxProperty = 32,
538    SyntaxOperator = 33,
539    SyntaxPunctuation = 34,
540    SyntaxTag = 35,
541    SyntaxAttribute = 36,
542    SyntaxEscape = 37,
543    SyntaxMacro = 38,
544    // A diff. The ink marks an added or removed line and its text; the container is the
545    // whole line's background, drawn as the line node's own background; the emphasis is
546    // the background behind the words that actually changed, drawn by the span. Added and
547    // removed are never told apart by colour alone: the `+` and `-` at the head of a line
548    // stay, because the two backgrounds can look the same to a red-green colour blind
549    // reader.
550    DiffAdded = 39,
551    DiffRemoved = 40,
552    DiffModified = 41,
553    DiffAddedContainer = 42,
554    DiffRemovedContainer = 43,
555    DiffAddedEmphasis = 44,
556    DiffRemovedEmphasis = 45,
557});
558
559/// How many colour roles there are, which is how many rows every token table has and how
560/// many slots a palette carries per scheme.
561pub const COLOR_ROLE_COUNT: usize = 45;
562
563// The nine-rung type ladder every supported design system maps onto.
564define_wire_enum!(TYPE_ROLE_SCHEMA, TypeRole {
565    Display = 1,
566    Headline = 2,
567    Title = 3,
568    Subtitle = 4,
569    Body = 5,
570    BodyStrong = 6,
571    Label = 7,
572    Caption = 8,
573    Mono = 9,
574});
575
576// Corner roles. The radius is the design system's decision.
577define_wire_enum!(SHAPE_ROLE_SCHEMA, ShapeRole {
578    None = 1,
579    ExtraSmall = 2,
580    Small = 3,
581    Medium = 4,
582    Large = 5,
583    Full = 6,
584});
585
586// How long a change takes and along which curve. A role rather than a duration, because
587// Material's emphasized curve and Cupertino's spring are different answers to the same
588// question, and a milliseconds figure from the application would settle it in the wrong
589// place. A system asked to reduce motion answers every one of these instantly.
590define_wire_enum!(MOTION_ROLE_SCHEMA, MotionRole {
591    Instant = 1,
592    Quick = 2,
593    Standard = 3,
594    Slow = 4,
595    Emphasized = 5,
596});
597
598// What a surface is made of. Four roles rather than a blur radius, because Cupertino and
599// Liquid Glass answer with blur, Material 3 with elevation and a tone laid over the
600// surface, and the GNOME and KDE systems with an opaque fill. A radius from the
601// application would be a blur instruction to systems that do not blur.
602define_wire_enum!(MATERIAL_ROLE_SCHEMA, MaterialRole {
603    Thin = 1,
604    Regular = 2,
605    Thick = 3,
606    Chrome = 4,
607});
608
609// Density roles, because dp density differs per design system.
610define_wire_enum!(SPACE_ROLE_SCHEMA, SpaceRole {
611    None = 1,
612    Xs = 2,
613    Sm = 3,
614    Md = 4,
615    Lg = 5,
616    Xl = 6,
617    Xxl = 7,
618});
619
620define_wire_enum!(TEXT_ALIGN_SCHEMA, TextAlign {
621    Start = 1,
622    Center = 2,
623    End = 3,
624    Justify = 4,
625});
626
627define_wire_enum!(TEXT_OVERFLOW_SCHEMA, TextOverflow {
628    Clip = 1,
629    Ellipsis = 2,
630    Visible = 3,
631});
632
633define_wire_enum!(ARRANGEMENT_SCHEMA, Arrangement {
634    Start = 1,
635    Center = 2,
636    End = 3,
637    SpaceBetween = 4,
638    SpaceAround = 5,
639    SpaceEvenly = 6,
640});
641
642define_wire_enum!(ALIGNMENT_SCHEMA, Alignment {
643    TopStart = 1,
644    TopCenter = 2,
645    TopEnd = 3,
646    CenterStart = 4,
647    Center = 5,
648    CenterEnd = 6,
649    BottomStart = 7,
650    BottomCenter = 8,
651    BottomEnd = 9,
652});
653
654// The neutral component variant names the design system styles.
655define_wire_enum!(BUTTON_VARIANT_SCHEMA, ButtonVariant {
656    Filled = 1,
657    Tonal = 2,
658    Outlined = 3,
659    Text = 4,
660    Operator = 5,
661});
662
663// What the bytes behind an asset id are. A kind the Renderer cannot read is a reported
664// protocol error, not a guess.
665define_wire_enum!(ASSET_KIND_SCHEMA, AssetKind {
666    Png = 1,
667    Jpeg = 2,
668    Svg = 3,
669    VectorIcon = 4,
670    // A font file, registered so that a type role can resolve to it. The bytes are the
671    // font, which is why this is an asset and not a name: a name would put the check that
672    // the font exists at run time on the reader's machine, and a missing one there is a
673    // screen in the wrong typeface with nobody to tell.
674    Font = 5,
675    // A gradient or an image fill, registered so that any Paint may name it. The bytes
676    // are the brush's own fixed layout rather than a file: nothing on any platform stores
677    // a list of stops as a document, and a format would be a parser to write and keep.
678    Brush = 6,
679});
680
681// How a brush carries on past the area it was given.
682define_wire_enum!(TILE_MODE_SCHEMA, TileMode {
683    // Stops at the edge and drags the last colour outwards, which is what a gradient
684    // filling a surface wants.
685    Clamp = 1,
686    Repeat = 2,
687    // Repeats, flipping every other copy, so the seams do not show.
688    Mirror = 3,
689});
690
691// The closed set of icon meanings. An icon is addressed by what it is for, never by a
692// system icon name: a name would defer the check that the icon exists to run time, and it
693// would pin one platform's artwork into the protocol. The role is what lets the same
694// declaration come out as SF Symbols under Cupertino and Material Symbols under Material 3.
695define_wire_enum!(ICON_ROLE_SCHEMA, IconRole {
696    Back = 1,
697    Forward = 2,
698    Close = 3,
699    Search = 4,
700    Add = 5,
701    Check = 6,
702    Settings = 7,
703    More = 8,
704    // The meanings a set of destinations needs before any other: where the application
705    // starts, the list it is mostly about, and what has arrived.
706    Home = 9,
707    List = 10,
708    Inbox = 11,
709    // What a window's own bar needs: the button that opens everything this screen does
710    // not have room for, and the one that goes back over what has already happened. Both
711    // are in the reference bar of more than one sample, and neither is a destination, so
712    // neither could be said with the three above.
713    Menu = 12,
714    History = 13,
715    // What putting the samples beside the applications they are drawn from turned up: eight
716    // meanings the set could not say. Each is a meaning rather than a picture, so a system
717    // that draws sending as a paper aeroplane is still answering `Send`.
718    //
719    // None of them is an existing role used loosely. A composer's send was drawn with
720    // `Forward` for a while, and an arrow pointing sideways says "on to the next one"
721    // rather than "send this".
722    Send = 14,
723    Mic = 15,
724    Image = 16,
725    Video = 17,
726    Library = 18,
727    Sidebar = 19,
728    Compose = 20,
729    Collapse = 21,
730});
731
732// How long a transient message stays on screen. Closed, and deliberately short: a message
733// that has to be acknowledged is a `Dialog`, so there is no indefinite duration. What the
734// two names mean in milliseconds is the design system's decision.
735define_wire_enum!(MESSAGE_DURATION_SCHEMA, MessageDuration {
736    Short = 1,
737    Long = 2,
738});
739
740// How much a notification matters. Closed for the same reason a message's duration is: what
741// it is worth in sound, in an Android channel's importance or in getting through Do Not
742// Disturb is the platform's answer, so the Host has no way to send a sound file or a
743// priority number.
744define_wire_enum!(NOTIFICATION_IMPORTANCE_SCHEMA, NotificationImportance {
745    Normal = 1,
746    Urgent = 2,
747});
748
749// Whether a notification is shown while the application's window is the active one.
750// `WhenInactive` is dropped by the Renderer when the window has focus: whether it has is
751// the Renderer's state, and a Host that wanted to judge it would need every focus change
752// sent across.
753define_wire_enum!(NOTIFICATION_PRESENTATION_SCHEMA, NotificationPresentation {
754    Always = 1,
755    WhenInactive = 2,
756});
757
758// Whether this run may show notifications. `Unsupported` is a platform or a run that has
759// no way to show one (no bundle identifier, no AppUserModelID, no notification daemon),
760// which is an answer and not an error.
761define_wire_enum!(NOTIFICATION_PERMISSION_SCHEMA, NotificationPermission {
762    NotDetermined = 1,
763    Granted = 2,
764    Denied = 3,
765    Unsupported = 4,
766});
767
768// The design systems of phase one. Later ones append variants here and nowhere else: a new
769// design system is one variant plus one token table and rule implementation in the Renderer.
770//
771// `LiquidGlass` is the language macOS 26 and iOS 26 draw, and it sits beside `Cupertino`
772// rather than replacing it: one draws grouped inset lists on a grey page, the other draws
773// translucent floating capsules, and an application that wants one does not want the
774// other. Tags are append only, so it lands after the Linux three even though it belongs
775// next to `Cupertino` in any list a reader would write.
776define_wire_enum!(DESIGN_SYSTEM_SCHEMA, DesignSystem {
777    Material3 = 1,
778    Cupertino = 2,
779    Fluent = 3,
780    Gnome = 4,
781    Breeze = 5,
782    Deepin = 6,
783    LiquidGlass = 7,
784});
785
786// Light and dark selection. `FollowSystem` leaves the choice to the Renderer, which learns
787// of a system change first and applies it without involving the Host.
788define_wire_enum!(COLOR_SCHEME_SCHEMA, ColorScheme {
789    Light = 1,
790    Dark = 2,
791    FollowSystem = 3,
792});
793
794// Which part of the screen's frame a `Scaffold` slot fills.
795//
796// What each one becomes is the Renderer's: a top bar may be the window's caption, an
797// ordinary bar under the system title bar, or a large title that shrinks as the page
798// scrolls; a bottom bar may be a bar, a rail or a permanent drawer; a floating action
799// may float, sit in the toolbar, or fold into a menu. The application says which slot
800// it filled and nothing about the answer.
801define_wire_enum!(SLOT_ROLE_SCHEMA, SlotRole {
802    TopBar = 1,
803    BottomBar = 2,
804    FloatingAction = 3,
805    Content = 4,
806});
807
808/// Every role enum codegen mirrors, in wire order. Appending is the only allowed edit.
809pub const ROLE_ENUM_SCHEMA: &[RoleEnumSchema] = &[
810    RoleEnumSchema {
811        name: "ColorRole",
812        variants: COLOR_ROLE_SCHEMA,
813    },
814    RoleEnumSchema {
815        name: "TypeRole",
816        variants: TYPE_ROLE_SCHEMA,
817    },
818    RoleEnumSchema {
819        name: "ShapeRole",
820        variants: SHAPE_ROLE_SCHEMA,
821    },
822    RoleEnumSchema {
823        name: "MotionRole",
824        variants: MOTION_ROLE_SCHEMA,
825    },
826    RoleEnumSchema {
827        name: "TileMode",
828        variants: TILE_MODE_SCHEMA,
829    },
830    RoleEnumSchema {
831        name: "MaterialRole",
832        variants: MATERIAL_ROLE_SCHEMA,
833    },
834    RoleEnumSchema {
835        name: "SpaceRole",
836        variants: SPACE_ROLE_SCHEMA,
837    },
838    RoleEnumSchema {
839        name: "TextAlign",
840        variants: TEXT_ALIGN_SCHEMA,
841    },
842    RoleEnumSchema {
843        name: "TextOverflow",
844        variants: TEXT_OVERFLOW_SCHEMA,
845    },
846    RoleEnumSchema {
847        name: "Arrangement",
848        variants: ARRANGEMENT_SCHEMA,
849    },
850    RoleEnumSchema {
851        name: "Alignment",
852        variants: ALIGNMENT_SCHEMA,
853    },
854    RoleEnumSchema {
855        name: "ButtonVariant",
856        variants: BUTTON_VARIANT_SCHEMA,
857    },
858    RoleEnumSchema {
859        name: "DesignSystem",
860        variants: DESIGN_SYSTEM_SCHEMA,
861    },
862    RoleEnumSchema {
863        name: "ColorScheme",
864        variants: COLOR_SCHEME_SCHEMA,
865    },
866    RoleEnumSchema {
867        name: "AssetKind",
868        variants: ASSET_KIND_SCHEMA,
869    },
870    RoleEnumSchema {
871        name: "IconRole",
872        variants: ICON_ROLE_SCHEMA,
873    },
874    RoleEnumSchema {
875        name: "MessageDuration",
876        variants: MESSAGE_DURATION_SCHEMA,
877    },
878    RoleEnumSchema {
879        name: "Chrome",
880        variants: CHROME_SCHEMA,
881    },
882    RoleEnumSchema {
883        name: "TitleBar",
884        variants: TITLE_BAR_SCHEMA,
885    },
886    RoleEnumSchema {
887        name: "SlotRole",
888        variants: SLOT_ROLE_SCHEMA,
889    },
890    RoleEnumSchema {
891        name: "NotificationImportance",
892        variants: NOTIFICATION_IMPORTANCE_SCHEMA,
893    },
894    RoleEnumSchema {
895        name: "NotificationPresentation",
896        variants: NOTIFICATION_PRESENTATION_SCHEMA,
897    },
898    RoleEnumSchema {
899        name: "NotificationPermission",
900        variants: NOTIFICATION_PERMISSION_SCHEMA,
901    },
902];
903
904/// Every place that takes a colour takes a `Paint`, so colour is expressed once.
905///
906/// Encoded as one `u64`: the high 32 bits are the kind, the low 32 bits are the value.
907#[derive(Clone, Copy, Debug, Eq, PartialEq)]
908pub enum Paint {
909    Role(ColorRole),
910    Literal(Color),
911    /// A registered brush: a gradient, or a picture laid out as a fill.
912    ///
913    /// An id rather than the thing itself, because a list of stops does not fit the two
914    /// words a modifier has and because a brush outlives the frame that draws it, which
915    /// is what the asset path already exists for.
916    Asset(u32),
917}
918
919const PAINT_KIND_ROLE: u64 = 1;
920const PAINT_KIND_LITERAL: u64 = 2;
921const PAINT_KIND_ASSET: u64 = 3;
922
923impl Paint {
924    pub const fn to_bits(self) -> u64 {
925        match self {
926            Self::Role(role) => (PAINT_KIND_ROLE << 32) | role as u64,
927            Self::Literal(color) => (PAINT_KIND_LITERAL << 32) | color.0 as u64,
928            Self::Asset(id) => (PAINT_KIND_ASSET << 32) | id as u64,
929        }
930    }
931
932    pub fn from_bits(bits: u64) -> Option<Self> {
933        let value = bits as u32;
934        match bits >> 32 {
935            PAINT_KIND_ROLE => Some(Self::Role(
936                ColorRole::try_from(u16::try_from(value).ok()?).ok()?,
937            )),
938            PAINT_KIND_LITERAL => Some(Self::Literal(Color(value))),
939            PAINT_KIND_ASSET => Some(Self::Asset(value)),
940            _ => None,
941        }
942    }
943}
944
945// How the window wears its title bar.
946//
947// `Modern` runs content into the title bar area and is the default, because every desktop
948// platform now expects it and a window with a separate system bar looks a decade old.
949//
950// `System` is the platform's ordinary bar, and it is kept deliberately. Some tool-shaped
951// applications want one, and more importantly it is where an application goes when
952// `Modern` turns out to be wrong on some machine: a default with no way out of it is a
953// default that strands people.
954define_wire_enum!(CHROME_SCHEMA, Chrome {
955    Modern = 1,
956    System = 2,
957});
958
959// Which of two title bars this window has. `Chrome` chooses who draws the caption; this
960// chooses, inside that, what kind of window the caption belongs to.
961//
962// What each one means is not the same on every platform, and the numbers are never the
963// Host's. On macOS the buttons sit further in and the window is rounded more; on Windows
964// and Linux the caption is a different height and the buttons stay where that platform
965// puts them.
966define_wire_enum!(TITLE_BAR_SCHEMA, TitleBar {
967    // What an application on that platform actually looks like now, which is why it is the
968    // default.
969    Normal = 1,
970    // An ordinary window. For tool applications, for the places the other reads oddly, and
971    // as the way back.
972    Simple = 2,
973});
974
975/// What an application may decide about its own window.
976///
977/// The window belongs to the Renderer, so this is short and stays short. What is here is
978/// what an application knows and the Renderer cannot guess: what the window is called and
979/// how large it should open. Nothing here names a colour or a corner, because those are
980/// the design system's, and nothing here is read more than once.
981#[derive(Clone, Copy, Debug, Eq, PartialEq)]
982pub struct Window {
983    pub chrome: Chrome,
984    /// Which of the two title bars this window has.
985    pub title_bar: TitleBar,
986    /// What the window calls itself.
987    ///
988    /// Empty means the application said nothing, and the renderer uses its own name for
989    /// it. It is not decoration: a desktop lists windows by this, and a window with no
990    /// title of its own is listed under whatever the renderer happened to be called.
991    pub title: &'static str,
992    /// The picture the window wears, as an asset id, or zero for none.
993    ///
994    /// An id rather than a path or a name, because a path is a fact about the machine the
995    /// application was built on and a name asks the toolkit to find something it may not
996    /// have. Registering the bytes and referring to them is what every other picture in
997    /// this protocol does.
998    pub icon: u32,
999    /// Zero means the Renderer chooses, which is what an application that said nothing
1000    /// gets. A size is in the same density independent pixels gestures are measured in.
1001    pub width: u16,
1002    pub height: u16,
1003    pub min_width: u16,
1004    pub min_height: u16,
1005    pub resizable: bool,
1006}
1007
1008impl Window {
1009    pub const fn new() -> Self {
1010        Self {
1011            chrome: Chrome::Modern,
1012            title_bar: TitleBar::Normal,
1013            title: "",
1014            icon: 0,
1015            width: 0,
1016            height: 0,
1017            min_width: 0,
1018            min_height: 0,
1019            resizable: true,
1020        }
1021    }
1022
1023    /// Which of the two title bars this window has.
1024    ///
1025    /// `Normal` unless it is said otherwise, because that is the window an application on
1026    /// this platform has now. What the mode is worth in pixels is the design system's, so
1027    /// nothing here is a measurement.
1028    pub const fn with_title_bar(mut self, title_bar: TitleBar) -> Self {
1029        self.title_bar = title_bar;
1030        self
1031    }
1032
1033    /// Names the window. A literal, because this is read once before the window is stood
1034    /// up and never again, so there is nothing for a computed title to change.
1035    pub const fn with_title(mut self, title: &'static str) -> Self {
1036        self.title = title;
1037        self
1038    }
1039
1040    /// Dresses the window in an asset that was registered with [`AssetKind::Png`].
1041    pub const fn with_icon(mut self, asset_id: u32) -> Self {
1042        self.icon = asset_id;
1043        self
1044    }
1045
1046    pub const fn with_chrome(mut self, chrome: Chrome) -> Self {
1047        self.chrome = chrome;
1048        self
1049    }
1050
1051    pub const fn with_size(mut self, width: u16, height: u16) -> Self {
1052        self.width = width;
1053        self.height = height;
1054        self
1055    }
1056
1057    pub const fn with_min_size(mut self, width: u16, height: u16) -> Self {
1058        self.min_width = width;
1059        self.min_height = height;
1060        self
1061    }
1062
1063    pub const fn resizable(mut self, resizable: bool) -> Self {
1064        self.resizable = resizable;
1065        self
1066    }
1067}
1068
1069impl Default for Window {
1070    fn default() -> Self {
1071        Self::new()
1072    }
1073}
1074
1075/// A design system choice plus how it reacts to the host platform.
1076#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1077pub struct Theme {
1078    pub design_system: DesignSystem,
1079    pub fallback: DesignSystem,
1080    pub color_scheme: ColorScheme,
1081    pub adaptive: bool,
1082    /// The font asset each type role resolves to, or zero for the system font.
1083    ///
1084    /// Per theme and not per node. A node that could name its own font would be a node
1085    /// deciding typography, and the whole point of roles is that it does not: an
1086    /// application changes what `Display` is made of, and every title changes with it.
1087    ///
1088    /// Indexed by the role's wire tag minus one, so the array and the enum cannot drift
1089    /// apart without the compiler saying so.
1090    pub fonts: [u32; TYPE_ROLE_COUNT],
1091    /// The application's own colours, laid over whichever design system is chosen.
1092    ///
1093    /// A reference to something that lives for the whole program, so that a theme stays
1094    /// `Copy` and can still be built in a `const`. The same palette goes onto every system
1095    /// an adaptive theme may pick, because a brand colour does not change with the
1096    /// platform. A palette built while the application runs is handed to `use_theme`,
1097    /// which owns it, so application code never has to give it that lifetime itself.
1098    pub palette: Option<&'static crate::palette::Palette>,
1099}
1100
1101/// How many type roles there are, which is how many font slots a theme carries.
1102pub const TYPE_ROLE_COUNT: usize = 9;
1103
1104impl Theme {
1105    /// The same design system on every platform.
1106    pub const fn unified(design_system: DesignSystem) -> Self {
1107        Self {
1108            design_system,
1109            fallback: design_system,
1110            color_scheme: ColorScheme::FollowSystem,
1111            adaptive: false,
1112            fonts: [0; TYPE_ROLE_COUNT],
1113            palette: None,
1114        }
1115    }
1116
1117    /// Follows the host platform. The fallback is required, so no platform is left undecided.
1118    pub const fn adaptive(fallback: DesignSystem) -> Self {
1119        Self {
1120            design_system: fallback,
1121            fallback,
1122            color_scheme: ColorScheme::FollowSystem,
1123            adaptive: true,
1124            fonts: [0; TYPE_ROLE_COUNT],
1125            palette: None,
1126        }
1127    }
1128
1129    pub const fn with_color_scheme(mut self, color_scheme: ColorScheme) -> Self {
1130        self.color_scheme = color_scheme;
1131        self
1132    }
1133
1134    /// Resolves one type role to a registered font, leaving every other role alone.
1135    ///
1136    /// The asset has to be registered before the theme that names it reaches the
1137    /// Renderer, the same way a picture does. An id that names nothing is reported and
1138    /// the role falls back to the system font, because a screen in the wrong typeface is
1139    /// better than no screen.
1140    pub const fn with_font(mut self, role: TypeRole, asset: u32) -> Self {
1141        self.fonts[role as usize - 1] = asset;
1142        self
1143    }
1144
1145    /// Lays the application's own colours over the design system.
1146    ///
1147    /// Only colour roles change. Shape, type, spacing, elevation, motion and how each
1148    /// component is drawn stay the design system's, so the same brand colour reads as
1149    /// that system's button rather than as a copy of another's. Both schemes go to the
1150    /// Renderer at once, so a switch between light and dark never asks the Host.
1151    pub const fn with_palette(mut self, palette: &'static crate::palette::Palette) -> Self {
1152        self.palette = Some(palette);
1153        self
1154    }
1155
1156    /// The font this role resolves to, or `None` for the system font.
1157    pub const fn font(&self, role: TypeRole) -> Option<u32> {
1158        match self.fonts[role as usize - 1] {
1159            0 => None,
1160            asset => Some(asset),
1161        }
1162    }
1163}
1164
1165/// Saying nothing follows the host platform, with Material 3 where the platform has no look
1166/// of its own. The default used to be `unified(Material3)`, on the theory that a default
1167/// looking the same everywhere is more predictable. In practice that meant macOS showed a
1168/// Material screen with no configuration, which is the wrong first impression for a toolkit
1169/// that claims native desktop UI, and it meant nothing on the default path ever exercised
1170/// platform adaptation. A default that can be broken without anyone noticing is not
1171/// predictability. An application that wants one design system everywhere says
1172/// `Theme::unified(..)`, which is a clearer statement of that intent than silence was.
1173impl Default for Theme {
1174    fn default() -> Self {
1175        Self::adaptive(DesignSystem::Material3)
1176    }
1177}
1178
1179#[derive(Clone, Debug, PartialEq)]
1180pub enum Modifier {
1181    Empty,
1182    Padding(f32),
1183    FillMaxWidth,
1184    FillMaxHeight,
1185    Width(f32),
1186    Height(f32),
1187    Size {
1188        width: f32,
1189        height: f32,
1190    },
1191    /// A `Paint`, not a raw ARGB. Colour has exactly one wire representation.
1192    Background(Paint),
1193    Clickable {
1194        handler_id: u64,
1195    },
1196    PaddingRole(SpaceRole),
1197    PaddingEach {
1198        start: f32,
1199        top: f32,
1200        end: f32,
1201        bottom: f32,
1202    },
1203    Weight(f32),
1204    Shape {
1205        top_start: f32,
1206        top_end: f32,
1207        bottom_end: f32,
1208        bottom_start: f32,
1209    },
1210    ShapeRole(ShapeRole),
1211    Border {
1212        width: f32,
1213        paint: Paint,
1214    },
1215    /// One dp value. How the shadow is drawn is the design system's rule: tonal lift plus a
1216    /// shadow, a wide soft shadow, or layered shadow plus a hairline stroke.
1217    Elevation(f32),
1218    /// Asks the Renderer to report this node's measured size, the way it reports the
1219    /// window's.
1220    ///
1221    /// The token is the application's own name for the node. A node id belongs to the
1222    /// Renderer and is never something the Host chose, so a screen that wants to know how
1223    /// wide one of its own containers ended up needs a name it gave itself. The Renderer
1224    /// does not read it.
1225    ObserveSize {
1226        token: u32,
1227    },
1228    /// Which curve and length this node's changes run along.
1229    ///
1230    /// The node says how important the change is, not how long it takes. Appearing,
1231    /// disappearing, a selection moving and a size changing all read this.
1232    Motion(MotionRole),
1233    /// What this node's surface is made of.
1234    ///
1235    /// A role, so that a system which blurs blurs and a system which does not lifts its
1236    /// surface instead. Nothing here claims a particular effect was achieved.
1237    Material(MaterialRole),
1238}
1239
1240const NO_FIELDS: &[FieldSchema] = &[];
1241const TOKEN_U32_FIELD: &[FieldSchema] = &[FieldSchema {
1242    name: "token",
1243    ty: FieldType::U32,
1244    slot: FieldSlot::FirstLow,
1245}];
1246const VALUE_FLOAT_FIELD: &[FieldSchema] = &[FieldSchema {
1247    name: "value",
1248    ty: FieldType::Float,
1249    slot: FieldSlot::FirstLow,
1250}];
1251const SIZE_FIELDS: &[FieldSchema] = &[
1252    FieldSchema {
1253        name: "width",
1254        ty: FieldType::Float,
1255        slot: FieldSlot::FirstLow,
1256    },
1257    FieldSchema {
1258        name: "height",
1259        ty: FieldType::Float,
1260        slot: FieldSlot::SecondLow,
1261    },
1262];
1263const PAINT_FIELD: &[FieldSchema] = &[FieldSchema {
1264    name: "paint",
1265    ty: FieldType::Paint,
1266    slot: FieldSlot::First,
1267}];
1268const HANDLER_ID_FIELD: &[FieldSchema] = &[FieldSchema {
1269    name: "handlerId",
1270    ty: FieldType::U64,
1271    slot: FieldSlot::First,
1272}];
1273const SPACE_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
1274    name: "role",
1275    ty: FieldType::Role("SpaceRole"),
1276    slot: FieldSlot::FirstLow,
1277}];
1278const SHAPE_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
1279    name: "role",
1280    ty: FieldType::Role("ShapeRole"),
1281    slot: FieldSlot::FirstLow,
1282}];
1283const MOTION_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
1284    name: "role",
1285    ty: FieldType::Role("MotionRole"),
1286    slot: FieldSlot::FirstLow,
1287}];
1288const MATERIAL_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
1289    name: "role",
1290    ty: FieldType::Role("MaterialRole"),
1291    slot: FieldSlot::FirstLow,
1292}];
1293const PADDING_EACH_FIELDS: &[FieldSchema] = &[
1294    FieldSchema {
1295        name: "start",
1296        ty: FieldType::Float,
1297        slot: FieldSlot::FirstLow,
1298    },
1299    FieldSchema {
1300        name: "top",
1301        ty: FieldType::Float,
1302        slot: FieldSlot::FirstHigh,
1303    },
1304    FieldSchema {
1305        name: "end",
1306        ty: FieldType::Float,
1307        slot: FieldSlot::SecondLow,
1308    },
1309    FieldSchema {
1310        name: "bottom",
1311        ty: FieldType::Float,
1312        slot: FieldSlot::SecondHigh,
1313    },
1314];
1315const SHAPE_FIELDS: &[FieldSchema] = &[
1316    FieldSchema {
1317        name: "topStart",
1318        ty: FieldType::Float,
1319        slot: FieldSlot::FirstLow,
1320    },
1321    FieldSchema {
1322        name: "topEnd",
1323        ty: FieldType::Float,
1324        slot: FieldSlot::FirstHigh,
1325    },
1326    FieldSchema {
1327        name: "bottomEnd",
1328        ty: FieldType::Float,
1329        slot: FieldSlot::SecondLow,
1330    },
1331    FieldSchema {
1332        name: "bottomStart",
1333        ty: FieldType::Float,
1334        slot: FieldSlot::SecondHigh,
1335    },
1336];
1337const BORDER_FIELDS: &[FieldSchema] = &[
1338    FieldSchema {
1339        name: "width",
1340        ty: FieldType::Float,
1341        slot: FieldSlot::FirstLow,
1342    },
1343    FieldSchema {
1344        name: "paint",
1345        ty: FieldType::Paint,
1346        slot: FieldSlot::Second,
1347    },
1348];
1349
1350pub const MODIFIER_SCHEMA: &[VariantSchema] = &[
1351    VariantSchema {
1352        name: "Empty",
1353        tag: 0,
1354        fields: NO_FIELDS,
1355    },
1356    VariantSchema {
1357        name: "Padding",
1358        tag: 1,
1359        fields: VALUE_FLOAT_FIELD,
1360    },
1361    VariantSchema {
1362        name: "FillMaxWidth",
1363        tag: 2,
1364        fields: NO_FIELDS,
1365    },
1366    VariantSchema {
1367        name: "FillMaxHeight",
1368        tag: 3,
1369        fields: NO_FIELDS,
1370    },
1371    VariantSchema {
1372        name: "Width",
1373        tag: 4,
1374        fields: VALUE_FLOAT_FIELD,
1375    },
1376    VariantSchema {
1377        name: "Height",
1378        tag: 5,
1379        fields: VALUE_FLOAT_FIELD,
1380    },
1381    VariantSchema {
1382        name: "Size",
1383        tag: 6,
1384        fields: SIZE_FIELDS,
1385    },
1386    VariantSchema {
1387        name: "Background",
1388        tag: 7,
1389        fields: PAINT_FIELD,
1390    },
1391    VariantSchema {
1392        name: "Clickable",
1393        tag: 8,
1394        fields: HANDLER_ID_FIELD,
1395    },
1396    VariantSchema {
1397        name: "PaddingRole",
1398        tag: 9,
1399        fields: SPACE_ROLE_FIELD,
1400    },
1401    VariantSchema {
1402        name: "PaddingEach",
1403        tag: 10,
1404        fields: PADDING_EACH_FIELDS,
1405    },
1406    VariantSchema {
1407        name: "Weight",
1408        tag: 11,
1409        fields: VALUE_FLOAT_FIELD,
1410    },
1411    VariantSchema {
1412        name: "Shape",
1413        tag: 12,
1414        fields: SHAPE_FIELDS,
1415    },
1416    VariantSchema {
1417        name: "ShapeRole",
1418        tag: 13,
1419        fields: SHAPE_ROLE_FIELD,
1420    },
1421    VariantSchema {
1422        name: "Border",
1423        tag: 14,
1424        fields: BORDER_FIELDS,
1425    },
1426    VariantSchema {
1427        name: "Elevation",
1428        tag: 15,
1429        fields: VALUE_FLOAT_FIELD,
1430    },
1431    VariantSchema {
1432        name: "ObserveSize",
1433        tag: 16,
1434        fields: TOKEN_U32_FIELD,
1435    },
1436    VariantSchema {
1437        name: "Motion",
1438        tag: 17,
1439        fields: MOTION_ROLE_FIELD,
1440    },
1441    VariantSchema {
1442        name: "Material",
1443        tag: 18,
1444        fields: MATERIAL_ROLE_FIELD,
1445    },
1446];
1447
1448#[derive(Clone, Debug, PartialEq)]
1449pub struct ColumnProps {
1450    pub enabled: bool,
1451}
1452
1453#[derive(Clone, Debug, PartialEq)]
1454pub struct RowProps {
1455    pub enabled: bool,
1456}
1457
1458#[derive(Clone, Debug, PartialEq)]
1459pub struct BoxProps {
1460    pub enabled: bool,
1461}
1462
1463#[derive(Clone, Debug, PartialEq)]
1464pub struct TextProps<'a> {
1465    pub text: &'a str,
1466}
1467
1468#[derive(Clone, Debug, PartialEq)]
1469pub struct TextFieldProps<'a> {
1470    pub placeholder: &'a str,
1471    pub enabled: bool,
1472    pub multiline: bool,
1473}
1474
1475#[derive(Clone, Debug, PartialEq)]
1476pub struct ButtonProps<'a> {
1477    pub text: &'a str,
1478    pub enabled: bool,
1479}
1480
1481#[derive(Clone, Debug, PartialEq)]
1482pub struct SpacerProps {
1483    pub width: f32,
1484    pub height: f32,
1485}
1486
1487crate::extensions::define_property_schema_with_extensions!(define_wire_enum; PROPERTY_SCHEMA, PropertyKind {
1488    Text = 1,
1489    Placeholder = 2,
1490    Enabled = 3,
1491    Multiline = 4,
1492    OnClick = 5,
1493    OnValueChange = 6,
1494    OnSubmit = 7,
1495    OnFocusLost = 8,
1496    OnKeyDown = 9,
1497    ItemCount = 10,
1498    ItemKey = 11,
1499    OnRangeRequested = 12,
1500    TypeRole = 13,
1501    FontSize = 14,
1502    FontWeight = 15,
1503    LineHeight = 16,
1504    LetterSpacing = 17,
1505    Color = 18,
1506    TextAlign = 19,
1507    MaxLines = 20,
1508    Overflow = 21,
1509    Arrangement = 22,
1510    Spacing = 23,
1511    SpaceRole = 24,
1512    Alignment = 25,
1513    Variant = 26,
1514    // The id of an asset the Host registered. Image and Icon carry nothing else: the
1515    // bytes were copied into the Renderer's cache once, at registration.
1516    Asset = 28,
1517    // Whether a toggle is on. One boolean for all three of them: a selected radio button
1518    // and a switch that is on are the same fact, and the schema names a concept once.
1519    Checked = 32,
1520    // Discrete stops between a slider's two ends. Zero leaves it continuous.
1521    Steps = 33,
1522    // Whether a progress indicator knows how far along it is. When it does not, `Value`
1523    // is not read at all.
1524    Determinate = 34,
1525    // Which of the two forms a progress indicator takes. It picks a shape, not a size.
1526    Circular = 35,
1527    // A divider's axis, which is the only thing a divider carries.
1528    Vertical = 36,
1529    // Whether an overlay is showing. The Renderer owns the state; this seeds it and
1530    // carries changes that came from outside the Renderer.
1531    Open = 40,
1532    OnDismiss = 41,
1533    SelectedIndex = 42,
1534    // The Canvas drawing command list, a byte blob in the batch arena.
1535    Commands = 50,
1536    // The control's current value, in the widget's own unit: an epoch day count for a
1537    // date, minutes since midnight for a time, the chosen position for a Dropdown, a
1538    // position between the ends for a Slider, a fraction for a ProgressIndicator. One tag,
1539    // because "this control's value" is one concept whichever widget holds it.
1540    Value = 51,
1541    // The ends of the selectable range, in the same unit as `Value`.
1542    Min = 52,
1543    Max = 53,
1544    // The meaning of the icon a destination carries, as an `IconRole` tag. Tag 0 is "not
1545    // sent", so a destination without an icon is label only.
1546    Icon = 60,
1547    // Which slot of a Scaffold this subtree fills.
1548    Slot = 61,
1549    // How many columns a grid has, where the screen said a number.
1550    Columns = 62,
1551    // How narrow a column may get before the grid drops one, where the screen said a
1552    // width instead of a number. The Renderer divides its own width by this, which is the
1553    // same kind of judgement as a size class and for the same reason is not the Host's.
1554    MinColumnWidth = 63,
1555    // Runs of different treatment inside one string, as a blob of fixed-length records.
1556    // A Text that says nothing about runs carries none of this and travels as it always
1557    // did.
1558    Spans = 64,
1559    // A node that is willing to have files dropped on it. The handler is the willingness:
1560    // a node without one is never told that files are over it, which is what keeps a
1561    // screen from lighting up every container it has.
1562    OnFilesEntered = 65,
1563    OnFilesDropped = 66,
1564    // Which named group of a navigation's strip this destination belongs to. Neighbouring
1565    // destinations carrying the same string are one group and the string is its heading.
1566    //
1567    // 76 rather than 67, because 67 through 74 are reserved for the gestures and 75 for a
1568    // button's kind. A reservation is not a free tag.
1569    Section = 76,
1570    // How many a badge counts. Absent rather than zero when the badge is a dot or a word,
1571    // because "no count" and "a count of nothing" are different things to show.
1572    //
1573    // 80 opens a fresh block of ten for widget tags 40 onwards, after the last tag in use
1574    // anywhere, so two pieces of work landing in either order cannot collide.
1575    Count = 80,
1576    // Whether a split pane's side pane may be folded away by dragging past its minimum, or
1577    // by the keyboard, or by the platform's own show and hide sidebar command. Whether a
1578    // side pane can be hidden is a fact about the screen rather than about the design
1579    // system: a list of sessions can, a settings screen's list of sections cannot.
1580    //
1581    // 90 opens the split pane's block of ten, after the badge's.
1582    Collapsible = 90,
1583});
1584
1585#[derive(Clone, Debug, PartialEq)]
1586pub enum EventPayload<'a> {
1587    Clicked,
1588    TextChanged(&'a str),
1589    TextSubmitted(&'a str),
1590    FocusLost,
1591    ProtocolError {
1592        code: u32,
1593        message: &'a str,
1594    },
1595    KeyDown {
1596        key: Key,
1597        shift_key: bool,
1598        ctrl_key: bool,
1599        alt_key: bool,
1600        meta_key: bool,
1601    },
1602    /// The Renderer asks the Host to materialise the visible item range.
1603    RangeRequested {
1604        start: u32,
1605        count: u32,
1606    },
1607    /// A control's new value, in the widget's own unit: an epoch count for a picker, a
1608    /// position for a slider, 0.0 or 1.0 for a toggle. The Renderer decided how the user
1609    /// reached it, so nothing about calendars, wheels, tracks or thumbs crosses here.
1610    ///
1611    /// One `f64` holds all of them: integers up to 2^53 survive it exactly, so splitting
1612    /// the event in two would only give the same concept two names.
1613    ValueChanged(f64),
1614    /// The window, or one observed node, moved into a different size class. The Renderer
1615    /// measures it and sends this only when a class changes, never on every layout pass.
1616    /// A `node_id` of 0 is the window; anything else is the node that asked to be
1617    /// measured.
1618    WindowSizeChanged {
1619        width_dp: f32,
1620        height_dp: f32,
1621        class: WindowSizeClass,
1622        height_class: WindowHeightClass,
1623    },
1624    /// Files are over a node that said it would take them. Nothing about what they are:
1625    /// the platforms disagree about what is knowable before a drop, and a screen that
1626    /// only needs to light up does not need to know.
1627    FilesEntered,
1628    /// Files were let go over a node. The paths arrive together, separated by a byte no
1629    /// path on any of the three desktops may contain.
1630    FilesDropped(&'a str),
1631    /// The Renderer resolved the theme and this is what it chose. Sent once and then
1632    /// only when the answer changes, the same way a size class is.
1633    DesignSystemResolved(DesignSystem),
1634    /// The Renderer lost its node table and asks for the whole tree again. A recreated
1635    /// Activity is the case that produces it.
1636    Resync,
1637    /// The platform brought the UI back. Timers and animations resume.
1638    LifecycleStart,
1639    /// The platform stopped the UI. Timers and animations are suppressed, so a process
1640    /// that is not on screen is not asked to draw.
1641    LifecycleStop,
1642    /// The user pressed a notification the application posted. `action` is 0 for the body
1643    /// and 1 or 2 for an action button; `key` is the name the application gave it.
1644    ///
1645    /// A name rather than a handler, because a notification outlives the component that
1646    /// posted it by hours and a callback dies with its scope.
1647    NotificationActivated {
1648        action: u32,
1649        key: &'a str,
1650    },
1651    /// Whether notifications may be shown, sent once after start and then only when the
1652    /// answer changes.
1653    NotificationPermissionChanged(NotificationPermission),
1654}
1655
1656pub const EVENT_SCHEMA: &[EventSchema] = &[
1657    EventSchema {
1658        name: "Clicked",
1659        tag: 1,
1660        payload: EventPayloadType::None,
1661    },
1662    EventSchema {
1663        name: "TextChanged",
1664        tag: 2,
1665        payload: EventPayloadType::Text,
1666    },
1667    EventSchema {
1668        name: "TextSubmitted",
1669        tag: 3,
1670        payload: EventPayloadType::Text,
1671    },
1672    EventSchema {
1673        name: "FocusLost",
1674        tag: 4,
1675        payload: EventPayloadType::None,
1676    },
1677    EventSchema {
1678        name: "ProtocolError",
1679        tag: 5,
1680        payload: EventPayloadType::ProtocolError,
1681    },
1682    EventSchema {
1683        name: "KeyDown",
1684        tag: 6,
1685        payload: EventPayloadType::KeyDown,
1686    },
1687    EventSchema {
1688        name: "RangeRequested",
1689        tag: 7,
1690        payload: EventPayloadType::Range,
1691    },
1692    EventSchema {
1693        name: "ValueChanged",
1694        // Tags 8 to 15 are reserved for the pointer gesture events.
1695        tag: 16,
1696        payload: EventPayloadType::Double,
1697    },
1698    EventSchema {
1699        name: "WindowSizeChanged",
1700        tag: 17,
1701        payload: EventPayloadType::WindowSize,
1702    },
1703    // The three below address the Host itself rather than a node, so they carry no handler
1704    // and the Host answers them before it looks one up.
1705    EventSchema {
1706        name: "Resync",
1707        tag: 18,
1708        payload: EventPayloadType::None,
1709    },
1710    EventSchema {
1711        name: "LifecycleStart",
1712        tag: 19,
1713        payload: EventPayloadType::None,
1714    },
1715    EventSchema {
1716        name: "LifecycleStop",
1717        tag: 20,
1718        payload: EventPayloadType::None,
1719    },
1720    // Addressed to the Host rather than to a node, like the three above: it is a fact
1721    // about the window, not something that happened inside it.
1722    EventSchema {
1723        name: "DesignSystemResolved",
1724        tag: 21,
1725        payload: EventPayloadType::DesignSystem,
1726    },
1727    EventSchema {
1728        name: "FilesEntered",
1729        tag: 22,
1730        payload: EventPayloadType::None,
1731    },
1732    // One string for however many paths, which is the string convention unchanged. A
1733    // second way of carrying a list would be a second thing to keep in step.
1734    EventSchema {
1735        name: "FilesDropped",
1736        tag: 23,
1737        payload: EventPayloadType::Text,
1738    },
1739    // Both address the Host itself, like the resolved design system: a notification is not
1740    // in the tree, so there is no node and no handler to name.
1741    EventSchema {
1742        name: "NotificationActivated",
1743        tag: 24,
1744        payload: EventPayloadType::NotificationActivation,
1745    },
1746    EventSchema {
1747        name: "NotificationPermissionChanged",
1748        tag: 25,
1749        payload: EventPayloadType::NotificationPermission,
1750    },
1751];
1752
1753/// One argument of a boundary operation.
1754///
1755/// The set is deliberately tiny, because only primitives, pointers and lengths cross the
1756/// boundary. Every platform binding can then be generated from the same list.
1757#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1758pub enum BoundaryParam {
1759    /// A readable byte range. It arrives as a direct byte buffer and a length on Android,
1760    /// as a pointer and a length on desktop.
1761    Bytes { name: &'static str },
1762    /// A 64-bit platform frame timestamp.
1763    Nanos { name: &'static str },
1764}
1765
1766impl BoundaryParam {
1767    pub const fn name(self) -> &'static str {
1768        match self {
1769            Self::Bytes { name } | Self::Nanos { name } => name,
1770        }
1771    }
1772}
1773
1774/// One logical boundary operation, stated without saying which side calls it.
1775///
1776/// `symbol` is the Host's C export. Every platform binding, the GraalVM function
1777/// declarations, the Android shims and the browser forwarders, is a rendering of this
1778/// table, which is why none of them is written by hand.
1779#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1780pub struct BoundaryOp {
1781    /// Direction-neutral name, in PascalCase.
1782    pub name: &'static str,
1783    pub symbol: &'static str,
1784    pub params: &'static [BoundaryParam],
1785    /// Whether the operation answers with a mutation batch.
1786    pub returns_batch: bool,
1787    /// Whether the call may skip the runtime's thread state transition on Android. That
1788    /// is only sound for a call that cannot run long, because the thread stays runnable
1789    /// and the garbage collector cannot suspend it while the call is in flight. A call
1790    /// that runs the VirtualDom can run long.
1791    pub fast: bool,
1792}
1793
1794pub const BOUNDARY_SCHEMA: &[BoundaryOp] = &[
1795    BoundaryOp {
1796        name: "Init",
1797        symbol: "compose_rust_host_init",
1798        params: &[BoundaryParam::Bytes { name: "handshake" }],
1799        returns_batch: true,
1800        fast: false,
1801    },
1802    BoundaryOp {
1803        name: "DispatchEvent",
1804        symbol: "compose_rust_host_dispatch_event",
1805        params: &[BoundaryParam::Bytes { name: "event" }],
1806        returns_batch: true,
1807        fast: false,
1808    },
1809    BoundaryOp {
1810        name: "RenderFrame",
1811        symbol: "compose_rust_host_render_frame",
1812        params: &[BoundaryParam::Nanos {
1813            name: "frameTimeNanos",
1814        }],
1815        returns_batch: true,
1816        fast: false,
1817    },
1818    BoundaryOp {
1819        name: "ReleaseBatch",
1820        symbol: "compose_rust_host_release_batch",
1821        params: &[],
1822        returns_batch: false,
1823        fast: true,
1824    },
1825    BoundaryOp {
1826        name: "Shutdown",
1827        symbol: "compose_rust_host_shutdown",
1828        params: &[],
1829        returns_batch: false,
1830        fast: false,
1831    },
1832];
1833
1834/// The JVM class the Android shims bind to. The Kotlin file is generated under the same
1835/// name, so the two sides cannot drift.
1836pub const ANDROID_BRIDGE_CLASS: &str = "dev/darkpyonix/composerust/ui/platform/HostBridge";
1837
1838// ---------------------------------------------------------------------------------------
1839// The browser's shared linear memory.
1840//
1841// One `WebAssembly.Memory` holds both modules' data. The Kotlin module defines and exports
1842// it, because a Kotlin/Wasm module has no way to import one, and the Rust module is linked
1843// with `--import-memory` so that the arena the Host writes is the arena the Renderer reads.
1844// Nothing below crosses the boundary as data: these are the addresses and offsets both
1845// generated halves are written against.
1846// ---------------------------------------------------------------------------------------
1847
1848/// The first address that belongs to the Rust module.
1849///
1850/// Below it is the Kotlin module's `kotlin.wasm.unsafe` allocator; at and above it are the
1851/// Rust module's data, stack and heap, which the linker is told with `--global-base`. Two
1852/// allocators handing out addresses in one memory is the hazard: an overlap does not crash,
1853/// it draws the wrong screen, so the Renderer checks which side of this line its own
1854/// addresses fall on before it makes the first boundary call.
1855pub const WEB_RUST_REGION_BASE: u32 = 4 * 1024 * 1024;
1856
1857/// One field of the `MutationBatch` record the Host writes into the shared memory.
1858#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1859pub struct WebBatchField {
1860    /// PascalCase, so the generated Kotlin constant reads as a name rather than an offset.
1861    pub name: &'static str,
1862    pub offset: u32,
1863    pub width: u32,
1864    pub description: &'static str,
1865}
1866
1867/// `MutationBatch` as a wasm32 target lays it out.
1868///
1869/// The offsets are written down rather than read from `offset_of!` because codegen runs on
1870/// the machine that generates, not the machine that runs: a 64-bit host makes the same
1871/// `#[repr(C)]` struct 24 bytes with an 8 byte pointer. The generated wasm glue asserts
1872/// this table against the real layout, so a compiler that disagrees stops that build
1873/// instead of producing a Renderer that reads the wrong four bytes.
1874pub const WEB_BATCH_FIELDS: &[WebBatchField] = &[
1875    WebBatchField {
1876        name: "Address",
1877        offset: 0,
1878        width: 4,
1879        description: "where the batch starts, as an address in the shared memory",
1880    },
1881    WebBatchField {
1882        name: "Length",
1883        offset: 4,
1884        width: 4,
1885        description: "the batch's length in bytes",
1886    },
1887    WebBatchField {
1888        name: "Result",
1889        offset: 8,
1890        width: 8,
1891        description: "the handler's synchronous result",
1892    },
1893];
1894
1895/// How long that record is, including the padding `#[repr(C)]` adds before the result.
1896pub const WEB_BATCH_BYTES: u32 = 16;
1897
1898/// How much room the Host lends the Renderer to encode one event into.
1899///
1900/// The same figure the Android connection allocates for the same job. An event that would
1901/// not fit is refused by the encoder rather than truncated.
1902pub const WEB_EVENT_BUFFER_BYTES: u32 = 4096;
1903
1904/// Where the event buffer starts inside the block `compose_rust_host_web_start` reports.
1905///
1906/// The out record comes first, at offset zero, and the event buffer follows it.
1907pub const WEB_EVENT_BUFFER_OFFSET: u32 = WEB_BATCH_BYTES;
1908
1909/// The extra Host export a browser needs, because a page has no library loader.
1910///
1911/// It does what `JNI_OnLoad` does on Android, registering the root component and installing
1912/// the renderer API, and it answers with the address of the block above.
1913pub const WEB_START_SYMBOL: &str = "compose_rust_host_web_start";
1914
1915/// The wasm import module the Host's frame request comes from.
1916///
1917/// This direction has no JavaScript on it: the page hands Kotlin's exported function object
1918/// straight to the Rust instantiation, which the engine binds as a wasm-to-wasm call. It can,
1919/// because Rust is instantiated second and Kotlin's exports already exist by then.
1920pub const WEB_RENDERER_IMPORT_MODULE: &str = "compose_rust_renderer";
1921
1922/// The global the generated forwarders read the Host's exports off.
1923///
1924/// A `@JsFun` body is inlined into the import object module Kotlin generates, so the only
1925/// name it can reach the loader through is a property of the global object.
1926pub const WEB_HOST_GLOBAL: &str = "__composeRustHost";
1927
1928/// The global the loader module publishes the compiled Host module under.
1929///
1930/// Compiling is all the page does, and the reason the page has to do it at all is that
1931/// compiling is asynchronous while the boundary is not: the fetch has to have finished
1932/// before the Renderer's `main` runs. Everything after it, the memory, the imports and the
1933/// entry point, is in the generated Kotlin, because only the Renderer's own module can name
1934/// them.
1935pub const WEB_MODULE_GLOBAL: &str = "__composeRustHostModule";