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    /// A word that is 0 or 1. Any other value is a protocol error, not a third state.
19    Bool,
20    /// An `f32` that has to be a number. NaN or an infinity is a protocol error, for a
21    /// value that would otherwise turn a whole subtree into nothing on one side only.
22    FiniteFloat,
23}
24
25/// Which half of which `u64` word of a modifier's value a field occupies.
26///
27/// Two `f32` share one `u64`: the low 32 bits hold the first value. Every modifier has the
28/// `first` and `second` words; the words after them exist only for a modifier whose
29/// schema entry gives it `extra_words`.
30#[derive(Clone, Copy, Debug, Eq, PartialEq)]
31pub enum FieldSlot {
32    FirstLow,
33    FirstHigh,
34    First,
35    SecondLow,
36    SecondHigh,
37    Second,
38    ThirdLow,
39    ThirdHigh,
40    Third,
41    FourthLow,
42    FourthHigh,
43    Fourth,
44    FifthLow,
45    FifthHigh,
46    Fifth,
47    SixthLow,
48    SixthHigh,
49    Sixth,
50}
51
52impl FieldSlot {
53    /// Which word, counting from zero, and whether the field is that word's high half.
54    pub const fn word(self) -> (usize, bool) {
55        match self {
56            Self::FirstLow | Self::First => (0, false),
57            Self::FirstHigh => (0, true),
58            Self::SecondLow | Self::Second => (1, false),
59            Self::SecondHigh => (1, true),
60            Self::ThirdLow | Self::Third => (2, false),
61            Self::ThirdHigh => (2, true),
62            Self::FourthLow | Self::Fourth => (3, false),
63            Self::FourthHigh => (3, true),
64            Self::FifthLow | Self::Fifth => (4, false),
65            Self::FifthHigh => (4, true),
66            Self::SixthLow | Self::Sixth => (5, false),
67            Self::SixthHigh => (5, true),
68        }
69    }
70}
71
72#[derive(Clone, Copy, Debug, Eq, PartialEq)]
73pub struct FieldSchema {
74    pub name: &'static str,
75    pub ty: FieldType,
76    pub slot: FieldSlot,
77}
78
79#[derive(Clone, Copy, Debug, Eq, PartialEq)]
80pub struct VariantSchema {
81    pub name: &'static str,
82    pub tag: u16,
83    /// How many eight byte words this modifier's value takes beyond the two every
84    /// `SetModifier` record carries. The record is `28 + 8 * extra_words` bytes long, fixed
85    /// per tag, so a decoder knows the length from the tag before it reads the value.
86    pub extra_words: u8,
87    pub fields: &'static [FieldSchema],
88}
89
90/// The most words any modifier's value takes, the two every record has included.
91pub const MAX_MODIFIER_WORDS: usize = 6;
92
93/// The `extra_words` the schema gives a modifier tag, or `None` for a tag it does not list.
94pub const fn modifier_extra_words(tag: u16) -> Option<u8> {
95    let mut index = 0;
96    while index < MODIFIER_SCHEMA.len() {
97        if MODIFIER_SCHEMA[index].tag == tag {
98            return Some(MODIFIER_SCHEMA[index].extra_words);
99        }
100        index += 1;
101    }
102    None
103}
104
105#[derive(Clone, Copy, Debug, Eq, PartialEq)]
106pub enum EventPayloadType {
107    None,
108    Text,
109    ProtocolError,
110    KeyDown,
111    Range,
112    /// One `f64`. Every value-carrying widget shares it, so the same concept has one
113    /// name on the wire whether the value counts days or slides between two ends.
114    Double,
115    WindowSize,
116    /// One `u16`: the tag of the design system the Renderer resolved the theme to.
117    DesignSystem,
118    /// A `u32` saying what was pressed (0 for the body, 1 or 2 for an action button), then
119    /// the notification's key as a string reference.
120    NotificationActivation,
121    /// One `u32`: the notification permission the Renderer last saw.
122    NotificationPermission,
123    /// One moment in a renderer-played animation: its id, what happened, the property and
124    /// slot it plays on, the iteration, the elapsed time and the frame time.
125    Animation,
126    /// One `u32`: whether the platform asks for reduced motion.
127    ReducedMotion,
128    /// One committed edit to a code editor's document: the version it produced, the range
129    /// it replaced in the document as it stood before, and the text that replaced it.
130    CodeChange,
131    /// A Host edit the Renderer would not apply: the request it answers, the version it
132    /// was written against, the version the document had reached, and the range as sent.
133    CodeEditRejected,
134    /// A pointer resting on, or leaving, a place in a code editor: the decoration under it,
135    /// where it is, and which of the two it was.
136    CodeHover,
137    /// The save shortcut, with the version of the document it was pressed on.
138    CodeSave,
139    /// The id the application gave one decoration, which was pressed or accepted.
140    Decoration,
141    /// One pointer happening inside a subscribed node: its kind, which pointer, where,
142    /// which buttons and modifiers, the wheel deltas and when. A fixed 72-byte record.
143    Pointer,
144    /// Two `f32` and an `i32`: the whole zoom factor, the system's text size, and the
145    /// application's zoom level.
146    Zoom,
147    /// Where a code editor's selection is: the document version and the two ends.
148    CodeSelection,
149    /// Which lines of a code editor are on screen, folded lines left out.
150    CodeVisibleRange,
151    /// One word, 0 or 1: whether a code editor holds the keyboard focus.
152    CodeFocus,
153    /// The line a fold starts on and whether it is now collapsed.
154    CodeFold,
155    /// One word, 0 or 1: whether an input method is composing in a code editor. The
156    /// composed text is never part of it.
157    CodeComposition,
158}
159
160#[derive(Clone, Copy, Debug, Eq, PartialEq)]
161pub struct EventSchema {
162    pub name: &'static str,
163    pub tag: u16,
164    pub payload: EventPayloadType,
165}
166
167/// Canonical schema text. Variant order is wire-significant and must only be appended to.
168pub const SCHEMA_DESCRIPTOR: &str = concat!(
169    "compose-rust/v1;",
170    "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,CodeEditor,AbsoluteBox;",
171    "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,button_kind,decorations,syntax_spans,tab_width,on_edit_rejected,on_hover,on_save,on_decoration_click,folds,gutter_decorations,on_selection_change,font,word_break,overflow_wrap,tab_size,absolute_size,soft_wrap,span_fonts,on_visible_range_change,on_editor_focus_change,on_fold_change,on_composition_change,on_pointer,pointer_kinds,cursor,user_select,island_zoom;",
172    "modifiers=Empty,Padding,FillMaxWidth,FillMaxHeight,Width,Height,Size,Background,Clickable,PaddingRole,PaddingEach,Weight,Shape,ShapeRole,Border,Elevation,ObserveSize,Motion,Material,Offset,RequiredSize,BorderEach,CornerEach,Shadow,Clip,Alpha,Transform;",
173    "keys=Enter;",
174    "events=Clicked,TextChanged,TextSubmitted,FocusLost,ProtocolError,KeyDown,RangeRequested,ValueChanged,WindowSizeChanged,DesignSystemResolved,FilesEntered,FilesDropped,NotificationActivated,NotificationPermissionChanged,CodeChanged,CodeEditRejected,CodeHovered,CodeSaveRequested,DecorationActivated,AnimationEvent,ReducedMotionChanged,Pointer,ZoomChanged,SelectionChanged,VisibleRangeChanged,EditorFocusChanged,FoldChanged,CompositionChanged;",
175    "windowsizeclasses=Compact,Medium,Expanded;",
176    "windowheightclasses=Compact,Medium,Expanded;",
177    "commands=Create,SetProp,SetModifier,Insert,Move,Remove,SetText,AppendText,SetTheme,SetWindow,RegisterAsset,ReleaseAsset,ShowMessage,PostNotification,WithdrawNotification,RequestNotificationPermission,EditCode,StartAnimation,ControlAnimation,SetSelection,RevealRange,SetFolded"
178);
179
180const fn hash_bytes(mut hash: u64, bytes: &[u8]) -> u64 {
181    let mut index = 0;
182    while index < bytes.len() {
183        hash ^= bytes[index] as u64;
184        hash = hash.wrapping_mul(0x0000_0100_0000_01b3);
185        index += 1;
186    }
187    hash
188}
189
190const fn hash_enum_schema(mut hash: u64, schema: &[EnumVariantSchema]) -> u64 {
191    let mut index = 0;
192    while index < schema.len() {
193        hash = hash_bytes(hash, schema[index].name.as_bytes());
194        hash = hash_bytes(hash, &schema[index].tag.to_le_bytes());
195        index += 1;
196    }
197    hash
198}
199
200const fn schema_hash() -> u64 {
201    let mut hash = hash_bytes(0xcbf2_9ce4_8422_2325_u64, SCHEMA_DESCRIPTOR.as_bytes());
202    hash = hash_bytes(hash, crate::extensions::SCHEMA_DESCRIPTOR.as_bytes());
203    hash = hash_bytes(hash, crate::measure::SCHEMA_DESCRIPTOR.as_bytes());
204    hash = hash_enum_schema(hash, WIDGET_SCHEMA);
205    hash = hash_enum_schema(hash, PROPERTY_SCHEMA);
206    hash = hash_enum_schema(hash, KEY_SCHEMA);
207    hash = hash_enum_schema(hash, WINDOW_SIZE_CLASS_SCHEMA);
208    hash = hash_enum_schema(hash, WINDOW_HEIGHT_CLASS_SCHEMA);
209    let mut role_index = 0;
210    while role_index < ROLE_ENUM_SCHEMA.len() {
211        hash = hash_bytes(hash, ROLE_ENUM_SCHEMA[role_index].name.as_bytes());
212        hash = hash_enum_schema(hash, ROLE_ENUM_SCHEMA[role_index].variants);
213        role_index += 1;
214    }
215    let mut index = 0;
216    while index < MODIFIER_SCHEMA.len() {
217        let modifier = MODIFIER_SCHEMA[index];
218        hash = hash_bytes(hash, modifier.name.as_bytes());
219        hash = hash_bytes(hash, &modifier.tag.to_le_bytes());
220        hash = hash_bytes(hash, &[modifier.extra_words]);
221        let mut field_index = 0;
222        while field_index < modifier.fields.len() {
223            let field = modifier.fields[field_index];
224            hash = hash_bytes(hash, field.name.as_bytes());
225            hash = hash_bytes(
226                hash,
227                &[match field.ty {
228                    FieldType::Float => 1,
229                    FieldType::U32 => 2,
230                    FieldType::U64 => 3,
231                    FieldType::Paint => 4,
232                    FieldType::Role(_) => 5,
233                    FieldType::Bool => 6,
234                    FieldType::FiniteFloat => 7,
235                }],
236            );
237            if let FieldType::Role(role) = field.ty {
238                hash = hash_bytes(hash, role.as_bytes());
239            }
240            hash = hash_bytes(
241                hash,
242                &[match field.slot {
243                    FieldSlot::FirstLow => 0,
244                    FieldSlot::FirstHigh => 1,
245                    FieldSlot::First => 2,
246                    FieldSlot::SecondLow => 3,
247                    FieldSlot::SecondHigh => 4,
248                    FieldSlot::Second => 5,
249                    FieldSlot::ThirdLow => 6,
250                    FieldSlot::ThirdHigh => 7,
251                    FieldSlot::Third => 8,
252                    FieldSlot::FourthLow => 9,
253                    FieldSlot::FourthHigh => 10,
254                    FieldSlot::Fourth => 11,
255                    FieldSlot::FifthLow => 12,
256                    FieldSlot::FifthHigh => 13,
257                    FieldSlot::Fifth => 14,
258                    FieldSlot::SixthLow => 15,
259                    FieldSlot::SixthHigh => 16,
260                    FieldSlot::Sixth => 17,
261                }],
262            );
263            field_index += 1;
264        }
265        index += 1;
266    }
267    index = 0;
268    while index < crate::drawing::DRAW_COMMAND_SCHEMA.len() {
269        let command = crate::drawing::DRAW_COMMAND_SCHEMA[index];
270        hash = hash_bytes(hash, command.name.as_bytes());
271        hash = hash_bytes(hash, &command.tag.to_le_bytes());
272        let mut field_index = 0;
273        while field_index < command.fields.len() {
274            let field = command.fields[field_index];
275            hash = hash_bytes(hash, field.name.as_bytes());
276            hash = hash_bytes(hash, &[field.word]);
277            hash = hash_bytes(
278                hash,
279                &[match field.ty {
280                    crate::drawing::DrawFieldType::Float => 1,
281                    crate::drawing::DrawFieldType::U32 => 2,
282                    crate::drawing::DrawFieldType::Role(_) => 3,
283                }],
284            );
285            if let crate::drawing::DrawFieldType::Role(role) = field.ty {
286                hash = hash_bytes(hash, role.as_bytes());
287            }
288            field_index += 1;
289        }
290        index += 1;
291    }
292    index = 0;
293    while index < EVENT_SCHEMA.len() {
294        let event = EVENT_SCHEMA[index];
295        hash = hash_bytes(hash, event.name.as_bytes());
296        hash = hash_bytes(hash, &event.tag.to_le_bytes());
297        hash = hash_bytes(
298            hash,
299            &[match event.payload {
300                EventPayloadType::None => 0,
301                EventPayloadType::Text => 1,
302                EventPayloadType::ProtocolError => 2,
303                EventPayloadType::KeyDown => 3,
304                EventPayloadType::Range => 4,
305                EventPayloadType::Double => 5,
306                EventPayloadType::WindowSize => 6,
307                EventPayloadType::DesignSystem => 7,
308                EventPayloadType::NotificationActivation => 8,
309                EventPayloadType::NotificationPermission => 9,
310                EventPayloadType::Animation => 10,
311                EventPayloadType::ReducedMotion => 11,
312                EventPayloadType::CodeChange => 12,
313                EventPayloadType::CodeEditRejected => 13,
314                EventPayloadType::CodeHover => 14,
315                EventPayloadType::CodeSave => 15,
316                EventPayloadType::Decoration => 16,
317                EventPayloadType::Pointer => 17,
318                EventPayloadType::Zoom => 18,
319                EventPayloadType::CodeSelection => 19,
320                EventPayloadType::CodeVisibleRange => 20,
321                EventPayloadType::CodeFocus => 21,
322                EventPayloadType::CodeFold => 22,
323                EventPayloadType::CodeComposition => 23,
324            }],
325        );
326        index += 1;
327    }
328    hash = crate::code::hash_record_schema(hash, crate::code::DECORATION_RECORD);
329    hash = crate::code::hash_record_schema(hash, crate::code::SYNTAX_SPAN_RECORD);
330    hash = crate::code::hash_record_schema(hash, crate::code::FOLD_RECORD);
331    hash = crate::code::hash_record_schema(hash, crate::code::GUTTER_DECORATION_RECORD);
332    hash
333}
334
335/// Stable handshake hash of the complete ordered schema metadata.
336pub const SCHEMA_HASH: u64 = schema_hash();
337pub const PROTOCOL_VERSION: u16 = 1;
338
339fn wire_name_eq(input: &str, schema_name: &str) -> bool {
340    input
341        .bytes()
342        .filter(|byte| *byte != b'_')
343        .map(|byte| byte.to_ascii_lowercase())
344        .eq(schema_name.bytes().map(|byte| byte.to_ascii_lowercase()))
345}
346
347macro_rules! define_wire_enum {
348    ($schema:ident, $name:ident { $($variant:ident = $tag:literal),+ $(,)? }) => {
349        define_wire_enum!($schema, $name { $($variant = $tag),+ } unlisted {});
350    };
351    // `unlisted` variants exist in Rust and travel as their tag, but are not in the schema
352    // table, so codegen mirrors no Kotlin enum entry for them and `TryFrom` does not
353    // produce them. The one there is, `TypeRole::None`, is tag 0, which every role
354    // decoder already reads as "no role", and an entry for it in the Kotlin enum would
355    // shift every ordinal the Renderer indexes its token tables by.
356    (
357        $schema:ident,
358        $name:ident { $($variant:ident = $tag:literal),+ $(,)? }
359        unlisted { $($extra:ident = $extra_tag:literal),* $(,)? }
360    ) => {
361        #[derive(Clone, Copy, Debug, Eq, PartialEq)]
362        #[repr(u16)]
363        pub enum $name {
364            $($extra = $extra_tag,)*
365            $($variant = $tag),+
366        }
367
368        pub const $schema: &[EnumVariantSchema] = &[
369            $(EnumVariantSchema { name: stringify!($variant), tag: $tag }),+
370        ];
371
372        impl From<$name> for u16 {
373            fn from(value: $name) -> Self {
374                value as Self
375            }
376        }
377
378        impl TryFrom<u16> for $name {
379            type Error = ();
380
381            // Spelled `()` rather than `Self::Error`: a `ColorRole::Error` variant would
382            // otherwise make the associated item ambiguous.
383            fn try_from(value: u16) -> Result<Self, ()> {
384                match value {
385                    $($tag => Ok(Self::$variant),)+
386                    _ => Err(()),
387                }
388            }
389        }
390
391        impl $name {
392            // The unit error is the wire's own convention: a name either is a variant or is
393            // not, and the caller reports which name it was.
394            #[allow(dead_code, clippy::result_unit_err)]
395            #[doc(hidden)]
396            #[allow(clippy::result_unit_err)]
397            pub fn from_name(value: &str) -> Result<Self, ()> {
398                $(if wire_name_eq(value, stringify!($variant)) {
399                    return Ok(Self::$variant);
400                })+
401                Err(())
402            }
403        }
404    };
405}
406
407crate::extensions::define_widget_schema_with_extensions!(define_wire_enum; WIDGET_SCHEMA, WidgetKind {
408    Column = 1,
409    Row = 2,
410    Box = 3,
411    Text = 4,
412    TextField = 5,
413    Button = 6,
414    Spacer = 7,
415    LazyColumn = 8,
416    ScrollColumn = 9,
417    Image = 10,
418    Icon = 11,
419    // The selection controls and the indicators. Each one emits a state and a role and
420    // nothing about how it is drawn: the tick, the track, the thumb and the sweep of an
421    // indeterminate bar are the design system's.
422    Checkbox = 12,
423    RadioButton = 13,
424    Switch = 14,
425    Slider = 15,
426    ProgressIndicator = 16,
427    Divider = 17,
428    Card = 18,
429    Surface = 19,
430    Dialog = 20,
431    Menu = 21,
432    Tabs = 22,
433    TopAppBar = 23,
434    LazyRow = 24,
435    Tooltip = 25,
436    // Drawing commands instead of child nodes. The command list is its only property.
437    Canvas = 26,
438    DatePicker = 27,
439    TimePicker = 28,
440    Dropdown = 29,
441    // One declaration, three presentations. The Renderer picks a bottom bar, a rail or a
442    // permanent drawer from the width it has already measured, so the same tree looks
443    // native on a phone and on a desktop without the Host branching on the size class.
444    Navigation = 30,
445    // One destination. Its label and its icon are properties rather than children,
446    // because a child tree would fix the arrangement the presentations need to differ in.
447    NavigationItem = 31,
448    // A temporary surface that slides in from an edge of the screen. Which edge is the
449    // Renderer's decision, for the same reason the navigation presentation is.
450    Sheet = 32,
451    // The screen's frame. The application fills slots and the Renderer decides what each
452    // one becomes on this platform and at this width, which is the same decision
453    // `Navigation` already makes for itself, widened to the whole frame.
454    Scaffold = 33,
455    // One filled slot. A wrapper rather than a property on the slot's own root, because
456    // an application may put any tree in a slot and the Renderer has to know which slot
457    // it is looking at without reading into it.
458    ScaffoldSlot = 34,
459    // A lazy grid. The windowing protocol is the list's, unchanged: the Renderer asks for a
460    // range of items and the Host materialises exactly that range. What the grid adds is
461    // that the Renderer rounds the range it asks for to whole rows, because a row is what
462    // it lays out, and how many items a row holds is its own decision when the columns
463    // were given as a minimum width rather than a count.
464    LazyGrid = 35,
465    // A place files may be dropped. Being this widget is the willingness: a node that is
466    // not one is never offered as a target, so the platform shows no drop cursor over it
467    // and nothing is reported.
468    FileDropTarget = 36,
469    // The whole content with a horizontal scroll, which is a ScrollColumn turned on its
470    // side. Every child is materialised and the scroll position is the Renderer's, so
471    // nothing about scrolling crosses the boundary; a long horizontal list is a LazyRow.
472    ScrollRow = 37,
473    // A small token that is chosen or filters. It carries its label and whether it is
474    // chosen, and the chosen state is the Host's: it changes only because the Host's own
475    // click handler changed it. The shape is the design system's.
476    Chip = 38,
477    // The one action a screen is about. It carries an icon, a label and a click. Where it
478    // goes and what it is drawn as is the design system's: a disc floating over the page
479    // is one system's answer, a plus at the trailing end of the bar is another's, and an
480    // accent button at the head of the command bar is a third.
481    FloatingAction = 39,
482    // A small mark on something else that says how many, that there is something new, or
483    // one short word. With one child it is attached to that child; with none it stands on
484    // its own, at the end of a row. Where it sits on the child, its shape, and how a large
485    // count is shortened are the design system's: nothing here can ask for a corner or a
486    // ceiling.
487    Badge = 40,
488    // A region whose text can be dragged over and copied, as one selection across every
489    // Text inside it. Text outside one cannot be selected. The selection and the copy are
490    // the Renderer's entirely, so nothing about either crosses the boundary.
491    SelectionContainer = 41,
492    // Two panes side by side, a side pane and a body, with a divider the user drags to
493    // change the side pane's width. Exactly two children, the side pane first.
494    //
495    // The drag is the Renderer's, like a scroll position: nothing crosses the boundary
496    // while it moves, and the width it ends on is reported once through the value change
497    // event. How the divider looks and how far its grip reaches, the side pane's default
498    // width, the body's minimum, and whether a narrow place shows the two panes side by
499    // side, with the side pane laid over the body, or one at a time, are the design
500    // system's answers.
501    SplitPane = 42,
502    // Several lines of code that the reader edits. The text, the caret, the selection,
503    // the scroll position and the undo history are the Renderer's, the way a text field's
504    // are, and the Host is told about each committed edit with the version it produced.
505    // What the Host adds on top is said as ranges: colour runs for the syntax, and
506    // decorations for diagnostics, lenses, hover anchors and inline suggestions. How any
507    // of it is drawn, from the gutter to the weight of an underline, is the design
508    // system's.
509    CodeEditor = 43,
510    // Children laid where the Host already put them. Each child is placed at its own
511    // `Offset`, relative to this box, and no child moves or sizes another: the layout was
512    // computed on the Host (an HTML engine, for instance) and laying it out a second time
513    // here would only be a second answer that can disagree with the first. Later children
514    // draw over earlier ones, so a stacking order is the child order.
515    AbsoluteBox = 44,
516});
517
518#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
519#[repr(u8)]
520pub enum LoopMode {
521    #[default]
522    Renderer = 0,
523    Platform = 1,
524}
525
526#[derive(Clone, Copy, Debug, Eq, PartialEq)]
527pub struct Selection {
528    pub start: u32,
529    pub end: u32,
530}
531
532// Compose key identities supported by the M0 schema.
533define_wire_enum!(KEY_SCHEMA, Key {
534    Enter = 1,
535});
536
537define_wire_enum!(WINDOW_SIZE_CLASS_SCHEMA, WindowSizeClass {
538    Compact = 0,
539    Medium = 1,
540    Expanded = 2,
541});
542
543impl WindowSizeClass {
544    /// The width in dp at which `Medium` begins, and the width at which `Expanded` does.
545    ///
546    /// These are the Material 3 window size class boundaries. Using different numbers
547    /// would make the Compose components adapt at one width and the Rust layout branch at
548    /// another, and that mismatch is visible on screen.
549    pub const MEDIUM_MIN_WIDTH_DP: f32 = 600.0;
550    pub const EXPANDED_MIN_WIDTH_DP: f32 = 840.0;
551
552    /// The class a window of this width belongs to. Height does not take part.
553    pub fn from_width_dp(width_dp: f32) -> Self {
554        if width_dp >= Self::EXPANDED_MIN_WIDTH_DP {
555            Self::Expanded
556        } else if width_dp >= Self::MEDIUM_MIN_WIDTH_DP {
557            Self::Medium
558        } else {
559            Self::Compact
560        }
561    }
562}
563
564define_wire_enum!(WINDOW_HEIGHT_CLASS_SCHEMA, WindowHeightClass {
565    Compact = 0,
566    Medium = 1,
567    Expanded = 2,
568});
569
570impl WindowHeightClass {
571    /// The height in dp at which `Medium` begins, and the height at which `Expanded` does.
572    ///
573    /// The same published table the width boundaries came from, so a layout that folds
574    /// when it runs out of vertical room folds at the height Compose's own components
575    /// treat as short.
576    pub const MEDIUM_MIN_HEIGHT_DP: f32 = 480.0;
577    pub const EXPANDED_MIN_HEIGHT_DP: f32 = 900.0;
578
579    /// The class a window of this height belongs to. Width does not take part.
580    pub fn from_height_dp(height_dp: f32) -> Self {
581        if height_dp >= Self::EXPANDED_MIN_HEIGHT_DP {
582            Self::Expanded
583        } else if height_dp >= Self::MEDIUM_MIN_HEIGHT_DP {
584            Self::Medium
585        } else {
586            Self::Compact
587        }
588    }
589}
590
591/// A role enum that codegen mirrors into Kotlin.
592#[derive(Clone, Copy, Debug, Eq, PartialEq)]
593pub struct RoleEnumSchema {
594    pub name: &'static str,
595    pub variants: &'static [EnumVariantSchema],
596}
597
598// An opaque ARGB colour. Gradients and image brushes are deliberately absent: they do not
599// fit the two words a modifier variant has, and they would need resources to cross the
600// boundary, which the fixed-layout records cannot carry.
601#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
602#[repr(transparent)]
603pub struct Color(pub u32);
604
605impl Color {
606    /// An opaque colour from a 24-bit `0xRRGGBB` value.
607    pub const fn rgb(value: u32) -> Self {
608        Self(0xff00_0000 | (value & 0x00ff_ffff))
609    }
610
611    /// A colour from a full 32-bit `0xAARRGGBB` value.
612    pub const fn argb(value: u32) -> Self {
613        Self(value)
614    }
615
616    pub const fn to_argb(self) -> u32 {
617        self.0
618    }
619}
620
621// Semantic colour slots. The design system resolves them, inside the Renderer.
622define_wire_enum!(COLOR_ROLE_SCHEMA, ColorRole {
623    Primary = 1,
624    OnPrimary = 2,
625    Secondary = 3,
626    OnSecondary = 4,
627    Surface = 5,
628    OnSurface = 6,
629    SurfaceVariant = 7,
630    OnSurfaceVariant = 8,
631    Background = 9,
632    OnBackground = 10,
633    Outline = 11,
634    OutlineVariant = 12,
635    Error = 13,
636    OnError = 14,
637    // The layer a panel is made of: a thing raised off the page, holding the page's own
638    // reading ink. It exists because `Surface` cannot do that job everywhere. Material 3
639    // gives `Surface` and `Background` one value on purpose and expresses depth through
640    // tonal containers instead, so a panel filled with `Surface` on a page of `Background`
641    // is drawn, in the right colour, and is invisible. `SurfaceVariant` is not the answer
642    // either: its ink pair is the muted secondary ink, so a panel holding primary content
643    // would have to set that content half faded. This is the one role every design system
644    // promises to make visible against the page, and `OnSurface` is what reads on it.
645    SurfaceContainer = 15,
646    // The third accent, and a quiet fill for each of the three.
647    //
648    // Two accents and a page say a button, a bar and a heading. They cannot say a grid of
649    // subject tiles where the subject is the colour, a mood picker, or a panel of costs
650    // beside a panel of totals. Those screens need fills that read as relatives of one
651    // another, are not reading surfaces, and are quiet enough that body text sits on them.
652    //
653    // A container is a colour in the table rather than its accent at a lower opacity.
654    // Opacity only means something once you know what is behind it, and a role has to
655    // answer before anyone knows that. So each container is its own value and carries its
656    // own ink, held to the body-text bound rather than the label bound, because the reason
657    // it exists is that paragraphs land on it.
658    //
659    // The three containers are not required to be told apart from each other. Material 3's
660    // baseline primary and secondary containers are neighbouring tones of one palette, and
661    // that is its published scheme, not a mistake. A caller who needs three fills that
662    // separate at a glance reaches for the tertiary pair.
663    Tertiary = 16,
664    OnTertiary = 17,
665    PrimaryContainer = 18,
666    OnPrimaryContainer = 19,
667    SecondaryContainer = 20,
668    OnSecondaryContainer = 21,
669    TertiaryContainer = 22,
670    OnTertiaryContainer = 23,
671    // What a highlighter paints code with. A conversation shows code blocks and diffs, and
672    // with only literals to paint them the same grey comment would be stamped into seven
673    // systems and two schemes, which is how a light comment grey disappears on a dark page.
674    // So a token kind is a role like any other, and each system answers it from the code
675    // editor of its own platform.
676    //
677    // The split follows what tree-sitter's highlight names, VS Code's semantic token kinds
678    // and TextMate scopes all agree on. Each of those divides more finely, and the finer
679    // names fold into one of these (`keyword.return` is a keyword). A token that fits none
680    // is left in the body ink rather than given a colour of its own.
681    //
682    // The fifteen syntax inks hold the body-text bound on the code block's panel, comments
683    // included: a comment can be quieter in hue and lightness, but one nobody can read is
684    // a comment that is not there.
685    SyntaxKeyword = 24,
686    SyntaxString = 25,
687    SyntaxComment = 26,
688    SyntaxNumber = 27,
689    SyntaxConstant = 28,
690    SyntaxType = 29,
691    SyntaxFunction = 30,
692    SyntaxVariable = 31,
693    SyntaxProperty = 32,
694    SyntaxOperator = 33,
695    SyntaxPunctuation = 34,
696    SyntaxTag = 35,
697    SyntaxAttribute = 36,
698    SyntaxEscape = 37,
699    SyntaxMacro = 38,
700    // A diff. The ink marks an added or removed line and its text; the container is the
701    // whole line's background, drawn as the line node's own background; the emphasis is
702    // the background behind the words that actually changed, drawn by the span. Added and
703    // removed are never told apart by colour alone: the `+` and `-` at the head of a line
704    // stay, because the two backgrounds can look the same to a red-green colour blind
705    // reader.
706    DiffAdded = 39,
707    DiffRemoved = 40,
708    DiffModified = 41,
709    DiffAddedContainer = 42,
710    DiffRemovedContainer = 43,
711    DiffAddedEmphasis = 44,
712    DiffRemovedEmphasis = 45,
713});
714
715/// How many colour roles there are, which is how many rows every token table has and how
716/// many slots a palette carries per scheme.
717pub const COLOR_ROLE_COUNT: usize = 45;
718
719// The nine-rung type ladder every supported design system maps onto.
720//
721// `None` is text with no rung at all, which is what a page laid out by CSS draws: its size,
722// weight, line height, letter spacing and font come from the text's own values and nothing
723// from the design system. It is the only text that may name a font of its own. It is not
724// in the schema table, because it is not a rung: see `define_wire_enum!`.
725define_wire_enum!(TYPE_ROLE_SCHEMA, TypeRole {
726    Display = 1,
727    Headline = 2,
728    Title = 3,
729    Subtitle = 4,
730    Body = 5,
731    BodyStrong = 6,
732    Label = 7,
733    Caption = 8,
734    Mono = 9,
735} unlisted {
736    None = 0,
737});
738
739// Where a line of text may break, as CSS `word-break` says it. `Normal` breaks where the
740// language does: between words, and between syllables in Korean. `KeepAll` breaks only at
741// spaces, which is how Korean prose is usually set. `BreakAll` breaks between any two
742// characters.
743define_wire_enum!(WORD_BREAK_SCHEMA, WordBreak {
744    Normal = 1,
745    KeepAll = 2,
746    BreakAll = 3,
747});
748
749// Whether a word too long for its line may be broken inside, as CSS `overflow-wrap` says
750// it. `Anywhere` also lets that break count when the narrowest width the text can take is
751// worked out; `BreakWord` does not.
752define_wire_enum!(OVERFLOW_WRAP_SCHEMA, OverflowWrap {
753    Normal = 1,
754    Anywhere = 2,
755    BreakWord = 3,
756});
757
758// The families CSS names without naming a font. Which installed font each one is, is the
759// platform's answer.
760define_wire_enum!(GENERIC_FAMILY_SCHEMA, GenericFamily {
761    SystemUi = 1,
762    SansSerif = 2,
763    Serif = 3,
764    Monospace = 4,
765});
766
767// Corner roles. The radius is the design system's decision.
768define_wire_enum!(SHAPE_ROLE_SCHEMA, ShapeRole {
769    None = 1,
770    ExtraSmall = 2,
771    Small = 3,
772    Medium = 4,
773    Large = 5,
774    Full = 6,
775});
776
777// How long a change takes and along which curve. A role rather than a duration, because
778// Material's emphasized curve and Cupertino's spring are different answers to the same
779// question, and a milliseconds figure from the application would settle it in the wrong
780// place. A system asked to reduce motion answers every one of these instantly.
781define_wire_enum!(MOTION_ROLE_SCHEMA, MotionRole {
782    Instant = 1,
783    Quick = 2,
784    Standard = 3,
785    Slow = 4,
786    Emphasized = 5,
787});
788
789// What a surface is made of. Four roles rather than a blur radius, because Cupertino and
790// Liquid Glass answer with blur, Material 3 with elevation and a tone laid over the
791// surface, and the GNOME and KDE systems with an opaque fill. A radius from the
792// application would be a blur instruction to systems that do not blur.
793define_wire_enum!(MATERIAL_ROLE_SCHEMA, MaterialRole {
794    Thin = 1,
795    Regular = 2,
796    Thick = 3,
797    Chrome = 4,
798});
799
800// Density roles, because dp density differs per design system.
801define_wire_enum!(SPACE_ROLE_SCHEMA, SpaceRole {
802    None = 1,
803    Xs = 2,
804    Sm = 3,
805    Md = 4,
806    Lg = 5,
807    Xl = 6,
808    Xxl = 7,
809});
810
811define_wire_enum!(TEXT_ALIGN_SCHEMA, TextAlign {
812    Start = 1,
813    Center = 2,
814    End = 3,
815    Justify = 4,
816});
817
818define_wire_enum!(TEXT_OVERFLOW_SCHEMA, TextOverflow {
819    Clip = 1,
820    Ellipsis = 2,
821    Visible = 3,
822});
823
824define_wire_enum!(ARRANGEMENT_SCHEMA, Arrangement {
825    Start = 1,
826    Center = 2,
827    End = 3,
828    SpaceBetween = 4,
829    SpaceAround = 5,
830    SpaceEvenly = 6,
831});
832
833define_wire_enum!(ALIGNMENT_SCHEMA, Alignment {
834    TopStart = 1,
835    TopCenter = 2,
836    TopEnd = 3,
837    CenterStart = 4,
838    Center = 5,
839    CenterEnd = 6,
840    BottomStart = 7,
841    BottomCenter = 8,
842    BottomEnd = 9,
843});
844
845// The neutral component variant names the design system styles.
846define_wire_enum!(BUTTON_VARIANT_SCHEMA, ButtonVariant {
847    Filled = 1,
848    Tonal = 2,
849    Outlined = 3,
850    Text = 4,
851    Operator = 5,
852});
853
854// What a button is, independently from the visual emphasis its variant carries.
855define_wire_enum!(BUTTON_KIND_SCHEMA, ButtonKind {
856    Standard = 1,
857    ActionKey = 2,
858});
859
860// What the bytes behind an asset id are. A kind the Renderer cannot read is a reported
861// protocol error, not a guess.
862define_wire_enum!(ASSET_KIND_SCHEMA, AssetKind {
863    Png = 1,
864    Jpeg = 2,
865    Svg = 3,
866    VectorIcon = 4,
867    // A font file, registered so that a type role can resolve to it. The bytes are the
868    // font, which is why this is an asset and not a name: a name would put the check that
869    // the font exists at run time on the reader's machine, and a missing one there is a
870    // screen in the wrong typeface with nobody to tell.
871    Font = 5,
872    // A gradient or an image fill, registered so that any Paint may name it. The bytes
873    // are the brush's own fixed layout rather than a file: nothing on any platform stores
874    // a list of stops as a document, and a format would be a parser to write and keep.
875    Brush = 6,
876});
877
878// How a brush carries on past the area it was given.
879define_wire_enum!(TILE_MODE_SCHEMA, TileMode {
880    // Stops at the edge and drags the last colour outwards, which is what a gradient
881    // filling a surface wants.
882    Clamp = 1,
883    Repeat = 2,
884    // Repeats, flipping every other copy, so the seams do not show.
885    Mirror = 3,
886});
887
888// The closed set of icon meanings. An icon is addressed by what it is for, never by a
889// system icon name: a name would defer the check that the icon exists to run time, and it
890// would pin one platform's artwork into the protocol. The role is what lets the same
891// declaration come out as SF Symbols under Cupertino and Material Symbols under Material 3.
892define_wire_enum!(ICON_ROLE_SCHEMA, IconRole {
893    Back = 1,
894    Forward = 2,
895    Close = 3,
896    Search = 4,
897    Add = 5,
898    Check = 6,
899    Settings = 7,
900    More = 8,
901    // The meanings a set of destinations needs before any other: where the application
902    // starts, the list it is mostly about, and what has arrived.
903    Home = 9,
904    List = 10,
905    Inbox = 11,
906    // What a window's own bar needs: the button that opens everything this screen does
907    // not have room for, and the one that goes back over what has already happened. Both
908    // are in the reference bar of more than one sample, and neither is a destination, so
909    // neither could be said with the three above.
910    Menu = 12,
911    History = 13,
912    // What putting the samples beside the applications they are drawn from turned up: eight
913    // meanings the set could not say. Each is a meaning rather than a picture, so a system
914    // that draws sending as a paper aeroplane is still answering `Send`.
915    //
916    // None of them is an existing role used loosely. A composer's send was drawn with
917    // `Forward` for a while, and an arrow pointing sideways says "on to the next one"
918    // rather than "send this".
919    Send = 14,
920    Mic = 15,
921    Image = 16,
922    Video = 17,
923    Library = 18,
924    Sidebar = 19,
925    Compose = 20,
926    Collapse = 21,
927});
928
929// How long a transient message stays on screen. Closed, and deliberately short: a message
930// that has to be acknowledged is a `Dialog`, so there is no indefinite duration. What the
931// two names mean in milliseconds is the design system's decision.
932define_wire_enum!(MESSAGE_DURATION_SCHEMA, MessageDuration {
933    Short = 1,
934    Long = 2,
935});
936
937// How much a notification matters. Closed for the same reason a message's duration is: what
938// it is worth in sound, in an Android channel's importance or in getting through Do Not
939// Disturb is the platform's answer, so the Host has no way to send a sound file or a
940// priority number.
941define_wire_enum!(NOTIFICATION_IMPORTANCE_SCHEMA, NotificationImportance {
942    Normal = 1,
943    Urgent = 2,
944});
945
946// Whether a notification is shown while the application's window is the active one.
947// `WhenInactive` is dropped by the Renderer when the window has focus: whether it has is
948// the Renderer's state, and a Host that wanted to judge it would need every focus change
949// sent across.
950define_wire_enum!(NOTIFICATION_PRESENTATION_SCHEMA, NotificationPresentation {
951    Always = 1,
952    WhenInactive = 2,
953});
954
955// Whether this run may show notifications. `Unsupported` is a platform or a run that has
956// no way to show one (no bundle identifier, no AppUserModelID, no notification daemon),
957// which is an answer and not an error.
958define_wire_enum!(NOTIFICATION_PERMISSION_SCHEMA, NotificationPermission {
959    NotDetermined = 1,
960    Granted = 2,
961    Denied = 3,
962    Unsupported = 4,
963});
964
965// The design systems of phase one. Later ones append variants here and nowhere else: a new
966// design system is one variant plus one token table and rule implementation in the Renderer.
967//
968// `LiquidGlass` is the language macOS 26 and iOS 26 draw, and it sits beside `Cupertino`
969// rather than replacing it: one draws grouped inset lists on a grey page, the other draws
970// translucent floating capsules, and an application that wants one does not want the
971// other. Tags are append only, so it lands after the Linux three even though it belongs
972// next to `Cupertino` in any list a reader would write.
973define_wire_enum!(DESIGN_SYSTEM_SCHEMA, DesignSystem {
974    Material3 = 1,
975    Cupertino = 2,
976    Fluent = 3,
977    Gnome = 4,
978    Breeze = 5,
979    Deepin = 6,
980    LiquidGlass = 7,
981});
982
983// Light and dark selection. `FollowSystem` leaves the choice to the Renderer, which learns
984// of a system change first and applies it without involving the Host.
985define_wire_enum!(COLOR_SCHEME_SCHEMA, ColorScheme {
986    Light = 1,
987    Dark = 2,
988    FollowSystem = 3,
989});
990
991// Which part of the screen's frame a `Scaffold` slot fills.
992//
993// What each one becomes is the Renderer's: a top bar may be the window's caption, an
994// ordinary bar under the system title bar, or a large title that shrinks as the page
995// scrolls; a bottom bar may be a bar, a rail or a permanent drawer; a floating action
996// may float, sit in the toolbar, or fold into a menu. The application says which slot
997// it filled and nothing about the answer.
998define_wire_enum!(SLOT_ROLE_SCHEMA, SlotRole {
999    TopBar = 1,
1000    BottomBar = 2,
1001    FloatingAction = 3,
1002    Content = 4,
1003});
1004
1005// What a code editor decoration is. Four kinds and no more: a mark under a range, a line of
1006// text above a line, a range that answers a resting pointer, and a suggestion shown in
1007// place. Each is what a language server or an extension host already speaks in, so an
1008// application can pass its answers through without inventing a fifth.
1009define_wire_enum!(DECORATION_KIND_SCHEMA, DecorationKind {
1010    Underline = 1,
1011    CodeLens = 2,
1012    HoverAnchor = 3,
1013    GhostText = 4,
1014});
1015
1016// How serious an underlined diagnostic is. The four a language server reports, in its
1017// order. The colour each one gets is the design system's unless the application names a
1018// role, and how the mark is drawn, a squiggle or a straight line, is always the system's.
1019define_wire_enum!(SEVERITY_SCHEMA, Severity {
1020    Error = 1,
1021    Warning = 2,
1022    Information = 3,
1023    Hint = 4,
1024});
1025
1026// Whether a pointer has come to rest over a place in a code editor or has left it. Two
1027// values, because the Host is told once on arriving and once on leaving and never while
1028// the pointer stays.
1029define_wire_enum!(HOVER_PHASE_SCHEMA, HoverPhase {
1030    Rest = 1,
1031    Leave = 2,
1032});
1033
1034// What a mark beside the line numbers of a code editor looks like: a dot, a bar the height
1035// of the line, or one character the application names. The colour is a role or the design
1036// system's; the size and the placement of all three are always the system's.
1037define_wire_enum!(GUTTER_DECORATION_KIND_SCHEMA, GutterDecorationKind {
1038    Dot = 1,
1039    Bar = 2,
1040    Glyph = 3,
1041});
1042
1043// Where `RevealRange` puts the range it brings into view: the least scroll that shows it,
1044// the middle of the view, or the top.
1045define_wire_enum!(REVEAL_ALIGN_SCHEMA, RevealAlign {
1046    Nearest = 1,
1047    Center = 2,
1048    Top = 3,
1049});
1050
1051// The vocabulary of animations the Renderer plays on its own frame clock. Every one is a
1052// byte on the wire and every one starts at 1, so a zero is always a record the two sides
1053// disagree about.
1054
1055// What an animation changes. Each has a place it is drawn from: the node's first `Alpha`,
1056// its `color` property, its first `Background`, its first `Transform`, and the colour of
1057// every side of its first `Border` or `BorderEach`. A container's `color` is the colour
1058// the text inside it inherits, so one animation on it recolours all of that text.
1059define_wire_enum!(ANIMATED_PROPERTY_SCHEMA, AnimatedProperty {
1060    Alpha = 1,
1061    Color = 2,
1062    Background = 3,
1063    Transform = 4,
1064    BorderColor = 5,
1065});
1066
1067define_wire_enum!(PLAYBACK_DIRECTION_SCHEMA, PlaybackDirection {
1068    Normal = 1,
1069    Reverse = 2,
1070    Alternate = 3,
1071    AlternateReverse = 4,
1072});
1073
1074define_wire_enum!(FILL_MODE_SCHEMA, FillMode {
1075    None = 1,
1076    Forwards = 2,
1077    Backwards = 3,
1078    Both = 4,
1079});
1080
1081define_wire_enum!(PLAY_STATE_SCHEMA, PlayState {
1082    Running = 1,
1083    Paused = 2,
1084});
1085
1086// How two colours are mixed: the way CSS's older colour syntax does, or in Oklab, the
1087// default of CSS Color 4's newer syntax.
1088define_wire_enum!(COLOR_INTERPOLATION_SCHEMA, ColorInterpolation {
1089    SrgbPremultiplied = 1,
1090    Oklab = 2,
1091});
1092
1093define_wire_enum!(TIMING_KIND_SCHEMA, TimingKind {
1094    Linear = 1,
1095    CubicBezier = 2,
1096    Steps = 3,
1097});
1098
1099define_wire_enum!(STEP_POSITION_SCHEMA, StepPosition {
1100    JumpStart = 1,
1101    JumpEnd = 2,
1102    JumpNone = 3,
1103    JumpBoth = 4,
1104});
1105
1106define_wire_enum!(TRANSFORM_FUNCTION_KIND_SCHEMA, TransformFunctionKind {
1107    Translate = 1,
1108    Rotate = 2,
1109    Scale = 3,
1110    Skew = 4,
1111    Matrix = 5,
1112});
1113
1114define_wire_enum!(ANIMATION_CONTROL_SCHEMA, AnimationControl {
1115    Pause = 1,
1116    Resume = 2,
1117    Cancel = 3,
1118});
1119
1120define_wire_enum!(ANIMATION_EVENT_KIND_SCHEMA, AnimationEventKind {
1121    Ready = 1,
1122    Active = 2,
1123    Iteration = 3,
1124    End = 4,
1125});
1126
1127define_wire_enum!(REDUCED_MOTION_SCHEMA, ReducedMotion {
1128    Off = 1,
1129    On = 2,
1130    Unknown = 3,
1131});
1132
1133define_wire_enum!(POINTER_KIND_SCHEMA, PointerKind {
1134    Move = 1,
1135    Enter = 2,
1136    Leave = 3,
1137    Down = 4,
1138    Up = 5,
1139    Cancel = 6,
1140    Wheel = 7,
1141    ContextMenu = 8,
1142});
1143
1144define_wire_enum!(POINTER_TYPE_SCHEMA, PointerType {
1145    Mouse = 1,
1146    Touch = 2,
1147    Pen = 3,
1148});
1149
1150define_wire_enum!(WHEEL_DELTA_MODE_SCHEMA, WheelDeltaMode {
1151    Pixel = 0,
1152    Line = 1,
1153    Page = 2,
1154});
1155
1156// CSS `cursor` keywords, by name and in the order CSS lists them. `Auto` is "let the widget
1157// under the pointer decide".
1158define_wire_enum!(CURSOR_SCHEMA, Cursor {
1159    Auto = 0,
1160    Default = 1,
1161    Pointer = 2,
1162    Text = 3,
1163    VerticalText = 4,
1164    Crosshair = 5,
1165    Move = 6,
1166    Grab = 7,
1167    Grabbing = 8,
1168    NotAllowed = 9,
1169    NoDrop = 10,
1170    Wait = 11,
1171    Progress = 12,
1172    Help = 13,
1173    ContextMenu = 14,
1174    Cell = 15,
1175    Copy = 16,
1176    Alias = 17,
1177    AllScroll = 18,
1178    ZoomIn = 19,
1179    ZoomOut = 20,
1180    ColResize = 21,
1181    RowResize = 22,
1182    EResize = 23,
1183    NResize = 24,
1184    NeResize = 25,
1185    NwResize = 26,
1186    SResize = 27,
1187    SeResize = 28,
1188    SwResize = 29,
1189    WResize = 30,
1190    EwResize = 31,
1191    NsResize = 32,
1192    NeswResize = 33,
1193    NwseResize = 34,
1194    None = 35,
1195});
1196
1197// CSS `user-select`, resolved by the Host. `Auto` follows the parent.
1198define_wire_enum!(USER_SELECT_SCHEMA, UserSelect {
1199    Auto = 0,
1200    None = 1,
1201    Text = 2,
1202    All = 3,
1203});
1204
1205/// Every role enum codegen mirrors, in wire order. Appending is the only allowed edit.
1206pub const ROLE_ENUM_SCHEMA: &[RoleEnumSchema] = &[
1207    RoleEnumSchema {
1208        name: "ColorRole",
1209        variants: COLOR_ROLE_SCHEMA,
1210    },
1211    RoleEnumSchema {
1212        name: "TypeRole",
1213        variants: TYPE_ROLE_SCHEMA,
1214    },
1215    RoleEnumSchema {
1216        name: "ShapeRole",
1217        variants: SHAPE_ROLE_SCHEMA,
1218    },
1219    RoleEnumSchema {
1220        name: "MotionRole",
1221        variants: MOTION_ROLE_SCHEMA,
1222    },
1223    RoleEnumSchema {
1224        name: "TileMode",
1225        variants: TILE_MODE_SCHEMA,
1226    },
1227    RoleEnumSchema {
1228        name: "MaterialRole",
1229        variants: MATERIAL_ROLE_SCHEMA,
1230    },
1231    RoleEnumSchema {
1232        name: "SpaceRole",
1233        variants: SPACE_ROLE_SCHEMA,
1234    },
1235    RoleEnumSchema {
1236        name: "TextAlign",
1237        variants: TEXT_ALIGN_SCHEMA,
1238    },
1239    RoleEnumSchema {
1240        name: "TextOverflow",
1241        variants: TEXT_OVERFLOW_SCHEMA,
1242    },
1243    RoleEnumSchema {
1244        name: "Arrangement",
1245        variants: ARRANGEMENT_SCHEMA,
1246    },
1247    RoleEnumSchema {
1248        name: "Alignment",
1249        variants: ALIGNMENT_SCHEMA,
1250    },
1251    RoleEnumSchema {
1252        name: "ButtonVariant",
1253        variants: BUTTON_VARIANT_SCHEMA,
1254    },
1255    RoleEnumSchema {
1256        name: "ButtonKind",
1257        variants: BUTTON_KIND_SCHEMA,
1258    },
1259    RoleEnumSchema {
1260        name: "DesignSystem",
1261        variants: DESIGN_SYSTEM_SCHEMA,
1262    },
1263    RoleEnumSchema {
1264        name: "ColorScheme",
1265        variants: COLOR_SCHEME_SCHEMA,
1266    },
1267    RoleEnumSchema {
1268        name: "AssetKind",
1269        variants: ASSET_KIND_SCHEMA,
1270    },
1271    RoleEnumSchema {
1272        name: "IconRole",
1273        variants: ICON_ROLE_SCHEMA,
1274    },
1275    RoleEnumSchema {
1276        name: "MessageDuration",
1277        variants: MESSAGE_DURATION_SCHEMA,
1278    },
1279    RoleEnumSchema {
1280        name: "Chrome",
1281        variants: CHROME_SCHEMA,
1282    },
1283    RoleEnumSchema {
1284        name: "TitleBar",
1285        variants: TITLE_BAR_SCHEMA,
1286    },
1287    RoleEnumSchema {
1288        name: "SlotRole",
1289        variants: SLOT_ROLE_SCHEMA,
1290    },
1291    RoleEnumSchema {
1292        name: "NotificationImportance",
1293        variants: NOTIFICATION_IMPORTANCE_SCHEMA,
1294    },
1295    RoleEnumSchema {
1296        name: "NotificationPresentation",
1297        variants: NOTIFICATION_PRESENTATION_SCHEMA,
1298    },
1299    RoleEnumSchema {
1300        name: "NotificationPermission",
1301        variants: NOTIFICATION_PERMISSION_SCHEMA,
1302    },
1303    RoleEnumSchema {
1304        name: "WordBreak",
1305        variants: WORD_BREAK_SCHEMA,
1306    },
1307    RoleEnumSchema {
1308        name: "OverflowWrap",
1309        variants: OVERFLOW_WRAP_SCHEMA,
1310    },
1311    RoleEnumSchema {
1312        name: "GenericFamily",
1313        variants: GENERIC_FAMILY_SCHEMA,
1314    },
1315    RoleEnumSchema {
1316        name: "AnimatedProperty",
1317        variants: ANIMATED_PROPERTY_SCHEMA,
1318    },
1319    RoleEnumSchema {
1320        name: "PlaybackDirection",
1321        variants: PLAYBACK_DIRECTION_SCHEMA,
1322    },
1323    RoleEnumSchema {
1324        name: "FillMode",
1325        variants: FILL_MODE_SCHEMA,
1326    },
1327    RoleEnumSchema {
1328        name: "PlayState",
1329        variants: PLAY_STATE_SCHEMA,
1330    },
1331    RoleEnumSchema {
1332        name: "ColorInterpolation",
1333        variants: COLOR_INTERPOLATION_SCHEMA,
1334    },
1335    RoleEnumSchema {
1336        name: "TimingKind",
1337        variants: TIMING_KIND_SCHEMA,
1338    },
1339    RoleEnumSchema {
1340        name: "StepPosition",
1341        variants: STEP_POSITION_SCHEMA,
1342    },
1343    RoleEnumSchema {
1344        name: "TransformFunctionKind",
1345        variants: TRANSFORM_FUNCTION_KIND_SCHEMA,
1346    },
1347    RoleEnumSchema {
1348        name: "AnimationControl",
1349        variants: ANIMATION_CONTROL_SCHEMA,
1350    },
1351    RoleEnumSchema {
1352        name: "AnimationEventKind",
1353        variants: ANIMATION_EVENT_KIND_SCHEMA,
1354    },
1355    RoleEnumSchema {
1356        name: "ReducedMotion",
1357        variants: REDUCED_MOTION_SCHEMA,
1358    },
1359    RoleEnumSchema {
1360        name: "DecorationKind",
1361        variants: DECORATION_KIND_SCHEMA,
1362    },
1363    RoleEnumSchema {
1364        name: "Severity",
1365        variants: SEVERITY_SCHEMA,
1366    },
1367    RoleEnumSchema {
1368        name: "HoverPhase",
1369        variants: HOVER_PHASE_SCHEMA,
1370    },
1371    RoleEnumSchema {
1372        name: "GutterDecorationKind",
1373        variants: GUTTER_DECORATION_KIND_SCHEMA,
1374    },
1375    RoleEnumSchema {
1376        name: "RevealAlign",
1377        variants: REVEAL_ALIGN_SCHEMA,
1378    },
1379    RoleEnumSchema {
1380        name: "PointerKind",
1381        variants: POINTER_KIND_SCHEMA,
1382    },
1383    RoleEnumSchema {
1384        name: "PointerType",
1385        variants: POINTER_TYPE_SCHEMA,
1386    },
1387    RoleEnumSchema {
1388        name: "WheelDeltaMode",
1389        variants: WHEEL_DELTA_MODE_SCHEMA,
1390    },
1391    RoleEnumSchema {
1392        name: "Cursor",
1393        variants: CURSOR_SCHEMA,
1394    },
1395    RoleEnumSchema {
1396        name: "UserSelect",
1397        variants: USER_SELECT_SCHEMA,
1398    },
1399];
1400
1401/// Every place that takes a colour takes a `Paint`, so colour is expressed once.
1402///
1403/// Encoded as one `u64`: the high 32 bits are the kind, the low 32 bits are the value.
1404#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1405pub enum Paint {
1406    Role(ColorRole),
1407    Literal(Color),
1408    /// A registered brush: a gradient, or a picture laid out as a fill.
1409    ///
1410    /// An id rather than the thing itself, because a list of stops does not fit the two
1411    /// words a modifier has and because a brush outlives the frame that draws it, which
1412    /// is what the asset path already exists for.
1413    Asset(u32),
1414}
1415
1416const PAINT_KIND_ROLE: u64 = 1;
1417const PAINT_KIND_LITERAL: u64 = 2;
1418const PAINT_KIND_ASSET: u64 = 3;
1419
1420impl Paint {
1421    pub const fn to_bits(self) -> u64 {
1422        match self {
1423            Self::Role(role) => (PAINT_KIND_ROLE << 32) | role as u64,
1424            Self::Literal(color) => (PAINT_KIND_LITERAL << 32) | color.0 as u64,
1425            Self::Asset(id) => (PAINT_KIND_ASSET << 32) | id as u64,
1426        }
1427    }
1428
1429    pub fn from_bits(bits: u64) -> Option<Self> {
1430        let value = bits as u32;
1431        match bits >> 32 {
1432            PAINT_KIND_ROLE => Some(Self::Role(
1433                ColorRole::try_from(u16::try_from(value).ok()?).ok()?,
1434            )),
1435            PAINT_KIND_LITERAL => Some(Self::Literal(Color(value))),
1436            PAINT_KIND_ASSET => Some(Self::Asset(value)),
1437            _ => None,
1438        }
1439    }
1440}
1441
1442// How the window wears its title bar.
1443//
1444// `Modern` runs content into the title bar area and is the default, because every desktop
1445// platform now expects it and a window with a separate system bar looks a decade old.
1446//
1447// `System` is the platform's ordinary bar, and it is kept deliberately. Some tool-shaped
1448// applications want one, and more importantly it is where an application goes when
1449// `Modern` turns out to be wrong on some machine: a default with no way out of it is a
1450// default that strands people.
1451define_wire_enum!(CHROME_SCHEMA, Chrome {
1452    Modern = 1,
1453    System = 2,
1454});
1455
1456// Which of two title bars this window has. `Chrome` chooses who draws the caption; this
1457// chooses, inside that, what kind of window the caption belongs to.
1458//
1459// What each one means is not the same on every platform, and the numbers are never the
1460// Host's. On macOS the buttons sit further in and the window is rounded more; on Windows
1461// and Linux the caption is a different height and the buttons stay where that platform
1462// puts them.
1463define_wire_enum!(TITLE_BAR_SCHEMA, TitleBar {
1464    // What an application on that platform actually looks like now, which is why it is the
1465    // default.
1466    Normal = 1,
1467    // An ordinary window. For tool applications, for the places the other reads oddly, and
1468    // as the way back.
1469    Simple = 2,
1470});
1471
1472/// What an application may decide about its own window.
1473///
1474/// The window belongs to the Renderer, so this is short and stays short. What is here is
1475/// what an application knows and the Renderer cannot guess: what the window is called and
1476/// how large it should open. Nothing here names a colour or a corner, because those are
1477/// the design system's, and nothing here is read more than once.
1478#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1479pub struct Window {
1480    pub chrome: Chrome,
1481    /// Which of the two title bars this window has.
1482    pub title_bar: TitleBar,
1483    /// What the window calls itself.
1484    ///
1485    /// Empty means the application said nothing, and the renderer uses its own name for
1486    /// it. It is not decoration: a desktop lists windows by this, and a window with no
1487    /// title of its own is listed under whatever the renderer happened to be called.
1488    pub title: &'static str,
1489    /// The picture the window wears, as an asset id, or zero for none.
1490    ///
1491    /// An id rather than a path or a name, because a path is a fact about the machine the
1492    /// application was built on and a name asks the toolkit to find something it may not
1493    /// have. Registering the bytes and referring to them is what every other picture in
1494    /// this protocol does.
1495    pub icon: u32,
1496    /// Zero means the Renderer chooses, which is what an application that said nothing
1497    /// gets. A size is in the same density independent pixels gestures are measured in.
1498    pub width: u16,
1499    pub height: u16,
1500    pub min_width: u16,
1501    pub min_height: u16,
1502    pub resizable: bool,
1503    /// The application's zoom level, where it asked for one.
1504    ///
1505    /// `None` leaves the level to the Renderer, which starts from what the reader chose
1506    /// last time. A level set here is applied, remembered and reported back the way one
1507    /// chosen with the keys is. Kept inside `-8..=8`.
1508    pub zoom_level: Option<i8>,
1509}
1510
1511impl Window {
1512    pub const fn new() -> Self {
1513        Self {
1514            chrome: Chrome::Modern,
1515            title_bar: TitleBar::Normal,
1516            title: "",
1517            icon: 0,
1518            width: 0,
1519            height: 0,
1520            min_width: 0,
1521            min_height: 0,
1522            resizable: true,
1523            zoom_level: None,
1524        }
1525    }
1526
1527    /// Which of the two title bars this window has.
1528    ///
1529    /// `Normal` unless it is said otherwise, because that is the window an application on
1530    /// this platform has now. What the mode is worth in pixels is the design system's, so
1531    /// nothing here is a measurement.
1532    pub const fn with_title_bar(mut self, title_bar: TitleBar) -> Self {
1533        self.title_bar = title_bar;
1534        self
1535    }
1536
1537    /// Names the window. A literal, because this is read once before the window is stood
1538    /// up and never again, so there is nothing for a computed title to change.
1539    pub const fn with_title(mut self, title: &'static str) -> Self {
1540        self.title = title;
1541        self
1542    }
1543
1544    /// Dresses the window in an asset that was registered with [`AssetKind::Png`].
1545    pub const fn with_icon(mut self, asset_id: u32) -> Self {
1546        self.icon = asset_id;
1547        self
1548    }
1549
1550    pub const fn with_chrome(mut self, chrome: Chrome) -> Self {
1551        self.chrome = chrome;
1552        self
1553    }
1554
1555    pub const fn with_size(mut self, width: u16, height: u16) -> Self {
1556        self.width = width;
1557        self.height = height;
1558        self
1559    }
1560
1561    pub const fn with_min_size(mut self, width: u16, height: u16) -> Self {
1562        self.min_width = width;
1563        self.min_height = height;
1564        self
1565    }
1566
1567    pub const fn resizable(mut self, resizable: bool) -> Self {
1568        self.resizable = resizable;
1569        self
1570    }
1571
1572    /// Starts the window at an application zoom level, kept inside `-8..=8`.
1573    pub const fn with_zoom_level(mut self, level: i8) -> Self {
1574        self.zoom_level = Some(if level < -8 {
1575            -8
1576        } else if level > 8 {
1577            8
1578        } else {
1579            level
1580        });
1581        self
1582    }
1583}
1584
1585impl Default for Window {
1586    fn default() -> Self {
1587        Self::new()
1588    }
1589}
1590
1591/// A design system choice plus how it reacts to the host platform.
1592#[derive(Clone, Copy, Debug, Eq, PartialEq)]
1593pub struct Theme {
1594    pub design_system: DesignSystem,
1595    pub fallback: DesignSystem,
1596    pub color_scheme: ColorScheme,
1597    pub adaptive: bool,
1598    /// The font asset each type role resolves to, or zero for the system font.
1599    ///
1600    /// Per theme and not per node. A node that could name its own font would be a node
1601    /// deciding typography, and the whole point of roles is that it does not: an
1602    /// application changes what `Display` is made of, and every title changes with it.
1603    ///
1604    /// Indexed by the role's wire tag minus one, so the array and the enum cannot drift
1605    /// apart without the compiler saying so.
1606    pub fonts: [u32; TYPE_ROLE_COUNT],
1607    /// The application's own colours, laid over whichever design system is chosen.
1608    ///
1609    /// A reference to something that lives for the whole program, so that a theme stays
1610    /// `Copy` and can still be built in a `const`. The same palette goes onto every system
1611    /// an adaptive theme may pick, because a brand colour does not change with the
1612    /// platform. A palette built while the application runs is handed to `use_theme`,
1613    /// which owns it, so application code never has to give it that lifetime itself.
1614    pub palette: Option<&'static crate::palette::Palette>,
1615}
1616
1617/// How many type roles there are, which is how many font slots a theme carries.
1618pub const TYPE_ROLE_COUNT: usize = 9;
1619
1620impl Theme {
1621    /// The same design system on every platform.
1622    pub const fn unified(design_system: DesignSystem) -> Self {
1623        Self {
1624            design_system,
1625            fallback: design_system,
1626            color_scheme: ColorScheme::FollowSystem,
1627            adaptive: false,
1628            fonts: [0; TYPE_ROLE_COUNT],
1629            palette: None,
1630        }
1631    }
1632
1633    /// Follows the host platform. The fallback is required, so no platform is left undecided.
1634    pub const fn adaptive(fallback: DesignSystem) -> Self {
1635        Self {
1636            design_system: fallback,
1637            fallback,
1638            color_scheme: ColorScheme::FollowSystem,
1639            adaptive: true,
1640            fonts: [0; TYPE_ROLE_COUNT],
1641            palette: None,
1642        }
1643    }
1644
1645    pub const fn with_color_scheme(mut self, color_scheme: ColorScheme) -> Self {
1646        self.color_scheme = color_scheme;
1647        self
1648    }
1649
1650    /// Resolves one type role to a registered font, leaving every other role alone.
1651    ///
1652    /// The asset has to be registered before the theme that names it reaches the
1653    /// Renderer, the same way a picture does. An id that names nothing is reported and
1654    /// the role falls back to the system font, because a screen in the wrong typeface is
1655    /// better than no screen.
1656    ///
1657    /// `TypeRole::None` has no font slot: text with no role names its own font, so asking
1658    /// the theme to give it one changes nothing.
1659    pub const fn with_font(mut self, role: TypeRole, asset: u32) -> Self {
1660        if let TypeRole::None = role {
1661            return self;
1662        }
1663        self.fonts[role as usize - 1] = asset;
1664        self
1665    }
1666
1667    /// Lays the application's own colours over the design system.
1668    ///
1669    /// Only colour roles change. Shape, type, spacing, elevation, motion and how each
1670    /// component is drawn stay the design system's, so the same brand colour reads as
1671    /// that system's button rather than as a copy of another's. Both schemes go to the
1672    /// Renderer at once, so a switch between light and dark never asks the Host.
1673    pub const fn with_palette(mut self, palette: &'static crate::palette::Palette) -> Self {
1674        self.palette = Some(palette);
1675        self
1676    }
1677
1678    /// The font this role resolves to, or `None` for the system font.
1679    pub const fn font(&self, role: TypeRole) -> Option<u32> {
1680        if let TypeRole::None = role {
1681            return None;
1682        }
1683        match self.fonts[role as usize - 1] {
1684            0 => None,
1685            asset => Some(asset),
1686        }
1687    }
1688}
1689
1690/// Saying nothing follows the host platform, with Material 3 where the platform has no look
1691/// of its own. The default used to be `unified(Material3)`, on the theory that a default
1692/// looking the same everywhere is more predictable. In practice that meant macOS showed a
1693/// Material screen with no configuration, which is the wrong first impression for a toolkit
1694/// that claims native desktop UI, and it meant nothing on the default path ever exercised
1695/// platform adaptation. A default that can be broken without anyone noticing is not
1696/// predictability. An application that wants one design system everywhere says
1697/// `Theme::unified(..)`, which is a clearer statement of that intent than silence was.
1698impl Default for Theme {
1699    fn default() -> Self {
1700        Self::adaptive(DesignSystem::Material3)
1701    }
1702}
1703
1704#[derive(Clone, Debug, PartialEq)]
1705pub enum Modifier {
1706    Empty,
1707    Padding(f32),
1708    FillMaxWidth,
1709    FillMaxHeight,
1710    Width(f32),
1711    Height(f32),
1712    Size {
1713        width: f32,
1714        height: f32,
1715    },
1716    /// A `Paint`, not a raw ARGB. Colour has exactly one wire representation.
1717    Background(Paint),
1718    Clickable {
1719        handler_id: u64,
1720    },
1721    PaddingRole(SpaceRole),
1722    PaddingEach {
1723        start: f32,
1724        top: f32,
1725        end: f32,
1726        bottom: f32,
1727    },
1728    Weight(f32),
1729    Shape {
1730        top_start: f32,
1731        top_end: f32,
1732        bottom_end: f32,
1733        bottom_start: f32,
1734    },
1735    ShapeRole(ShapeRole),
1736    Border {
1737        width: f32,
1738        paint: Paint,
1739    },
1740    /// One dp value. How the shadow is drawn is the design system's rule: tonal lift plus a
1741    /// shadow, a wide soft shadow, or layered shadow plus a hairline stroke.
1742    Elevation(f32),
1743    /// Asks the Renderer to report this node's measured size, the way it reports the
1744    /// window's.
1745    ///
1746    /// The token is the application's own name for the node. A node id belongs to the
1747    /// Renderer and is never something the Host chose, so a screen that wants to know how
1748    /// wide one of its own containers ended up needs a name it gave itself. The Renderer
1749    /// does not read it.
1750    ObserveSize {
1751        token: u32,
1752    },
1753    /// Which curve and length this node's changes run along.
1754    ///
1755    /// The node says how important the change is, not how long it takes. Appearing,
1756    /// disappearing, a selection moving and a size changing all read this.
1757    Motion(MotionRole),
1758    /// What this node's surface is made of.
1759    ///
1760    /// A role, so that a system which blurs blurs and a system which does not lifts its
1761    /// surface instead. Nothing here claims a particular effect was achieved.
1762    Material(MaterialRole),
1763    /// Where this node sits inside its parent, in dp from the parent's top left corner.
1764    ///
1765    /// The position a layout engine on the Host already computed. Left and right are the
1766    /// screen's, not the reading direction's: the Host's coordinates are physical, so a
1767    /// right-to-left page is not mirrored a second time here.
1768    Offset {
1769        x: f32,
1770        y: f32,
1771    },
1772    /// This node's size in dp, whatever its parent's constraints say. The size a layout
1773    /// engine on the Host already computed, which is not open to negotiation.
1774    RequiredSize {
1775        width: f32,
1776        height: f32,
1777    },
1778    /// A border whose four sides each have their own width and paint. A node whose four
1779    /// sides are the same uses `Border`.
1780    BorderEach {
1781        top: f32,
1782        right: f32,
1783        bottom: f32,
1784        left: f32,
1785        top_paint: Paint,
1786        right_paint: Paint,
1787        bottom_paint: Paint,
1788        left_paint: Paint,
1789    },
1790    /// A radius in dp for each corner, cut as plain circular arcs. The background, the
1791    /// border, the shadow and a `Clip` all follow it. A node with one radius for every
1792    /// corner uses `Shape`.
1793    CornerEach {
1794        top_left: f32,
1795        top_right: f32,
1796        bottom_right: f32,
1797        bottom_left: f32,
1798    },
1799    /// One shadow cast by this node's outline: an offset, a blur radius and a spread in dp,
1800    /// and its paint, drawn only outside the node as CSS draws `box-shadow`. Several
1801    /// shadows are several of these. A modifier list draws in its order, so a later shadow
1802    /// lies over an earlier one; CSS puts the first of its list on top, so a Host sends
1803    /// that list last to first.
1804    Shadow {
1805        x: f32,
1806        y: f32,
1807        blur: f32,
1808        spread: f32,
1809        paint: Paint,
1810    },
1811    /// Whether this node's content is cut to its outline: the `CornerEach` shape where it
1812    /// has one, its rectangle otherwise.
1813    Clip(bool),
1814    /// The opacity of this node and everything in it, from 0 to 1, applied once to the
1815    /// whole group rather than to each child.
1816    Alpha(f32),
1817    /// Draws this node and everything in it through a 2D affine matrix, the way CSS
1818    /// `transform` does, without changing its layout. Pointer input follows the drawn
1819    /// shape.
1820    ///
1821    /// The matrix means what CSS `matrix(a, b, c, d, e, f)` means: a point `(x, y)` goes to
1822    /// `(a x + c y + e, b x + d y + f)`, with `e` and `f` in dp. The origin is a fraction of
1823    /// the node's measured size, `(0.5, 0.5)` being its centre, so it stays right when the
1824    /// size is decided on the Renderer's side. An origin given as a length is folded into
1825    /// the matrix by the Host, which then sends `(0, 0)`. A matrix with no inverse draws
1826    /// nothing and takes no input. No value may be NaN or infinite.
1827    Transform {
1828        a: f32,
1829        b: f32,
1830        c: f32,
1831        d: f32,
1832        e: f32,
1833        f: f32,
1834        origin_x: f32,
1835        origin_y: f32,
1836    },
1837}
1838
1839impl Modifier {
1840    /// A border with a width and a paint for each side, in CSS order: top, right, bottom,
1841    /// left.
1842    ///
1843    /// Four equal sides are one `Border`, which is the record every node with an ordinary
1844    /// border already sends. `BorderEach` is written only where the sides differ, so a
1845    /// screen that never mixes them never sends the longer record.
1846    pub fn border_sides(widths: [f32; 4], paints: [Paint; 4]) -> Self {
1847        let [top, right, bottom, left] = widths;
1848        let [top_paint, right_paint, bottom_paint, left_paint] = paints;
1849        if widths.iter().all(|width| width.to_bits() == top.to_bits())
1850            && paints.iter().all(|paint| *paint == top_paint)
1851        {
1852            return Self::Border {
1853                width: top,
1854                paint: top_paint,
1855            };
1856        }
1857        Self::BorderEach {
1858            top,
1859            right,
1860            bottom,
1861            left,
1862            top_paint,
1863            right_paint,
1864            bottom_paint,
1865            left_paint,
1866        }
1867    }
1868
1869    /// A radius for each corner, in CSS order: top left, top right, bottom right, bottom
1870    /// left.
1871    ///
1872    /// One radius for every corner is one `Shape`, the record an ordinary rounded node
1873    /// already sends. `CornerEach` is written only where the corners differ.
1874    pub fn corner_radii(radii: [f32; 4]) -> Self {
1875        let [top_left, top_right, bottom_right, bottom_left] = radii;
1876        if radii
1877            .iter()
1878            .all(|radius| radius.to_bits() == top_left.to_bits())
1879        {
1880            return Self::Shape {
1881                top_start: top_left,
1882                top_end: top_left,
1883                bottom_end: top_left,
1884                bottom_start: top_left,
1885            };
1886        }
1887        Self::CornerEach {
1888            top_left,
1889            top_right,
1890            bottom_right,
1891            bottom_left,
1892        }
1893    }
1894}
1895
1896/// The timing function of one keyframe, which shapes the segment to the next one.
1897#[derive(Clone, Copy, Debug, PartialEq)]
1898pub enum Timing {
1899    Linear,
1900    /// CSS `cubic-bezier(x1, y1, x2, y2)`. Both `x` values are between 0 and 1.
1901    CubicBezier {
1902        x1: f32,
1903        y1: f32,
1904        x2: f32,
1905        y2: f32,
1906    },
1907    /// CSS `steps(count, position)`. `count` is at least 1, and at least 2 for `JumpNone`.
1908    Steps {
1909        count: u32,
1910        position: StepPosition,
1911    },
1912}
1913
1914/// One CSS transform function: its kind and up to six values, the unused ones zero.
1915///
1916/// `Translate` is x and y in dp, `Rotate` degrees (not folded into one turn, so 0 to 720
1917/// is two turns), `Scale` x and y, `Skew` the x and y angles in degrees, and `Matrix` the
1918/// six values of CSS `matrix()`.
1919#[derive(Clone, Copy, Debug, PartialEq)]
1920pub struct TransformFunction {
1921    pub kind: TransformFunctionKind,
1922    pub values: [f32; 6],
1923}
1924
1925impl TransformFunction {
1926    pub const fn translate(x: f32, y: f32) -> Self {
1927        Self {
1928            kind: TransformFunctionKind::Translate,
1929            values: [x, y, 0.0, 0.0, 0.0, 0.0],
1930        }
1931    }
1932
1933    pub const fn rotate(degrees: f32) -> Self {
1934        Self {
1935            kind: TransformFunctionKind::Rotate,
1936            values: [degrees, 0.0, 0.0, 0.0, 0.0, 0.0],
1937        }
1938    }
1939
1940    pub const fn scale(x: f32, y: f32) -> Self {
1941        Self {
1942            kind: TransformFunctionKind::Scale,
1943            values: [x, y, 0.0, 0.0, 0.0, 0.0],
1944        }
1945    }
1946
1947    pub const fn skew(x_degrees: f32, y_degrees: f32) -> Self {
1948        Self {
1949            kind: TransformFunctionKind::Skew,
1950            values: [x_degrees, y_degrees, 0.0, 0.0, 0.0, 0.0],
1951        }
1952    }
1953
1954    pub const fn matrix(a: f32, b: f32, c: f32, d: f32, e: f32, f: f32) -> Self {
1955        Self {
1956            kind: TransformFunctionKind::Matrix,
1957            values: [a, b, c, d, e, f],
1958        }
1959    }
1960}
1961
1962/// What one keyframe holds, which has to be what its animation's property takes.
1963#[derive(Clone, Debug, PartialEq)]
1964pub enum KeyframeValue {
1965    Alpha(f32),
1966    /// For `Color`, `Background` and `BorderColor`. A role is resolved against the theme every frame.
1967    Paint(Paint),
1968    /// A list of functions, the same kinds in the same order in every keyframe.
1969    Transform(std::borrow::Cow<'static, [TransformFunction]>),
1970}
1971
1972#[derive(Clone, Debug, PartialEq)]
1973pub struct Keyframe {
1974    /// Where in an iteration this keyframe stands, from 0 to 1.
1975    pub offset: f32,
1976    /// How the segment from this keyframe to the next is timed.
1977    pub timing: Timing,
1978    /// Starts from whatever the node shows the frame this animation begins, instead of
1979    /// `value`. Only the first keyframe may say so: it is how a CSS transition that
1980    /// changes course midway carries on from where it was without a jump.
1981    pub from_presented: bool,
1982    pub value: KeyframeValue,
1983}
1984
1985/// Which moments of an animation are reported back as `AnimationEvent`s. None, the
1986/// default, means the animation plays to its end without a single call to the Host.
1987#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
1988pub struct AnimationEvents(pub u8);
1989
1990impl AnimationEvents {
1991    pub const NONE: Self = Self(0);
1992    pub const READY: Self = Self(1);
1993    pub const ACTIVE: Self = Self(2);
1994    pub const ITERATION: Self = Self(4);
1995    pub const END: Self = Self(8);
1996    /// Every kind there is. A bit outside these is a record the sides disagree about.
1997    pub const ALL: Self = Self(1 | 2 | 4 | 8);
1998
1999    pub const fn union(self, other: Self) -> Self {
2000        Self(self.0 | other.0)
2001    }
2002
2003    pub const fn contains(self, other: Self) -> bool {
2004        self.0 & other.0 == other.0
2005    }
2006}
2007
2008/// One animation the Renderer plays to its end on its own frame clock, described once.
2009///
2010/// The key is `(node_id, property, slot)`: a second animation with the same key replaces
2011/// the first, and of the ones in effect on one property the highest slot is what shows.
2012/// Timing follows Web Animations Level 1, which is what CSS transitions and animations
2013/// are defined by.
2014#[derive(Clone, Debug, PartialEq)]
2015pub struct Animation<'a> {
2016    pub node_id: u32,
2017    /// The Host's own name for the animation, returned in every event about it.
2018    pub animation_id: u32,
2019    pub property: AnimatedProperty,
2020    pub slot: u8,
2021    pub direction: PlaybackDirection,
2022    pub fill: FillMode,
2023    pub play_state: PlayState,
2024    /// For `Color`, `Background` and `BorderColor` only, and required there.
2025    pub interpolation: Option<ColorInterpolation>,
2026    /// On the Renderer's frame clock. Zero starts it on the first frame that draws it.
2027    pub start_time_nanos: u64,
2028    /// May be negative, which starts it partway through.
2029    pub delay_ms: f32,
2030    pub duration_ms: f32,
2031    /// Infinite for `infinite`.
2032    pub iterations: f32,
2033    /// For `Transform` only: the origin, as a fraction of the node's size.
2034    pub origin_x: f32,
2035    pub origin_y: f32,
2036    pub events: AnimationEvents,
2037    /// At least two, the first at offset 0 and the last at 1.
2038    pub keyframes: std::borrow::Cow<'a, [Keyframe]>,
2039}
2040
2041const NO_FIELDS: &[FieldSchema] = &[];
2042const TOKEN_U32_FIELD: &[FieldSchema] = &[FieldSchema {
2043    name: "token",
2044    ty: FieldType::U32,
2045    slot: FieldSlot::FirstLow,
2046}];
2047const VALUE_FLOAT_FIELD: &[FieldSchema] = &[FieldSchema {
2048    name: "value",
2049    ty: FieldType::Float,
2050    slot: FieldSlot::FirstLow,
2051}];
2052const SIZE_FIELDS: &[FieldSchema] = &[
2053    FieldSchema {
2054        name: "width",
2055        ty: FieldType::Float,
2056        slot: FieldSlot::FirstLow,
2057    },
2058    FieldSchema {
2059        name: "height",
2060        ty: FieldType::Float,
2061        slot: FieldSlot::SecondLow,
2062    },
2063];
2064const PAINT_FIELD: &[FieldSchema] = &[FieldSchema {
2065    name: "paint",
2066    ty: FieldType::Paint,
2067    slot: FieldSlot::First,
2068}];
2069const HANDLER_ID_FIELD: &[FieldSchema] = &[FieldSchema {
2070    name: "handlerId",
2071    ty: FieldType::U64,
2072    slot: FieldSlot::First,
2073}];
2074const SPACE_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
2075    name: "role",
2076    ty: FieldType::Role("SpaceRole"),
2077    slot: FieldSlot::FirstLow,
2078}];
2079const SHAPE_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
2080    name: "role",
2081    ty: FieldType::Role("ShapeRole"),
2082    slot: FieldSlot::FirstLow,
2083}];
2084const MOTION_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
2085    name: "role",
2086    ty: FieldType::Role("MotionRole"),
2087    slot: FieldSlot::FirstLow,
2088}];
2089const MATERIAL_ROLE_FIELD: &[FieldSchema] = &[FieldSchema {
2090    name: "role",
2091    ty: FieldType::Role("MaterialRole"),
2092    slot: FieldSlot::FirstLow,
2093}];
2094const PADDING_EACH_FIELDS: &[FieldSchema] = &[
2095    FieldSchema {
2096        name: "start",
2097        ty: FieldType::Float,
2098        slot: FieldSlot::FirstLow,
2099    },
2100    FieldSchema {
2101        name: "top",
2102        ty: FieldType::Float,
2103        slot: FieldSlot::FirstHigh,
2104    },
2105    FieldSchema {
2106        name: "end",
2107        ty: FieldType::Float,
2108        slot: FieldSlot::SecondLow,
2109    },
2110    FieldSchema {
2111        name: "bottom",
2112        ty: FieldType::Float,
2113        slot: FieldSlot::SecondHigh,
2114    },
2115];
2116const SHAPE_FIELDS: &[FieldSchema] = &[
2117    FieldSchema {
2118        name: "topStart",
2119        ty: FieldType::Float,
2120        slot: FieldSlot::FirstLow,
2121    },
2122    FieldSchema {
2123        name: "topEnd",
2124        ty: FieldType::Float,
2125        slot: FieldSlot::FirstHigh,
2126    },
2127    FieldSchema {
2128        name: "bottomEnd",
2129        ty: FieldType::Float,
2130        slot: FieldSlot::SecondLow,
2131    },
2132    FieldSchema {
2133        name: "bottomStart",
2134        ty: FieldType::Float,
2135        slot: FieldSlot::SecondHigh,
2136    },
2137];
2138const BORDER_FIELDS: &[FieldSchema] = &[
2139    FieldSchema {
2140        name: "width",
2141        ty: FieldType::Float,
2142        slot: FieldSlot::FirstLow,
2143    },
2144    FieldSchema {
2145        name: "paint",
2146        ty: FieldType::Paint,
2147        slot: FieldSlot::Second,
2148    },
2149];
2150const OFFSET_FIELDS: &[FieldSchema] = &[
2151    FieldSchema {
2152        name: "x",
2153        ty: FieldType::Float,
2154        slot: FieldSlot::FirstLow,
2155    },
2156    FieldSchema {
2157        name: "y",
2158        ty: FieldType::Float,
2159        slot: FieldSlot::SecondLow,
2160    },
2161];
2162const BORDER_EACH_FIELDS: &[FieldSchema] = &[
2163    FieldSchema {
2164        name: "top",
2165        ty: FieldType::Float,
2166        slot: FieldSlot::FirstLow,
2167    },
2168    FieldSchema {
2169        name: "right",
2170        ty: FieldType::Float,
2171        slot: FieldSlot::FirstHigh,
2172    },
2173    FieldSchema {
2174        name: "bottom",
2175        ty: FieldType::Float,
2176        slot: FieldSlot::SecondLow,
2177    },
2178    FieldSchema {
2179        name: "left",
2180        ty: FieldType::Float,
2181        slot: FieldSlot::SecondHigh,
2182    },
2183    FieldSchema {
2184        name: "topPaint",
2185        ty: FieldType::Paint,
2186        slot: FieldSlot::Third,
2187    },
2188    FieldSchema {
2189        name: "rightPaint",
2190        ty: FieldType::Paint,
2191        slot: FieldSlot::Fourth,
2192    },
2193    FieldSchema {
2194        name: "bottomPaint",
2195        ty: FieldType::Paint,
2196        slot: FieldSlot::Fifth,
2197    },
2198    FieldSchema {
2199        name: "leftPaint",
2200        ty: FieldType::Paint,
2201        slot: FieldSlot::Sixth,
2202    },
2203];
2204const CORNER_EACH_FIELDS: &[FieldSchema] = &[
2205    FieldSchema {
2206        name: "topLeft",
2207        ty: FieldType::Float,
2208        slot: FieldSlot::FirstLow,
2209    },
2210    FieldSchema {
2211        name: "topRight",
2212        ty: FieldType::Float,
2213        slot: FieldSlot::FirstHigh,
2214    },
2215    FieldSchema {
2216        name: "bottomRight",
2217        ty: FieldType::Float,
2218        slot: FieldSlot::SecondLow,
2219    },
2220    FieldSchema {
2221        name: "bottomLeft",
2222        ty: FieldType::Float,
2223        slot: FieldSlot::SecondHigh,
2224    },
2225];
2226const SHADOW_FIELDS: &[FieldSchema] = &[
2227    FieldSchema {
2228        name: "x",
2229        ty: FieldType::Float,
2230        slot: FieldSlot::FirstLow,
2231    },
2232    FieldSchema {
2233        name: "y",
2234        ty: FieldType::Float,
2235        slot: FieldSlot::FirstHigh,
2236    },
2237    FieldSchema {
2238        name: "blur",
2239        ty: FieldType::Float,
2240        slot: FieldSlot::SecondLow,
2241    },
2242    FieldSchema {
2243        name: "spread",
2244        ty: FieldType::Float,
2245        slot: FieldSlot::SecondHigh,
2246    },
2247    FieldSchema {
2248        name: "paint",
2249        ty: FieldType::Paint,
2250        slot: FieldSlot::Third,
2251    },
2252];
2253const TRANSFORM_FIELDS: &[FieldSchema] = &[
2254    FieldSchema {
2255        name: "a",
2256        ty: FieldType::FiniteFloat,
2257        slot: FieldSlot::FirstLow,
2258    },
2259    FieldSchema {
2260        name: "b",
2261        ty: FieldType::FiniteFloat,
2262        slot: FieldSlot::FirstHigh,
2263    },
2264    FieldSchema {
2265        name: "c",
2266        ty: FieldType::FiniteFloat,
2267        slot: FieldSlot::SecondLow,
2268    },
2269    FieldSchema {
2270        name: "d",
2271        ty: FieldType::FiniteFloat,
2272        slot: FieldSlot::SecondHigh,
2273    },
2274    FieldSchema {
2275        name: "e",
2276        ty: FieldType::FiniteFloat,
2277        slot: FieldSlot::ThirdLow,
2278    },
2279    FieldSchema {
2280        name: "f",
2281        ty: FieldType::FiniteFloat,
2282        slot: FieldSlot::ThirdHigh,
2283    },
2284    FieldSchema {
2285        name: "originX",
2286        ty: FieldType::FiniteFloat,
2287        slot: FieldSlot::FourthLow,
2288    },
2289    FieldSchema {
2290        name: "originY",
2291        ty: FieldType::FiniteFloat,
2292        slot: FieldSlot::FourthHigh,
2293    },
2294];
2295const ENABLED_FIELD: &[FieldSchema] = &[FieldSchema {
2296    name: "enabled",
2297    ty: FieldType::Bool,
2298    slot: FieldSlot::First,
2299}];
2300
2301pub const MODIFIER_SCHEMA: &[VariantSchema] = &[
2302    VariantSchema {
2303        name: "Empty",
2304        tag: 0,
2305        extra_words: 0,
2306        fields: NO_FIELDS,
2307    },
2308    VariantSchema {
2309        name: "Padding",
2310        tag: 1,
2311        extra_words: 0,
2312        fields: VALUE_FLOAT_FIELD,
2313    },
2314    VariantSchema {
2315        name: "FillMaxWidth",
2316        tag: 2,
2317        extra_words: 0,
2318        fields: NO_FIELDS,
2319    },
2320    VariantSchema {
2321        name: "FillMaxHeight",
2322        tag: 3,
2323        extra_words: 0,
2324        fields: NO_FIELDS,
2325    },
2326    VariantSchema {
2327        name: "Width",
2328        tag: 4,
2329        extra_words: 0,
2330        fields: VALUE_FLOAT_FIELD,
2331    },
2332    VariantSchema {
2333        name: "Height",
2334        tag: 5,
2335        extra_words: 0,
2336        fields: VALUE_FLOAT_FIELD,
2337    },
2338    VariantSchema {
2339        name: "Size",
2340        tag: 6,
2341        extra_words: 0,
2342        fields: SIZE_FIELDS,
2343    },
2344    VariantSchema {
2345        name: "Background",
2346        tag: 7,
2347        extra_words: 0,
2348        fields: PAINT_FIELD,
2349    },
2350    VariantSchema {
2351        name: "Clickable",
2352        tag: 8,
2353        extra_words: 0,
2354        fields: HANDLER_ID_FIELD,
2355    },
2356    VariantSchema {
2357        name: "PaddingRole",
2358        tag: 9,
2359        extra_words: 0,
2360        fields: SPACE_ROLE_FIELD,
2361    },
2362    VariantSchema {
2363        name: "PaddingEach",
2364        tag: 10,
2365        extra_words: 0,
2366        fields: PADDING_EACH_FIELDS,
2367    },
2368    VariantSchema {
2369        name: "Weight",
2370        tag: 11,
2371        extra_words: 0,
2372        fields: VALUE_FLOAT_FIELD,
2373    },
2374    VariantSchema {
2375        name: "Shape",
2376        tag: 12,
2377        extra_words: 0,
2378        fields: SHAPE_FIELDS,
2379    },
2380    VariantSchema {
2381        name: "ShapeRole",
2382        tag: 13,
2383        extra_words: 0,
2384        fields: SHAPE_ROLE_FIELD,
2385    },
2386    VariantSchema {
2387        name: "Border",
2388        tag: 14,
2389        extra_words: 0,
2390        fields: BORDER_FIELDS,
2391    },
2392    VariantSchema {
2393        name: "Elevation",
2394        tag: 15,
2395        extra_words: 0,
2396        fields: VALUE_FLOAT_FIELD,
2397    },
2398    VariantSchema {
2399        name: "ObserveSize",
2400        tag: 16,
2401        extra_words: 0,
2402        fields: TOKEN_U32_FIELD,
2403    },
2404    VariantSchema {
2405        name: "Motion",
2406        tag: 17,
2407        extra_words: 0,
2408        fields: MOTION_ROLE_FIELD,
2409    },
2410    VariantSchema {
2411        name: "Material",
2412        tag: 18,
2413        extra_words: 0,
2414        fields: MATERIAL_ROLE_FIELD,
2415    },
2416    // The elements an HTML and CSS screen is drawn with: boxes a layout engine on the Host
2417    // already placed and sized, and the decoration CSS gives them.
2418    VariantSchema {
2419        name: "Offset",
2420        tag: 19,
2421        extra_words: 0,
2422        fields: OFFSET_FIELDS,
2423    },
2424    VariantSchema {
2425        name: "RequiredSize",
2426        tag: 20,
2427        extra_words: 0,
2428        fields: SIZE_FIELDS,
2429    },
2430    // Four widths and four paints are six words, so the record is four words longer.
2431    VariantSchema {
2432        name: "BorderEach",
2433        tag: 21,
2434        extra_words: 4,
2435        fields: BORDER_EACH_FIELDS,
2436    },
2437    VariantSchema {
2438        name: "CornerEach",
2439        tag: 22,
2440        extra_words: 0,
2441        fields: CORNER_EACH_FIELDS,
2442    },
2443    // Four measurements and a paint are three words, one more than the record has.
2444    VariantSchema {
2445        name: "Shadow",
2446        tag: 23,
2447        extra_words: 1,
2448        fields: SHADOW_FIELDS,
2449    },
2450    VariantSchema {
2451        name: "Clip",
2452        tag: 24,
2453        extra_words: 0,
2454        fields: ENABLED_FIELD,
2455    },
2456    VariantSchema {
2457        name: "Alpha",
2458        tag: 25,
2459        extra_words: 0,
2460        fields: VALUE_FLOAT_FIELD,
2461    },
2462    // Eight `f32` are four words: the linear part, the translation, and the origin.
2463    VariantSchema {
2464        name: "Transform",
2465        tag: 26,
2466        extra_words: 2,
2467        fields: TRANSFORM_FIELDS,
2468    },
2469];
2470
2471#[derive(Clone, Debug, PartialEq)]
2472pub struct ColumnProps {
2473    pub enabled: bool,
2474}
2475
2476#[derive(Clone, Debug, PartialEq)]
2477pub struct RowProps {
2478    pub enabled: bool,
2479}
2480
2481#[derive(Clone, Debug, PartialEq)]
2482pub struct BoxProps {
2483    pub enabled: bool,
2484}
2485
2486#[derive(Clone, Debug, PartialEq)]
2487pub struct TextProps<'a> {
2488    pub text: &'a str,
2489}
2490
2491#[derive(Clone, Debug, PartialEq)]
2492pub struct TextFieldProps<'a> {
2493    pub placeholder: &'a str,
2494    pub enabled: bool,
2495    pub multiline: bool,
2496}
2497
2498#[derive(Clone, Debug, PartialEq)]
2499pub struct ButtonProps<'a> {
2500    pub text: &'a str,
2501    pub enabled: bool,
2502}
2503
2504#[derive(Clone, Debug, PartialEq)]
2505pub struct SpacerProps {
2506    pub width: f32,
2507    pub height: f32,
2508}
2509
2510crate::extensions::define_property_schema_with_extensions!(define_wire_enum; PROPERTY_SCHEMA, PropertyKind {
2511    Text = 1,
2512    Placeholder = 2,
2513    Enabled = 3,
2514    Multiline = 4,
2515    OnClick = 5,
2516    OnValueChange = 6,
2517    OnSubmit = 7,
2518    OnFocusLost = 8,
2519    OnKeyDown = 9,
2520    ItemCount = 10,
2521    ItemKey = 11,
2522    OnRangeRequested = 12,
2523    TypeRole = 13,
2524    FontSize = 14,
2525    FontWeight = 15,
2526    LineHeight = 16,
2527    LetterSpacing = 17,
2528    Color = 18,
2529    TextAlign = 19,
2530    MaxLines = 20,
2531    Overflow = 21,
2532    Arrangement = 22,
2533    Spacing = 23,
2534    SpaceRole = 24,
2535    Alignment = 25,
2536    Variant = 26,
2537    // The id of an asset the Host registered. Image and Icon carry nothing else: the
2538    // bytes were copied into the Renderer's cache once, at registration.
2539    Asset = 28,
2540    // Whether a toggle is on. One boolean for all three of them: a selected radio button
2541    // and a switch that is on are the same fact, and the schema names a concept once.
2542    Checked = 32,
2543    // Discrete stops between a slider's two ends. Zero leaves it continuous.
2544    Steps = 33,
2545    // Whether a progress indicator knows how far along it is. When it does not, `Value`
2546    // is not read at all.
2547    Determinate = 34,
2548    // Which of the two forms a progress indicator takes. It picks a shape, not a size.
2549    Circular = 35,
2550    // A divider's axis, which is the only thing a divider carries.
2551    Vertical = 36,
2552    // Whether an overlay is showing. The Renderer owns the state; this seeds it and
2553    // carries changes that came from outside the Renderer.
2554    Open = 40,
2555    OnDismiss = 41,
2556    SelectedIndex = 42,
2557    // The Canvas drawing command list, a byte blob in the batch arena.
2558    Commands = 50,
2559    // The control's current value, in the widget's own unit: an epoch day count for a
2560    // date, minutes since midnight for a time, the chosen position for a Dropdown, a
2561    // position between the ends for a Slider, a fraction for a ProgressIndicator. One tag,
2562    // because "this control's value" is one concept whichever widget holds it.
2563    Value = 51,
2564    // The ends of the selectable range, in the same unit as `Value`.
2565    Min = 52,
2566    Max = 53,
2567    // The meaning of the icon a destination carries, as an `IconRole` tag. Tag 0 is "not
2568    // sent", so a destination without an icon is label only.
2569    Icon = 60,
2570    // Which slot of a Scaffold this subtree fills.
2571    Slot = 61,
2572    // How many columns a grid has, where the screen said a number.
2573    Columns = 62,
2574    // How narrow a column may get before the grid drops one, where the screen said a
2575    // width instead of a number. The Renderer divides its own width by this, which is the
2576    // same kind of judgement as a size class and for the same reason is not the Host's.
2577    MinColumnWidth = 63,
2578    // Runs of different treatment inside one string, as a blob of fixed-length records.
2579    // A Text that says nothing about runs carries none of this and travels as it always
2580    // did.
2581    Spans = 64,
2582    // A node that is willing to have files dropped on it. The handler is the willingness:
2583    // a node without one is never told that files are over it, which is what keeps a
2584    // screen from lighting up every container it has.
2585    OnFilesEntered = 65,
2586    OnFilesDropped = 66,
2587    // Which named group of a navigation's strip this destination belongs to. Neighbouring
2588    // destinations carrying the same string are one group and the string is its heading.
2589    //
2590    // 76 rather than 67, because 67 through 74 are reserved for the gestures and 75 for a
2591    // button's kind. A reservation is not a free tag.
2592    Section = 76,
2593    // How many a badge counts. Absent rather than zero when the badge is a dot or a word,
2594    // because "no count" and "a count of nothing" are different things to show.
2595    //
2596    // 80 opens a fresh block of ten for widget tags 40 onwards, after the last tag in use
2597    // anywhere, so two pieces of work landing in either order cannot collide.
2598    Count = 80,
2599    // Whether a split pane's side pane may be folded away by dragging past its minimum, or
2600    // by the keyboard, or by the platform's own show and hide sidebar command. Whether a
2601    // side pane can be hidden is a fact about the screen rather than about the design
2602    // system: a list of sessions can, a settings screen's list of sections cannot.
2603    //
2604    // 90 opens the split pane's block of ten, after the badge's.
2605    Collapsible = 90,
2606    // What a button is, as opposed to how loud it is: a plain button, or one key of a
2607    // dense grid of them. The variant still chooses the fill and the emphasis; this lets
2608    // the design system give the key its own calculator's silhouette, so the Host never
2609    // sends a radius or a shape role to get one.
2610    //
2611    // 75 was reserved for this when the gestures took 67 through 74, and stays here
2612    // rather than with its neighbours by number because this list is append only.
2613    ButtonKind = 75,
2614    // A code editor's own block, after the split pane's.
2615    //
2616    // The decorations over the document and the colour runs of its syntax, each a blob of
2617    // fixed-length records sent whole. Every record carries the document version its
2618    // range was written against, so a list computed while the reader kept typing still
2619    // lands where it was meant to.
2620    Decorations = 100,
2621    SyntaxSpans = 101,
2622    // How many columns a tab advances. Absent leaves it to the design system.
2623    TabWidth = 102,
2624    OnEditRejected = 103,
2625    OnHover = 104,
2626    OnSave = 105,
2627    // One listener for every decoration that can be pressed or accepted. The ids inside
2628    // the decoration blob are the application's own names for them, not handlers, so the
2629    // event carries the id and the application tells them apart.
2630    OnDecorationClick = 106,
2631    // The editor's additions: the stretches that can be folded and the marks beside the
2632    // line numbers, each a blob of fixed-length records sent whole, and the listener for
2633    // the selection moving. The other four listeners are in the block at 130.
2634    Folds = 107,
2635    GutterDecorations = 108,
2636    OnSelectionChange = 109,
2637    // The text a page laid out by CSS draws, which takes its letters from its own values
2638    // rather than from a rung of the type ladder. 110 opens the block for it.
2639    //
2640    // The fonts to try, in order, for text whose role is `TypeRole::None`: a list of up to
2641    // eight registered font assets, installed families by name and generic families, as a
2642    // blob. Text with a role ignores it, because its font is the theme's.
2643    Font = 110,
2644    // Where a line may break, and whether a word too long for its line may be broken
2645    // inside. The `WordBreak` and `OverflowWrap` roles.
2646    WordBreak = 111,
2647    OverflowWrap = 112,
2648    // How many spaces' width apart the tab stops are. Absent is eight. A tab advances to
2649    // the next stop, which only the side that measures the letters can work out.
2650    TabSize = 113,
2651    // Whether the sizes are CSS pixels, which the system's font scale does not enlarge.
2652    AbsoluteSize = 114,
2653    // Whether lines wrap at the width they are given. Absent is yes; `false` is CSS
2654    // `nowrap` and `pre`, whose lines run as long as their text.
2655    SoftWrap = 115,
2656    // The font each run of a `Text` with no role is set in, as a blob beside `Spans`
2657    // rather than inside its records: a run record keeps the 36 bytes it has always had,
2658    // and only text that names fonts carries this at all.
2659    SpanFonts = 116,
2660    // The rest of the code editor's listeners, in the first block free after the text and
2661    // pointer blocks. The visible lines changing, the editor gaining or losing focus, a
2662    // stretch folded or unfolded, and an input method starting or ending a composition.
2663    OnVisibleRangeChange = 130,
2664    OnEditorFocusChange = 131,
2665    OnFoldChange = 132,
2666    OnCompositionChange = 133,
2667    // The node's pointer subscription: the handler, and the kinds it wants as a bit mask
2668    // (`1 << (kind - 1)`). 67 was reserved for a gesture handler that was never sent, so
2669    // it is free to carry this one. It is listed here, after the newer blocks, because
2670    // this list is append only.
2671    OnPointer = 67,
2672    // 140 opens the block after the animation and HTML-text properties.
2673    PointerKinds = 140,
2674    // The cursor the platform shows over the node and whether its text can be selected.
2675    // The Host resolves CSS inheritance and sends the value for each node.
2676    Cursor = 141,
2677    UserSelect = 142,
2678    // How large an HTML island is drawn, as the f32 factor `k` (see `ZoomChanged`), on the
2679    // island's root `AbsoluteBox`. 120 opens the block for zoom. Absent is 1.
2680    IslandZoom = 120,
2681});
2682
2683#[derive(Clone, Debug, PartialEq)]
2684pub enum EventPayload<'a> {
2685    Clicked,
2686    TextChanged(&'a str),
2687    TextSubmitted(&'a str),
2688    FocusLost,
2689    ProtocolError {
2690        code: u32,
2691        message: &'a str,
2692    },
2693    KeyDown {
2694        key: Key,
2695        shift_key: bool,
2696        ctrl_key: bool,
2697        alt_key: bool,
2698        meta_key: bool,
2699    },
2700    /// The Renderer asks the Host to materialise the visible item range.
2701    RangeRequested {
2702        start: u32,
2703        count: u32,
2704    },
2705    /// A control's new value, in the widget's own unit: an epoch count for a picker, a
2706    /// position for a slider, 0.0 or 1.0 for a toggle. The Renderer decided how the user
2707    /// reached it, so nothing about calendars, wheels, tracks or thumbs crosses here.
2708    ///
2709    /// One `f64` holds all of them: integers up to 2^53 survive it exactly, so splitting
2710    /// the event in two would only give the same concept two names.
2711    ValueChanged(f64),
2712    /// The window, or one observed node, moved into a different size class. The Renderer
2713    /// measures it and sends this only when a class changes, never on every layout pass.
2714    /// A `node_id` of 0 is the window; anything else is the node that asked to be
2715    /// measured.
2716    WindowSizeChanged {
2717        width_dp: f32,
2718        height_dp: f32,
2719        class: WindowSizeClass,
2720        height_class: WindowHeightClass,
2721    },
2722    /// Files are over a node that said it would take them. Nothing about what they are:
2723    /// the platforms disagree about what is knowable before a drop, and a screen that
2724    /// only needs to light up does not need to know.
2725    FilesEntered,
2726    /// Files were let go over a node. The paths arrive together, separated by a byte no
2727    /// path on any of the three desktops may contain.
2728    FilesDropped(&'a str),
2729    /// The Renderer resolved the theme and this is what it chose. Sent once and then
2730    /// only when the answer changes, the same way a size class is.
2731    DesignSystemResolved(DesignSystem),
2732    /// The Renderer lost its node table and asks for the whole tree again. A recreated
2733    /// Activity is the case that produces it.
2734    Resync,
2735    /// The platform brought the UI back. Timers and animations resume.
2736    LifecycleStart,
2737    /// The platform stopped the UI. Timers and animations are suppressed, so a process
2738    /// that is not on screen is not asked to draw.
2739    LifecycleStop,
2740    /// The user pressed a notification the application posted. `action` is 0 for the body
2741    /// and 1 or 2 for an action button; `key` is the name the application gave it.
2742    ///
2743    /// A name rather than a handler, because a notification outlives the component that
2744    /// posted it by hours and a callback dies with its scope.
2745    NotificationActivated {
2746        action: u32,
2747        key: &'a str,
2748    },
2749    /// Whether notifications may be shown, sent once after start and then only when the
2750    /// answer changes.
2751    NotificationPermissionChanged(NotificationPermission),
2752    /// One committed edit to a code editor's document.
2753    ///
2754    /// The range is in the document as it stood before the edit, and `version` is the
2755    /// version the edit produced. Lines and columns count from zero and columns are UTF-16
2756    /// code units, which is what a language server counts in, so the event can be handed
2757    /// to one unchanged. Text still being composed by an input method never arrives here:
2758    /// only what the reader committed does.
2759    CodeChanged {
2760        version: u32,
2761        start_line: u32,
2762        start_column: u32,
2763        end_line: u32,
2764        end_column: u32,
2765        text: &'a str,
2766    },
2767    /// An edit the Host asked for and the Renderer did not apply, because the reader had
2768    /// changed the same place since the version the edit was written against.
2769    CodeEditRejected {
2770        request_id: u32,
2771        base_version: u32,
2772        current_version: u32,
2773        start_line: u32,
2774        start_column: u32,
2775        end_line: u32,
2776        end_column: u32,
2777    },
2778    /// A pointer came to rest over a place in a code editor, or left it. `decoration` is
2779    /// the id of the hover anchor under it, or zero where there is none.
2780    CodeHovered {
2781        decoration: u64,
2782        line: u32,
2783        column: u32,
2784        phase: HoverPhase,
2785    },
2786    /// The reader pressed the platform's save shortcut on a code editor.
2787    CodeSaveRequested {
2788        version: u32,
2789    },
2790    /// A decoration was pressed or accepted, named by the id the application gave it.
2791    DecorationActivated {
2792        decoration: u64,
2793    },
2794    /// Something happened to an animation the Renderer is playing, sent only for the kinds
2795    /// the animation asked for. It names the node the animation plays on and no handler.
2796    AnimationEvent {
2797        animation_id: u32,
2798        kind: AnimationEventKind,
2799        property: AnimatedProperty,
2800        slot: u8,
2801        /// The iteration the animation was in at that moment.
2802        iteration: u32,
2803        /// What CSS reports as `elapsedTime`: active time, without the delay, in ms.
2804        elapsed_ms: f32,
2805        /// The frame the moment happened in; for `Ready`, the start time it was given.
2806        time_nanos: u64,
2807    },
2808    /// Whether the platform asks for reduced motion, sent once after start and then only
2809    /// when the answer changes.
2810    ReducedMotionChanged(ReducedMotion),
2811    /// A pointer did something inside a node that subscribed to it. Coordinates are in dp
2812    /// from the node's top left corner, `buttons` is what is held now and `button` is the
2813    /// one that caused a `Down` or an `Up`. `timestamp_nanos` is on the frame time's clock.
2814    Pointer {
2815        kind: PointerKind,
2816        pointer_id: u32,
2817        pointer_type: PointerType,
2818        x: f32,
2819        y: f32,
2820        buttons: u32,
2821        button: u32,
2822        modifiers: u32,
2823        click_count: u32,
2824        delta_x: f32,
2825        delta_y: f32,
2826        delta_mode: WheelDeltaMode,
2827        timestamp_nanos: u64,
2828    },
2829    /// How large the Renderer is drawing: `k = os × 1.2^level`. Sent once at start and
2830    /// again whenever any of the three changes. A move to a screen of another density
2831    /// changes none of them and is not reported.
2832    ZoomChanged {
2833        k: f32,
2834        os: f32,
2835        level: i32,
2836    },
2837    /// The selection of a code editor moved, or an edit moved it. `version` is the document
2838    /// version it is read against, the same series `CodeChanged` counts, and an edit's
2839    /// change always arrives first. Columns are UTF-16 units and never fall between the
2840    /// two halves of a pair.
2841    SelectionChanged {
2842        version: u32,
2843        anchor_line: u32,
2844        anchor_column: u32,
2845        active_line: u32,
2846        active_column: u32,
2847    },
2848    /// The lines of a code editor that are on screen changed. Folded lines are skipped, so
2849    /// `first_line` and `last_line` are the first and last lines the reader can see.
2850    VisibleRangeChanged {
2851        version: u32,
2852        first_line: u32,
2853        last_line: u32,
2854    },
2855    /// A code editor gained or lost the keyboard focus.
2856    EditorFocusChanged {
2857        focused: bool,
2858    },
2859    /// The reader folded or unfolded the stretch that starts on `start_line`.
2860    FoldChanged {
2861        start_line: u32,
2862        collapsed: bool,
2863    },
2864    /// An input method started or ended a composition in a code editor. What is being
2865    /// composed stays in the Renderer: only the fact crosses.
2866    CompositionChanged {
2867        composing: bool,
2868    },
2869}
2870
2871pub const EVENT_SCHEMA: &[EventSchema] = &[
2872    EventSchema {
2873        name: "Clicked",
2874        tag: 1,
2875        payload: EventPayloadType::None,
2876    },
2877    EventSchema {
2878        name: "TextChanged",
2879        tag: 2,
2880        payload: EventPayloadType::Text,
2881    },
2882    EventSchema {
2883        name: "TextSubmitted",
2884        tag: 3,
2885        payload: EventPayloadType::Text,
2886    },
2887    EventSchema {
2888        name: "FocusLost",
2889        tag: 4,
2890        payload: EventPayloadType::None,
2891    },
2892    EventSchema {
2893        name: "ProtocolError",
2894        tag: 5,
2895        payload: EventPayloadType::ProtocolError,
2896    },
2897    EventSchema {
2898        name: "KeyDown",
2899        tag: 6,
2900        payload: EventPayloadType::KeyDown,
2901    },
2902    EventSchema {
2903        name: "RangeRequested",
2904        tag: 7,
2905        payload: EventPayloadType::Range,
2906    },
2907    EventSchema {
2908        name: "ValueChanged",
2909        // Tag 8 is the pointer event and 9 to 15 are reserved for the gesture events.
2910        tag: 16,
2911        payload: EventPayloadType::Double,
2912    },
2913    EventSchema {
2914        name: "WindowSizeChanged",
2915        tag: 17,
2916        payload: EventPayloadType::WindowSize,
2917    },
2918    // The three below address the Host itself rather than a node, so they carry no handler
2919    // and the Host answers them before it looks one up.
2920    EventSchema {
2921        name: "Resync",
2922        tag: 18,
2923        payload: EventPayloadType::None,
2924    },
2925    EventSchema {
2926        name: "LifecycleStart",
2927        tag: 19,
2928        payload: EventPayloadType::None,
2929    },
2930    EventSchema {
2931        name: "LifecycleStop",
2932        tag: 20,
2933        payload: EventPayloadType::None,
2934    },
2935    // Addressed to the Host rather than to a node, like the three above: it is a fact
2936    // about the window, not something that happened inside it.
2937    EventSchema {
2938        name: "DesignSystemResolved",
2939        tag: 21,
2940        payload: EventPayloadType::DesignSystem,
2941    },
2942    EventSchema {
2943        name: "FilesEntered",
2944        tag: 22,
2945        payload: EventPayloadType::None,
2946    },
2947    // One string for however many paths, which is the string convention unchanged. A
2948    // second way of carrying a list would be a second thing to keep in step.
2949    EventSchema {
2950        name: "FilesDropped",
2951        tag: 23,
2952        payload: EventPayloadType::Text,
2953    },
2954    // Both address the Host itself, like the resolved design system: a notification is not
2955    // in the tree, so there is no node and no handler to name.
2956    EventSchema {
2957        name: "NotificationActivated",
2958        tag: 24,
2959        payload: EventPayloadType::NotificationActivation,
2960    },
2961    EventSchema {
2962        name: "NotificationPermissionChanged",
2963        tag: 25,
2964        payload: EventPayloadType::NotificationPermission,
2965    },
2966    EventSchema {
2967        name: "CodeChanged",
2968        tag: 26,
2969        payload: EventPayloadType::CodeChange,
2970    },
2971    EventSchema {
2972        name: "CodeEditRejected",
2973        tag: 27,
2974        payload: EventPayloadType::CodeEditRejected,
2975    },
2976    EventSchema {
2977        name: "CodeHovered",
2978        tag: 28,
2979        payload: EventPayloadType::CodeHover,
2980    },
2981    EventSchema {
2982        name: "CodeSaveRequested",
2983        tag: 29,
2984        payload: EventPayloadType::CodeSave,
2985    },
2986    EventSchema {
2987        name: "DecorationActivated",
2988        tag: 30,
2989        payload: EventPayloadType::Decoration,
2990    },
2991    EventSchema {
2992        name: "AnimationEvent",
2993        tag: 31,
2994        payload: EventPayloadType::Animation,
2995    },
2996    // About the platform rather than a node, like the notification permission.
2997    EventSchema {
2998        name: "ReducedMotionChanged",
2999        tag: 32,
3000        payload: EventPayloadType::ReducedMotion,
3001    },
3002    // Tag 8 is the pointer event and 9 stays reserved. It is listed last because this table
3003    // is append only, not because its tag is the highest.
3004    EventSchema {
3005        name: "Pointer",
3006        tag: 8,
3007        payload: EventPayloadType::Pointer,
3008    },
3009    // Tags 26 to 32 are taken by events other requirements have numbered.
3010    EventSchema {
3011        name: "ZoomChanged",
3012        tag: 33,
3013        payload: EventPayloadType::Zoom,
3014    },
3015    EventSchema {
3016        name: "SelectionChanged",
3017        tag: 34,
3018        payload: EventPayloadType::CodeSelection,
3019    },
3020    EventSchema {
3021        name: "VisibleRangeChanged",
3022        tag: 35,
3023        payload: EventPayloadType::CodeVisibleRange,
3024    },
3025    EventSchema {
3026        name: "EditorFocusChanged",
3027        tag: 36,
3028        payload: EventPayloadType::CodeFocus,
3029    },
3030    EventSchema {
3031        name: "FoldChanged",
3032        tag: 37,
3033        payload: EventPayloadType::CodeFold,
3034    },
3035    EventSchema {
3036        name: "CompositionChanged",
3037        tag: 38,
3038        payload: EventPayloadType::CodeComposition,
3039    },
3040];
3041
3042/// One argument of a boundary operation.
3043///
3044/// The set is deliberately tiny, because only primitives, pointers and lengths cross the
3045/// boundary. Every platform binding can then be generated from the same list.
3046#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3047pub enum BoundaryParam {
3048    /// A readable byte range. It arrives as a direct byte buffer and a length on Android,
3049    /// as a pointer and a length on desktop.
3050    Bytes { name: &'static str },
3051    /// A 64-bit platform frame timestamp.
3052    Nanos { name: &'static str },
3053}
3054
3055impl BoundaryParam {
3056    pub const fn name(self) -> &'static str {
3057        match self {
3058            Self::Bytes { name } | Self::Nanos { name } => name,
3059        }
3060    }
3061}
3062
3063/// One logical boundary operation, stated without saying which side calls it.
3064///
3065/// `symbol` is the Host's C export. Every platform binding, the GraalVM function
3066/// declarations, the Android shims and the browser forwarders, is a rendering of this
3067/// table, which is why none of them is written by hand.
3068#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3069pub struct BoundaryOp {
3070    /// Direction-neutral name, in PascalCase.
3071    pub name: &'static str,
3072    pub symbol: &'static str,
3073    pub params: &'static [BoundaryParam],
3074    /// Whether the operation answers with a mutation batch.
3075    pub returns_batch: bool,
3076    /// Whether the call may skip the runtime's thread state transition on Android. That
3077    /// is only sound for a call that cannot run long, because the thread stays runnable
3078    /// and the garbage collector cannot suspend it while the call is in flight. A call
3079    /// that runs the VirtualDom can run long.
3080    pub fast: bool,
3081}
3082
3083pub const BOUNDARY_SCHEMA: &[BoundaryOp] = &[
3084    BoundaryOp {
3085        name: "Init",
3086        symbol: "compose_rust_host_init",
3087        params: &[BoundaryParam::Bytes { name: "handshake" }],
3088        returns_batch: true,
3089        fast: false,
3090    },
3091    BoundaryOp {
3092        name: "DispatchEvent",
3093        symbol: "compose_rust_host_dispatch_event",
3094        params: &[BoundaryParam::Bytes { name: "event" }],
3095        returns_batch: true,
3096        fast: false,
3097    },
3098    BoundaryOp {
3099        name: "RenderFrame",
3100        symbol: "compose_rust_host_render_frame",
3101        params: &[BoundaryParam::Nanos {
3102            name: "frameTimeNanos",
3103        }],
3104        returns_batch: true,
3105        fast: false,
3106    },
3107    BoundaryOp {
3108        name: "ReleaseBatch",
3109        symbol: "compose_rust_host_release_batch",
3110        params: &[],
3111        returns_batch: false,
3112        fast: true,
3113    },
3114    BoundaryOp {
3115        name: "Shutdown",
3116        symbol: "compose_rust_host_shutdown",
3117        params: &[],
3118        returns_batch: false,
3119        fast: false,
3120    },
3121];
3122
3123/// The JVM class the Android shims bind to. The Kotlin file is generated under the same
3124/// name, so the two sides cannot drift.
3125pub const ANDROID_BRIDGE_CLASS: &str = "dev/darkpyonix/composerust/ui/platform/HostBridge";
3126
3127// ---------------------------------------------------------------------------------------
3128// The browser's shared linear memory.
3129//
3130// One `WebAssembly.Memory` holds both modules' data. The Kotlin module defines and exports
3131// it, because a Kotlin/Wasm module has no way to import one, and the Rust module is linked
3132// with `--import-memory` so that the arena the Host writes is the arena the Renderer reads.
3133// Nothing below crosses the boundary as data: these are the addresses and offsets both
3134// generated halves are written against.
3135// ---------------------------------------------------------------------------------------
3136
3137/// The first address that belongs to the Rust module.
3138///
3139/// Below it is the Kotlin module's `kotlin.wasm.unsafe` allocator; at and above it are the
3140/// Rust module's data, stack and heap, which the linker is told with `--global-base`. Two
3141/// allocators handing out addresses in one memory is the hazard: an overlap does not crash,
3142/// it draws the wrong screen, so the Renderer checks which side of this line its own
3143/// addresses fall on before it makes the first boundary call.
3144pub const WEB_RUST_REGION_BASE: u32 = 4 * 1024 * 1024;
3145
3146/// One field of the `MutationBatch` record the Host writes into the shared memory.
3147#[derive(Clone, Copy, Debug, Eq, PartialEq)]
3148pub struct WebBatchField {
3149    /// PascalCase, so the generated Kotlin constant reads as a name rather than an offset.
3150    pub name: &'static str,
3151    pub offset: u32,
3152    pub width: u32,
3153    pub description: &'static str,
3154}
3155
3156/// `MutationBatch` as a wasm32 target lays it out.
3157///
3158/// The offsets are written down rather than read from `offset_of!` because codegen runs on
3159/// the machine that generates, not the machine that runs: a 64-bit host makes the same
3160/// `#[repr(C)]` struct 24 bytes with an 8 byte pointer. The generated wasm glue asserts
3161/// this table against the real layout, so a compiler that disagrees stops that build
3162/// instead of producing a Renderer that reads the wrong four bytes.
3163pub const WEB_BATCH_FIELDS: &[WebBatchField] = &[
3164    WebBatchField {
3165        name: "Address",
3166        offset: 0,
3167        width: 4,
3168        description: "where the batch starts, as an address in the shared memory",
3169    },
3170    WebBatchField {
3171        name: "Length",
3172        offset: 4,
3173        width: 4,
3174        description: "the batch's length in bytes",
3175    },
3176    WebBatchField {
3177        name: "Result",
3178        offset: 8,
3179        width: 8,
3180        description: "the handler's synchronous result",
3181    },
3182];
3183
3184/// How long that record is, including the padding `#[repr(C)]` adds before the result.
3185pub const WEB_BATCH_BYTES: u32 = 16;
3186
3187/// How much room the Host lends the Renderer to encode one event into.
3188///
3189/// The same figure the Android connection allocates for the same job. An event that would
3190/// not fit is refused by the encoder rather than truncated.
3191pub const WEB_EVENT_BUFFER_BYTES: u32 = 4096;
3192
3193/// Where the event buffer starts inside the block `compose_rust_host_web_start` reports.
3194///
3195/// The out record comes first, at offset zero, and the event buffer follows it.
3196pub const WEB_EVENT_BUFFER_OFFSET: u32 = WEB_BATCH_BYTES;
3197
3198/// The extra Host export a browser needs, because a page has no library loader.
3199///
3200/// It does what `JNI_OnLoad` does on Android, registering the root component and installing
3201/// the renderer API, and it answers with the address of the block above.
3202pub const WEB_START_SYMBOL: &str = "compose_rust_host_web_start";
3203
3204/// The wasm import module the Host's frame request comes from.
3205///
3206/// This direction has no JavaScript on it: the page hands Kotlin's exported function object
3207/// straight to the Rust instantiation, which the engine binds as a wasm-to-wasm call. It can,
3208/// because Rust is instantiated second and Kotlin's exports already exist by then.
3209pub const WEB_RENDERER_IMPORT_MODULE: &str = "compose_rust_renderer";
3210
3211/// The global the generated forwarders read the Host's exports off.
3212///
3213/// A `@JsFun` body is inlined into the import object module Kotlin generates, so the only
3214/// name it can reach the loader through is a property of the global object.
3215pub const WEB_HOST_GLOBAL: &str = "__composeRustHost";
3216
3217/// The global the loader module publishes the compiled Host module under.
3218///
3219/// Compiling is all the page does, and the reason the page has to do it at all is that
3220/// compiling is asynchronous while the boundary is not: the fetch has to have finished
3221/// before the Renderer's `main` runs. Everything after it, the memory, the imports and the
3222/// entry point, is in the generated Kotlin, because only the Renderer's own module can name
3223/// them.
3224pub const WEB_MODULE_GLOBAL: &str = "__composeRustHostModule";