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