compose_rust/lib.rs
1#![deny(unsafe_op_in_unsafe_fn)]
2
3pub mod asset;
4pub mod boundary;
5/// Generated JNI shims. Compiled only for Android, where the Host is a cdylib that the
6/// Kotlin Activity loads.
7#[cfg(target_os = "android")]
8#[path = "boundary_jni.gen.rs"]
9mod boundary_jni;
10/// Generated wasm shims. Compiled only for the browser, where the page owns the loop and
11/// the Renderer's module owns the one linear memory both halves read.
12#[cfg(target_family = "wasm")]
13#[path = "boundary_wasm.gen.rs"]
14mod boundary_wasm;
15pub mod brush;
16#[cfg(target_family = "wasm")]
17#[doc(hidden)]
18pub use boundary_wasm::web_start as __web_start;
19#[doc(hidden)]
20pub mod codegen;
21pub mod design;
22pub mod drawing;
23mod extensions;
24pub mod message;
25pub mod protocol;
26pub mod renderer;
27pub mod schema;
28pub mod spans;
29pub mod tokens;
30mod widgets;
31pub mod window;
32
33pub use asset::asset;
34pub use boundary::{
35 Host, LaunchBuilder, MutationBatch, RendererApi, demo_theme, demo_theme_for,
36 install_renderer_api, launch, request_frame_from_worker,
37};
38pub use dioxus_core::{Element, VirtualDom};
39// `Props` goes out with `component` because `#[component]` expands into a
40// `#[derive(Props)]`. Without it an application that writes a component of its own fails
41// to compile on a macro it never typed, and the fix is to add `dioxus-core-macro` as a
42// second dependency, which defeats the promise that one dependency is enough.
43pub use brush::{Brush, Stop, brush};
44pub use design::{design_system, use_design_system};
45pub use dioxus_core_macro::{Props, component, rsx};
46pub use drawing::{DrawCommand, DrawList, DrawListBuilder};
47pub use elements::*;
48pub use extensions::LinearProgressIndicator;
49pub use message::{Message, show_message};
50pub use schema::{
51 Alignment, Arrangement, AssetKind, ButtonVariant, Chrome, Color, ColorRole, ColorScheme,
52 DesignSystem, EventPayload, IconRole, Key, LoopMode, MaterialRole, MessageDuration, Modifier,
53 MotionRole, Paint, PropertyKind, SCHEMA_HASH, Selection, ShapeRole, SpaceRole, TextAlign,
54 TextOverflow, Theme, TileMode, TypeRole, WidgetKind, WindowHeightClass, WindowSizeClass,
55};
56pub use widgets::{
57 Badge, Button, Canvas, Card, Checkbox, Column, ComposeBox as Box, DatePicker, Dialog, Divider,
58 Dropdown, FileDrop, FileDropTarget, Icon, Image, KeyEvent, LazyColumn, LazyGrid, LazyRow, Menu,
59 Navigation, NavigationItem, ProgressIndicator, RadioButton, RangeRequest, Row, Scaffold,
60 ScrollColumn, SelectionContainer, Separator, Sheet, Slider, Spacer, Surface, Switch, Tabs,
61 Text, TextField, TimePicker, Tooltip, TopAppBar,
62};
63pub use window::{NodeSize, WindowSize, node_size, use_node_size, use_window_size, window_size};
64
65/// Declares the Android entry point for an application's cdylib.
66///
67/// Android has no `main`: the Kotlin Activity owns the process and the frame loop, so the
68/// root component is registered from `JNI_OnLoad`, which the generated shims reach through
69/// the symbol this macro defines.
70///
71/// ```ignore
72/// compose_rust::android_main!(app);
73/// ```
74#[macro_export]
75macro_rules! android_main {
76 ($app:path) => {
77 $crate::android_main!($crate::LaunchBuilder::new(), $app);
78 };
79 ($builder:expr, $app:path) => {
80 #[unsafe(no_mangle)]
81 pub extern "C" fn dioxus_compose_android_main() {
82 $builder.with_mode($crate::LoopMode::Platform).launch($app);
83 }
84 };
85}
86
87/// Declares the entry point an iOS application starts at.
88///
89/// iOS is the one platform where the application is the library: the renderer is a
90/// Kotlin/Native archive and the two are linked into a single executable, so there is no
91/// Activity to load anything and no page to fetch anything. What there is instead is a
92/// `main`, and a `main` in an application bundle has to be C, so this exports the launch
93/// under a name that C can call.
94///
95/// ```ignore
96/// compose_rust::ios_main!(launch);
97/// ```
98#[macro_export]
99macro_rules! ios_main {
100 ($launch:path) => {
101 #[unsafe(no_mangle)]
102 pub extern "C" fn dioxus_compose_ios_main() -> i32 {
103 $launch();
104 0
105 }
106 };
107}
108
109/// Declares the browser entry point for an application's wasm module.
110///
111/// A page has no library loader and no `main` of its own to run: the Renderer's module
112/// owns the loop and calls this once both wasm modules exist, and it answers with the
113/// address of the block the Host lends the Renderer.
114///
115/// The export is here rather than in this crate because a wasm module cannot be linked
116/// with an undefined symbol the way an ELF shared library can. Android's cdylib imports
117/// `dioxus_compose_android_main` from the application and the dynamic linker resolves it
118/// at load time; a browser refuses to instantiate a module whose imports are not all
119/// supplied, so the entry point is defined where the root component is.
120///
121/// ```ignore
122/// compose_rust::web_main!(app);
123/// ```
124#[macro_export]
125macro_rules! web_main {
126 ($app:path) => {
127 $crate::web_main!($crate::LaunchBuilder::new(), $app);
128 };
129 ($builder:expr, $app:path) => {
130 #[cfg(target_family = "wasm")]
131 #[unsafe(no_mangle)]
132 pub extern "C" fn dioxus_compose_host_web_start() -> u32 {
133 $crate::__web_start($builder, $app)
134 }
135
136 /// Off the web there is no page to call this and no shared memory to report an
137 /// address in, but it stays defined so that a build for the machine you are
138 /// working on still compiles the component rather than leaving it unreferenced.
139 #[cfg(not(target_family = "wasm"))]
140 #[unsafe(no_mangle)]
141 pub extern "C" fn dioxus_compose_host_web_start() -> u32 {
142 let _: fn() -> $crate::Element = $app;
143 let _ = $builder;
144 0
145 }
146 };
147}
148
149pub mod prelude {
150 pub use crate as dioxus_elements;
151 // dioxus-core 0.7's rsx! expansion uses unqualified `Box<T>`.
152 // Exporting the Compose `Box` through this glob prelude shadows it. Use
153 // `compose_rust::Box { ... }` in RSX until upstream qualifies std::boxed::Box.
154 pub use crate::{
155 Alignment, Arrangement, AssetKind, Badge, Brush, Button, ButtonVariant, Canvas, Card,
156 Checkbox, Color, ColorRole, ColorScheme, Column, DatePicker, DesignSystem, Dialog, Divider,
157 DrawCommand, DrawList, Dropdown, Element, FileDrop, FileDropTarget, Icon, IconRole, Image,
158 Key, KeyEvent, LaunchBuilder, LazyColumn, LazyGrid, LazyRow, LinearProgressIndicator,
159 LoopMode, MaterialRole, Menu, Message, MessageDuration, Modifier, MotionRole, Navigation,
160 NavigationItem, Paint, ProgressIndicator, Props, RadioButton, RangeRequest, Row, Scaffold,
161 ScrollColumn, SelectionContainer, Separator, ShapeRole, Sheet, Slider, SpaceRole, Spacer,
162 Stop, Surface, Switch, Tabs, Text, TextAlign, TextField, TextOverflow, Theme, TileMode,
163 TimePicker, Tooltip, TopAppBar, TypeRole, WindowHeightClass, WindowSize, WindowSizeClass,
164 asset, brush, component, launch, rsx, show_message, use_design_system, use_node_size,
165 use_window_size,
166 };
167 // Under its own name, and the one thing in this list that could shadow something a
168 // reader already has: an application that draws its own `Window` component would find
169 // this one instead. It is here because the alternative is a fully qualified path in
170 // every `main`, and because `Chrome` beside it is meaningless on its own.
171 pub use crate::schema::{Chrome, Window};
172 // The crates `rsx!` expands into references to, under the names it expands into. A
173 // consumer who added only `dioxus-compose` does not have `dioxus_core` or
174 // `dioxus_signals` in their dependency graph by name, so without these the macro
175 // fails to resolve them and the crate cannot be used at all with one dependency,
176 // which is the whole promise.
177 pub use dioxus_core;
178 pub use dioxus_signals;
179
180 // `use_hook` is how a component starts something once and keeps it: a worker thread,
181 // a connection, a subscription. Domain work runs on worker threads, so a consumer who
182 // added only this crate needs it by name.
183 pub use dioxus_core::{Callback, Event, EventHandler, Properties, VirtualDom, use_hook};
184 pub use dioxus_hooks::*;
185 pub use dioxus_signals::*;
186}
187
188pub mod elements {
189 #![allow(non_upper_case_globals)]
190
191 pub type AttributeDescription = (&'static str, Option<&'static str>, bool);
192
193 pub use crate::extensions::elements::*;
194
195 /// The Modifier attributes every widget accepts.
196 ///
197 /// In Compose a Modifier applies to any composable, and the same has to be true here
198 /// or an application cannot give a widget padding, a background, a corner or a border.
199 /// Without them a sample can only place bare text and buttons against the window edge,
200 /// which is what these samples looked like before this existed.
201 ///
202 /// They are declared once, on every widget, rather than listed per element, because
203 /// the set that applies to a Card and to a Text is the same set.
204 macro_rules! modifier_attributes {
205 () => {
206 pub const weight: AttributeDescription = ("weight", None, false);
207 pub const fill_max_width: AttributeDescription = ("fill_max_width", None, false);
208 pub const fill_max_height: AttributeDescription = ("fill_max_height", None, false);
209 pub const width: AttributeDescription = ("width", None, false);
210 pub const height: AttributeDescription = ("height", None, false);
211 pub const padding: AttributeDescription = ("padding", None, false);
212 pub const padding_role: AttributeDescription = ("padding_role", None, false);
213 pub const background: AttributeDescription = ("background", None, false);
214 pub const shape_role: AttributeDescription = ("shape_role", None, false);
215 pub const corner_radius: AttributeDescription = ("corner_radius", None, false);
216 pub const border_width: AttributeDescription = ("border_width", None, false);
217 pub const border_color: AttributeDescription = ("border_color", None, false);
218 pub const elevation: AttributeDescription = ("elevation", None, false);
219 pub const onclickable: AttributeDescription = ("onclickable", None, false);
220 pub const observe_size: AttributeDescription = ("observe_size", None, false);
221 // How important this node's changes are. Every widget takes it, because
222 // anything that appears, moves or resizes has changes to run.
223 pub const motion: AttributeDescription = ("motion", None, false);
224 // What this node's surface is made of, where it has one.
225 pub const material: AttributeDescription = ("material", None, false);
226 // Willingness to have files dropped, said by having somewhere to report them.
227 // A node without a handler is never told files are over it, which is what
228 // keeps a screen from lighting up every container it has.
229 pub const onfilesentered: AttributeDescription = ("onfilesentered", None, false);
230 pub const onfilesdropped: AttributeDescription = ("onfilesdropped", None, false);
231 };
232 }
233
234 macro_rules! element {
235 // A widget with no attributes of its own still takes Modifiers, so this arm is the
236 // same as the one below with an empty list rather than a smaller module.
237 ($module:ident, $tag:literal, []) => {
238 pub mod $module {
239 use super::AttributeDescription;
240
241 pub const TAG_NAME: &str = $tag;
242 pub const NAME_SPACE: Option<&str> = None;
243 modifier_attributes!();
244 }
245 };
246 ($module:ident, $tag:literal, [$($attribute:ident),* $(,)?]) => {
247 pub mod $module {
248 use super::AttributeDescription;
249
250 pub const TAG_NAME: &str = $tag;
251 pub const NAME_SPACE: Option<&str> = None;
252 $(pub const $attribute: AttributeDescription = (stringify!($attribute), None, false);)*
253 modifier_attributes!();
254 }
255 };
256 }
257
258 // Layout containers carry arrangement, spacing and cross-axis alignment.
259 element!(
260 column,
261 "Column",
262 [arrangement, spacing, space_role, alignment]
263 );
264 element!(row, "Row", [arrangement, spacing, space_role, alignment]);
265 element!(composebox, "Box", [item_key, alignment]);
266 // The type role plus one attribute per override axis, so changing one axis is one
267 // SetProp and the rest of the text's styling is not resent.
268 element!(
269 text,
270 "Text",
271 [
272 text,
273 spans,
274 type_role,
275 font_size,
276 font_weight,
277 line_height,
278 letter_spacing,
279 color,
280 text_align,
281 max_lines,
282 overflow
283 ]
284 );
285 element!(
286 textfield,
287 "TextField",
288 [placeholder, enabled, multiline, type_role]
289 );
290 // `icon` is the meaning of the glyph on it and never a picture. A button is the
291 // second place a role icon reaches the tree, because a toolbar is a row of icon
292 // buttons and nothing else in the vocabulary can place one.
293 element!(button, "Button", [text, icon, enabled, variant, color]);
294 // Spacer has no attributes of its own: its size comes from the Modifier attributes
295 // every widget carries, which is also how a Compose Spacer is sized.
296 element!(spacer, "Spacer", []);
297 element!(lazycolumn, "LazyColumn", [item_count]);
298 // Widget tags 10 and 11. An asset id is the whole of what they carry: the bytes were
299 // copied into the Renderer's cache once, at registration.
300 element!(image, "Image", [asset]);
301 // An Icon takes a tint as well, through the same Paint attribute Text uses.
302 element!(icon, "Icon", [asset, color]);
303 // Widget tags 12 to 17. A toggle is controlled: `checked` is the whole of what it
304 // draws, so the box on screen and the value the Host holds can never disagree.
305 element!(checkbox, "Checkbox", [checked, enabled]);
306 element!(radiobutton, "RadioButton", [checked, enabled]);
307 element!(switch, "Switch", [checked, enabled]);
308 // The position a drag is passing through is the Renderer's, like scroll and focus, so
309 // following a finger costs no boundary call. `value` seeds it and carries a change
310 // that came from somewhere else.
311 element!(slider, "Slider", [value, min, max, steps, enabled, color]);
312 // `determinate` says whether `value` means anything and `circular` picks the form. How
313 // fast an indeterminate indicator travels is motion, and motion is the design system's.
314 element!(
315 progressindicator,
316 "ProgressIndicator",
317 [value, determinate, circular]
318 );
319 // The thickness, the colour and the inset come from the design system. The axis is the
320 // only decision left to make.
321 element!(divider, "Divider", [vertical]);
322 // Widget tags 18 to 25. Each one emits roles and children only: how a card, a bar or a
323 // popup is drawn belongs to the design system, not to the Host that declared it.
324 element!(card, "Card", []);
325 element!(surface, "Surface", []);
326 // `open` seeds the Renderer's own open state rather than driving it frame by frame: the
327 // Renderer owns the enter and exit so neither crosses the boundary.
328 element!(dialog, "Dialog", [open]);
329 element!(menu, "Menu", [open]);
330 // The selection is the Renderer's too. `selected_index` seeds it and moves it when the
331 // change came from outside the Renderer.
332 element!(tabs, "Tabs", [selected_index, color]);
333 element!(topappbar, "TopAppBar", [text]);
334 element!(lazyrow, "LazyRow", [item_count]);
335 element!(tooltip, "Tooltip", [text]);
336 // The command list is a byte blob, so it is one attribute and one SetProp. An
337 // unchanged list compares equal and produces no mutation at all.
338 element!(canvas, "Canvas", [commands]);
339 // Widget tags 27 to 29. A picker carries a value, a range and a change event, and
340 // nothing that says how the user should pick: a calendar grid, a dial, a wheel or a
341 // flyout is the design system's decision.
342 element!(datepicker, "DatePicker", [value, min, max, enabled]);
343 element!(timepicker, "TimePicker", [value, min, max, enabled]);
344 element!(dropdown, "Dropdown", [selected_index, enabled]);
345 // Whole content plus a vertical scroll. The position stays in the Renderer.
346 element!(scrollcolumn, "ScrollColumn", []);
347 // Widget tags 30 to 32. `Navigation` carries the selection and nothing about whether
348 // it is a bar, a rail or a drawer: the Renderer has measured the window and chooses.
349 // The destinations are the `NavigationItem` children and the rest is the screen.
350 element!(navigation, "Navigation", [selected_index]);
351 // A destination's label and icon are properties, not a child tree. A child tree would
352 // fix the arrangement, and the three presentations exist because it differs: a bar
353 // stacks the label under the icon, a drawer sets it beside.
354 element!(
355 navigationitem,
356 "NavigationItem",
357 [text, icon, color, enabled, section]
358 );
359 // Like a Dialog on the wire: `open` seeds the Renderer's own state and `on_dismiss`
360 // says once that the user asked to close it. Which edge it enters from is the
361 // Renderer's, because it is the side that knows how wide the window is.
362 element!(sheet, "Sheet", [open]);
363 // The screen's frame. It carries nothing of its own: what it is made of arrives as
364 // slots, and what each slot becomes is decided where the window's width is known.
365 element!(scaffold, "Scaffold", []);
366 element!(scaffoldslot, "ScaffoldSlot", [slot]);
367 // A grid whose window is the list's, unchanged. The two ways of saying how wide a column is
368 // are separate attributes because they are separate questions: a count the screen
369 // insists on, or a width below which the Renderer drops one.
370 element!(
371 lazygrid,
372 "LazyGrid",
373 [item_count, columns, min_column_width]
374 );
375 element!(filedroptarget, "FileDropTarget", [alignment]);
376 // A count, a word, or with neither a dot. The colour is a role. Where it sits on its
377 // child and how a large count is written are the design system's, so there is nothing
378 // here that could ask for a corner or a ceiling.
379 element!(badge, "Badge", [count, text, color]);
380 // Nothing of its own. Being this widget is the whole of what it says: the text inside
381 // may be selected and copied, and the selection never crosses the boundary.
382 element!(selectioncontainer, "SelectionContainer", []);
383
384 #[doc(hidden)]
385 pub mod completions {
386 #[allow(non_camel_case_types)]
387 pub enum CompleteWithBraces {
388 column {},
389 scaffold {},
390 lazygrid {},
391 scaffoldslot {},
392 row {},
393 composebox {},
394 text {},
395 textfield {},
396 button {},
397 spacer {},
398 lazycolumn {},
399 scrollcolumn {},
400 checkbox {},
401 radiobutton {},
402 switch {},
403 slider {},
404 progressindicator {},
405 divider {},
406 card {},
407 surface {},
408 dialog {},
409 menu {},
410 tabs {},
411 topappbar {},
412 lazyrow {},
413 tooltip {},
414 canvas {},
415 image {},
416 icon {},
417 datepicker {},
418 timepicker {},
419 dropdown {},
420 navigation {},
421 navigationitem {},
422 sheet {},
423 badge {},
424 selectioncontainer {},
425 }
426 }
427}
428
429pub mod events {
430 use dioxus_core::{Attribute, Event, ListenerCallback, SpawnIfAsync, SuperInto};
431
432 macro_rules! event {
433 ($name:ident, $data:ty) => {
434 pub fn $name<Marker>(
435 handler: impl SuperInto<ListenerCallback<$data>, Marker>,
436 ) -> Attribute {
437 Attribute::new(stringify!($name), handler.super_into(), None, false)
438 }
439
440 pub mod $name {
441 use super::*;
442
443 pub fn call_with_explicit_closure<Marker, Return>(
444 handler: impl FnMut(Event<$data>) -> Return + 'static,
445 ) -> Attribute
446 where
447 Return: SpawnIfAsync<Marker> + 'static,
448 {
449 Attribute::new(
450 stringify!($name),
451 ListenerCallback::new(handler),
452 None,
453 false,
454 )
455 }
456 }
457 };
458 }
459
460 event!(onclick, ());
461 event!(onvaluechange, String);
462 event!(onsubmit, String);
463 event!(onfocuslost, ());
464 event!(onkeydown, crate::KeyEvent);
465 event!(onrangerequest, crate::RangeRequest);
466 // A dismissal carries no value, so it reuses the empty event payload a click uses.
467 event!(ondismiss, ());
468 // Files over a node, and files let go on it. The first carries nothing: the platforms
469 // disagree about what is knowable before a drop, and a node that only lights up does
470 // not need to know.
471 event!(onfilesentered, ());
472 event!(onfilesdropped, crate::FileDrop);
473 // A control reports the value the user landed on, as one f64. A picker reads it as
474 // the epoch count it speaks, a slider as a position, a toggle as off or on. It shares
475 // the wire property with the text field's value change, because both are "this
476 // control's value is now this".
477 event!(onchange, f64);
478}