dioxus-core 0.8.0-alpha.2

Build fullstack web, desktop, and mobile apps with a single codebase.
Documentation
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
use crate::{AttributeValue, RenderTargetId, Runtime, arena::ElementId};
use std::{collections::BTreeMap, rc::Rc};

/// Something that can handle the mutations that are generated by the diffing process and apply them to the Real DOM
///
/// This object provides a stack-only stream of changes for a renderer to use
/// to patch the Real DOM with the state of the VirtualDom.
///
/// These changes are specific to one subtree, so to patch multiple subtrees, you'd need to handle each set separately.
///
/// Mutations are the only link between the RealDOM and the VirtualDOM.
pub trait WriteMutations {
    /// Whether this writer applies and retains renderer-local template
    /// prototype nodes across mutation flushes.
    ///
    /// Writers that ignore mutations, or otherwise do not apply emitted
    /// template prototype mutations to a retained renderer-local node map,
    /// must return `false` so VirtualDom-side template cache entries are not
    /// created for renderer nodes that do not actually exist.
    #[doc(hidden)]
    fn can_cache_template_roots(&mut self) -> bool {
        true
    }

    /// Push the node registered to `id` onto the stack.
    fn push_id(&mut self, id: ElementId);

    /// Replace the current top node with its `index`th child.
    fn child(&mut self, index: usize);

    /// Pop and discard the current top node.
    fn pop(&mut self);

    /// Create an element and push it onto the stack.
    fn create_element(&mut self, tag: &str, ns: Option<&str>);

    /// Create a text node and push it onto the stack.
    fn create_text(&mut self, value: &str);

    /// Replace the current top node with a deep clone of itself.
    fn clone(&mut self);

    /// Append the top `m` nodes to the parent below them. Leaves the parent
    /// on the stack.
    fn append_children(&mut self, m: usize);

    /// Replace the target below the top `m` nodes with those nodes. Consumes
    /// the target and replacement nodes.
    fn replace_with(&mut self, m: usize);

    /// Insert the top `m` nodes after the anchor below them. Leaves the anchor
    /// on the stack.
    fn insert_after(&mut self, m: usize);

    /// Insert the top `m` nodes before the anchor below them. Leaves the anchor
    /// on the stack.
    fn insert_before(&mut self, m: usize);

    /// Set the value of a node's attribute.
    ///
    /// Name: The name of the attribute to set.
    /// NS: The (optional) namespace of the attribute. For instance, "style" is in the "style" namespace.
    /// Value: The value of the attribute.
    fn set_attribute(&mut self, name: &str, ns: Option<&str>, value: &AttributeValue);

    /// Set the text content of a node.
    ///
    /// Value: The textcontent of the node
    fn set_text(&mut self, value: &str);

    /// Create a new Event Listener.
    ///
    /// Name: The name of the event to listen for.
    fn add_event_listener(&mut self, name: &str);

    /// Remove an existing Event Listener.
    ///
    /// Name: The name of the event to remove.
    fn remove_event_listener(&mut self, name: &str);

    /// Remove the current top node from the DOM and consume it.
    fn remove(&mut self);

    /// Register the current top node under `id`, leaving it on the stack.
    ///
    /// Replaces the old `pop_id` (which registered *and* popped): callers that want the node gone
    /// follow with `pop`, and freshly-created nodes that will be appended just stay on the stack.
    fn set_id(&mut self, id: ElementId);
}

macro_rules! write_mutation_methods {
    ($macro:ident, $($prefix:tt)*) => {
        $macro! {
            $($prefix)*
            push_id(id: ElementId);
            child(index: usize);
            pop();
            create_element(tag: &str, ns: Option<&str>);
            create_text(value: &str);
            clone();
            append_children(m: usize);
            replace_with(m: usize);
            insert_after(m: usize);
            insert_before(m: usize);
            set_attribute(name: &str, ns: Option<&str>, value: &AttributeValue);
            set_text(value: &str);
            add_event_listener(name: &str);
            remove_event_listener(name: &str);
            remove();
            set_id(id: ElementId);
        }
    };
}

macro_rules! forward_mut_write_mutations {
    ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
        $(
            fn $method(&mut self, $($arg: $arg_ty),*) {
                WriteMutations::$method(&mut **self, $($arg),*);
            }
        )*
    };
}

/// Forward through a mutable reference so writer-generic code can be
/// instantiated at `&mut dyn WriteMutations`.
impl<W: WriteMutations + ?Sized> WriteMutations for &mut W {
    fn can_cache_template_roots(&mut self) -> bool {
        WriteMutations::can_cache_template_roots(&mut **self)
    }

    write_mutation_methods!(forward_mut_write_mutations,);
}

/// Push `id`, run `with_node`, then pop the node.
///
/// Invariant: `with_node` must leave the pushed node on top of the renderer
/// stack. No-op writers still receive the stack operations; their implementation
/// is responsible for ignoring them while the diff keeps a single control flow.
pub(crate) fn with_id<R>(
    to: &mut dyn WriteMutations,
    id: ElementId,
    with_node: impl FnOnce(&mut dyn WriteMutations) -> R,
) -> R {
    to.push_id(id);
    let result = with_node(to);
    to.pop();
    result
}

/// Push `id` and let `consume_node` consume it.
///
/// Invariant: `consume_node` must consume the pushed node with a terminal stack
/// operation such as `remove`, `replace_with`, or `pop`.
pub(crate) fn with_consumed_id<R>(
    to: &mut dyn WriteMutations,
    id: ElementId,
    consume_node: impl FnOnce(&mut dyn WriteMutations) -> R,
) -> R {
    to.push_id(id);
    consume_node(to)
}

pub(crate) fn remove_id(to: &mut dyn WriteMutations, id: ElementId) {
    with_consumed_id(to, id, |to| to.remove());
}

pub(crate) struct TargetedLazyScope<'a, P: FnMut(&mut dyn WriteMutations)> {
    to: &'a mut dyn WriteMutations,
    push: P,
    runtime: Rc<Runtime>,
    target: RenderTargetId,
    pushed: bool,
}

impl<'a, P: FnMut(&mut dyn WriteMutations)> TargetedLazyScope<'a, P> {
    pub(crate) fn new(to: &'a mut dyn WriteMutations, runtime: Rc<Runtime>, push: P) -> Self {
        let target = runtime.current_render_target_id();
        Self {
            to,
            push,
            runtime,
            target,
            pushed: false,
        }
    }

    #[inline]
    fn ensure_pushed(&mut self) {
        if !self.pushed && self.runtime.current_render_target_id() == self.target {
            (self.push)(self.to);
            self.pushed = true;
        }
    }
}

impl<P: FnMut(&mut dyn WriteMutations)> Drop for TargetedLazyScope<'_, P> {
    fn drop(&mut self) {
        if self.pushed {
            self.to.pop();
        }
    }
}

macro_rules! forward_lazy_scope_write_mutations {
    ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
        $(
            fn $method(&mut self, $($arg: $arg_ty),*) {
                self.ensure_pushed();
                WriteMutations::$method(self.to, $($arg),*);
            }
        )*
    };
}

impl<P: FnMut(&mut dyn WriteMutations)> WriteMutations for TargetedLazyScope<'_, P> {
    fn can_cache_template_roots(&mut self) -> bool {
        self.to.can_cache_template_roots()
    }

    write_mutation_methods!(forward_lazy_scope_write_mutations,);
}

pub(crate) fn append_children_to(
    to: &mut dyn WriteMutations,
    id: ElementId,
    runtime: Rc<Runtime>,
    create_children: impl FnOnce(&mut dyn WriteMutations) -> usize,
) -> usize {
    append_children_to_with_result(to, id, runtime, |to| {
        let count = create_children(to);
        (count, count)
    })
}

pub(crate) fn append_children_to_with_result<R>(
    to: &mut dyn WriteMutations,
    id: ElementId,
    runtime: Rc<Runtime>,
    create_children: impl FnOnce(&mut dyn WriteMutations) -> (usize, R),
) -> R {
    let mut to = TargetedLazyScope::new(to, runtime, move |to| to.push_id(id));
    let (count, result) = create_children(&mut to);
    if count > 0 {
        to.append_children(count);
    }
    result
}

/// A host's collection of render-target writers.
///
/// The diff routes each write to the writer for the render target it lands
/// in. Single-target hosts get this for free: every [`WriteMutations`] writer
/// is a `MultiWriter` serving [`RenderTargetId::ROOT`] and nothing else.
/// Hosts that fan out to several sinks (one webview per window, one collector
/// per portal target) implement [`Self::writer_for`] over their own writer
/// storage.
pub trait MultiWriter {
    /// The writer type serving each individual target.
    type Writer: WriteMutations;

    /// The writer for `id`, or `None` for a target with no attached host.
    fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut Self::Writer>;
}

impl<W: WriteMutations> MultiWriter for W {
    type Writer = W;

    fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut W> {
        if id == RenderTargetId::ROOT {
            Some(self)
        } else {
            None
        }
    }
}

impl<W: WriteMutations> MultiWriter for BTreeMap<RenderTargetId, W> {
    type Writer = W;

    fn writer_for(&mut self, id: RenderTargetId) -> Option<&mut W> {
        self.get_mut(&id)
    }
}

pub(crate) struct TargetRouter<'a, M: MultiWriter> {
    to: &'a mut M,
    runtime: Rc<Runtime>,
}

impl<'a, M: MultiWriter> TargetRouter<'a, M> {
    pub(crate) fn new(to: &'a mut M, runtime: Rc<Runtime>) -> Self {
        Self { to, runtime }
    }

    #[inline]
    fn current(&mut self) -> Option<&mut M::Writer> {
        self.to.writer_for(self.runtime.current_render_target_id())
    }

    #[inline]
    fn with_current(&mut self, f: impl FnOnce(&mut M::Writer)) {
        if let Some(w) = self.current() {
            f(w);
        }
    }
}

macro_rules! route_write_mutations {
    ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
        $(
            fn $method(&mut self, $($arg: $arg_ty),*) {
                self.with_current(|w| WriteMutations::$method(w, $($arg),*));
            }
        )*
    };
}

impl<M: MultiWriter> WriteMutations for TargetRouter<'_, M> {
    fn can_cache_template_roots(&mut self) -> bool {
        self.current()
            .is_some_and(WriteMutations::can_cache_template_roots)
    }

    write_mutation_methods!(route_write_mutations,);
}

/// A `Mutation` represents a single instruction for the renderer to use to modify the UI tree to match the state
/// of the Dioxus VirtualDom.
///
/// These edits can be serialized and sent over the network or through any interface
#[derive(Debug, PartialEq)]
pub enum Mutation {
    /// Push the node registered to an element id onto the stack.
    PushId {
        /// The renderer-local element id to read.
        id: ElementId,
    },
    /// Register the top stack node to an element id, leaving it on the stack.
    SetId {
        /// The renderer-local element id to write.
        id: ElementId,
    },
    /// Replace the top stack node with its child at `index`.
    Child {
        /// The child index to select.
        index: usize,
    },
    /// Pop and discard the top stack node.
    Pop,
    /// Create an element and push it onto the stack.
    CreateElement {
        /// The element tag name.
        tag: String,
        /// The optional element namespace.
        ns: Option<String>,
    },
    /// Create a text node and push it onto the stack.
    CreateText {
        /// The text node value.
        value: String,
    },
    /// Replace the top stack node with a deep clone of itself.
    Clone,
    /// Append the top `m` nodes to the parent below them.
    AppendChildren {
        /// The number of child nodes to append.
        m: usize,
    },
    /// Replace the target below the top `m` nodes with those nodes.
    ReplaceWith {
        /// The number of replacement nodes.
        m: usize,
    },
    /// Insert the top `m` nodes after the anchor below them.
    InsertAfter {
        /// The number of inserted nodes.
        m: usize,
    },
    /// Insert the top `m` nodes before the anchor below them.
    InsertBefore {
        /// The number of inserted nodes.
        m: usize,
    },

    /// Set the value of a node's attribute.
    SetAttribute {
        /// The name of the attribute to set.
        name: String,

        /// The (optional) namespace of the attribute.
        /// For instance, "style" is in the "style" namespace.
        ns: Option<String>,

        /// The value of the attribute.
        value: AttributeValue,
    },

    /// Set the textcontent of a node.
    SetText {
        /// The textcontent of the node
        value: String,
    },

    /// Create a new Event Listener.
    NewEventListener {
        /// The name of the event to listen for.
        name: String,
    },

    /// Remove an existing Event Listener.
    RemoveEventListener {
        /// The name of the event to remove.
        name: String,
    },
    /// Remove the top stack node from the renderer tree.
    Remove,
}

/// A static list of mutations that can be applied to the DOM. Note: this list does not contain any `Any` attribute values
#[derive(Debug, PartialEq, Default)]
pub struct Mutations {
    /// Any mutations required to patch the renderer to match the layout of the VirtualDom
    pub edits: Vec<Mutation>,
}

fn serializable_attribute_value(value: &AttributeValue) -> AttributeValue {
    match value {
        AttributeValue::Text(_)
        | AttributeValue::Bool(_)
        | AttributeValue::Float(_)
        | AttributeValue::Int(_)
        | AttributeValue::None => value.clone(),
        _ => panic!("Cannot serialize attribute value"),
    }
}

macro_rules! collect_field_mutations {
    ($($method:ident($field:ident: $field_ty:ty) => $variant:ident;)*) => {
        $(
            fn $method(&mut self, $field: $field_ty) {
                self.edits.push(Mutation::$variant { $field })
            }
        )*
    };
}

macro_rules! collect_string_mutations {
    ($($method:ident($field:ident) => $variant:ident;)*) => {
        $(
            fn $method(&mut self, $field: &str) {
                self.edits.push(Mutation::$variant {
                    $field: $field.into(),
                })
            }
        )*
    };
}

macro_rules! collect_unit_mutations {
    ($($method:ident => $variant:ident;)*) => {
        $(
            fn $method(&mut self) {
                self.edits.push(Mutation::$variant)
            }
        )*
    };
}

impl WriteMutations for Mutations {
    collect_field_mutations! {
        push_id(id: ElementId) => PushId;
        set_id(id: ElementId) => SetId;
        child(index: usize) => Child;
        append_children(m: usize) => AppendChildren;
        replace_with(m: usize) => ReplaceWith;
        insert_after(m: usize) => InsertAfter;
        insert_before(m: usize) => InsertBefore;
    }

    fn create_element(&mut self, tag: &str, ns: Option<&str>) {
        self.edits.push(Mutation::CreateElement {
            tag: tag.to_string(),
            ns: ns.map(str::to_string),
        })
    }

    collect_string_mutations! {
        create_text(value) => CreateText;
        set_text(value) => SetText;
        add_event_listener(name) => NewEventListener;
        remove_event_listener(name) => RemoveEventListener;
    }

    collect_unit_mutations! {
        pop => Pop;
        clone => Clone;
        remove => Remove;
    }

    fn set_attribute(&mut self, name: &str, ns: Option<&str>, value: &AttributeValue) {
        self.edits.push(Mutation::SetAttribute {
            name: name.to_string(),
            ns: ns.map(str::to_string),
            value: serializable_attribute_value(value),
        })
    }
}

/// A struct that ignores all mutations
pub struct NoOpMutations;

macro_rules! noop_write_mutations {
    ($($method:ident($($arg:ident: $arg_ty:ty),*);)*) => {
        $(
            fn $method(&mut self, $($arg: $arg_ty),*) {
                $(let _ = $arg;)*
            }
        )*
    };
}

impl WriteMutations for NoOpMutations {
    fn can_cache_template_roots(&mut self) -> bool {
        false
    }

    write_mutation_methods!(noop_write_mutations,);
}