Skip to main content

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}