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