Skip to main content

compose_rust/
renderer.rs

1use crate::drawing::DrawList;
2use crate::protocol::{BatchEncoder, Mutation, PropertyValue, ProtocolError};
3use crate::schema::{PropertyKind, WidgetKind};
4use dioxus_core::{
5    AttributeValue, ElementId, Template, TemplateAttribute, TemplateNode, WriteMutations,
6};
7use std::collections::HashMap;
8
9/// The inclusive range of design-property tags. Extension properties follow this range.
10const FIRST_OPTIONAL_PROPERTY: u16 = PropertyKind::TypeRole as u16;
11const LAST_OPTIONAL_PROPERTY: u16 = PropertyKind::Variant as u16;
12
13/// The bit that tracks whether a node has ever carried a list of text runs.
14///
15/// Outside the range above and kept separately, because widening that range swallowed
16/// values where zero means zero: a picker whose minimum really is 0 stopped sending it
17/// and got the Renderer's default instead. A list of runs has no such value. Empty and
18/// absent are the same state, so the first empty one is worth no record.
19const SPANS_BIT: u32 = 63;
20
21#[derive(Clone, Copy, Debug)]
22struct Handler {
23    id: u64,
24    element: ElementId,
25    node_id: u32,
26    name: &'static str,
27}
28
29#[derive(Clone, Copy, Debug, PartialEq, Eq)]
30enum PathTarget {
31    Node(u32),
32    /// A template's dynamic child: the node it hangs under, and the marker standing in
33    /// its place until its content arrives.
34    Slot {
35        parent: u32,
36        marker: u32,
37    },
38}
39
40/// One position among a parent's children.
41///
42/// The Renderer counts only the nodes it drew, so the index an Insert carries is the
43/// number of drawn siblings that come first. A placeholder and a template's unfilled
44/// dynamic child are both positions nothing is drawn for, and keeping them here is what
45/// lets the content that arrives later take the position it was promised rather than the
46/// end of the list.
47#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48enum Slot {
49    /// A node the Renderer drew.
50    Node(u32),
51    /// A Dioxus placeholder, named by the element it was created for. Placeholders all
52    /// carry node id 0, so the element is the only thing that tells two of them apart.
53    Hole(usize),
54    /// A template's dynamic child, from the moment the template is built until the
55    /// content of that child arrives.
56    Pending(u32),
57}
58
59#[derive(Debug)]
60struct StackNode {
61    node_id: u32,
62    /// The element this entry was created for, when it is a placeholder.
63    ///
64    /// A placeholder is not a node in the Compose tree, so every one of them carries node
65    /// id 0. Two placeholders on screen at once still stand in two different places, and
66    /// the element is what tells them apart.
67    element: Option<ElementId>,
68    paths: HashMap<Vec<u8>, PathTarget>,
69}
70
71/// How many Modifier slots a node has. The slot numbers are assigned in `set_modifier`,
72/// and this is one past the last of them.
73const MODIFIER_SLOTS: usize = 14;
74
75/// Node id 0 is the "no node" sentinel: a Dioxus placeholder, which draws nothing and
76/// takes no slot in the Compose tree.
77const PLACEHOLDER_NODE: u32 = 0;
78
79/// `dioxus-core` mutation sink that writes the Compose wire protocol directly.
80pub struct ComposeRenderer {
81    encoder: BatchEncoder,
82    next_node_id: u32,
83    next_handler_id: u64,
84    nodes: Vec<Option<u32>>,
85    handlers: Vec<Handler>,
86    /// Which parent each drawn node hangs under.
87    parents: HashMap<u32, u32>,
88    /// Which parent each placeholder stands under, keyed by its element rather than by a
89    /// node id.
90    ///
91    /// Placeholders all share node id 0, so keying them by node id made every one of them
92    /// overwrite the last: a branch that filled in was then attached to whichever other
93    /// placeholder had been seen most recently, which put a subtree under a stranger and,
94    /// when that stranger was its own descendant, left the parent chain with no root.
95    placeholders: HashMap<usize, u32>,
96    /// What stands under each parent, in order.
97    ///
98    /// An index is read off this list rather than remembered from the Insert that put a
99    /// node there. Removing a sibling moves everything after it, so a remembered index is
100    /// only right until the next removal: a menu whose entries came and went handed the
101    /// next set of entries the position the old last entry had held, and they piled up
102    /// past the end of the list instead of taking its place.
103    children: HashMap<u32, Vec<Slot>>,
104    /// The next marker to stand in a template's dynamic child.
105    next_marker: u32,
106    /// Which design properties a node has actually been given, one bit per tag.
107    optional_props: HashMap<u32, u64>,
108    /// A border arrives as a width and a colour in separate attributes; this holds
109    /// whichever came first until the pair can be written as one modifier.
110    pending_borders: HashMap<u32, (Option<f32>, Option<crate::Paint>)>,
111    /// Which attribute last wrote each of a node's Modifier slots, or `""` for a slot
112    /// nothing has written.
113    ///
114    /// Two things need the owner rather than a "written" bit. Clearing a slot that was
115    /// never written would cost a mutation on the first frame of every widget, for a
116    /// Modifier nobody asked for. And some slots have two attributes that can fill them,
117    /// `shape_role` and `corner_radius` for the shape, `padding_role` and `padding` for
118    /// the padding: the one left unset arrives as an empty attribute, and without the
119    /// owner it would clear the modifier its partner had just written, which is how a
120    /// rounded container came out square.
121    modifier_slots: HashMap<u32, [&'static str; MODIFIER_SLOTS]>,
122    /// Which application token each observed node was given, for reading a report back.
123    size_tokens: HashMap<u32, u32>,
124    stack: Vec<StackNode>,
125    error: Option<ProtocolError>,
126}
127
128impl Default for ComposeRenderer {
129    fn default() -> Self {
130        Self::new()
131    }
132}
133
134impl ComposeRenderer {
135    /// The application's name for an observed node, if that node is observed.
136    pub(crate) fn size_token(&self, node_id: u32) -> Option<u32> {
137        self.size_tokens.get(&node_id).copied()
138    }
139
140    pub fn new() -> Self {
141        Self {
142            encoder: BatchEncoder::with_capacity(16 * 1024, 4 * 1024, 256),
143            next_node_id: 1,
144            next_handler_id: 1,
145            nodes: Vec::with_capacity(256),
146            handlers: Vec::with_capacity(64),
147            parents: HashMap::with_capacity(256),
148            placeholders: HashMap::with_capacity(16),
149            children: HashMap::with_capacity(256),
150            next_marker: 1,
151            optional_props: HashMap::with_capacity(64),
152            pending_borders: HashMap::with_capacity(16),
153            modifier_slots: HashMap::with_capacity(64),
154            // Empty until a screen asks, which is the point: a tree that observes nothing
155            // allocates nothing here.
156            size_tokens: HashMap::new(),
157            stack: Vec::with_capacity(64),
158            error: None,
159        }
160    }
161
162    /// The batch arena, so a runtime that reads Host memory through a mapped view can be
163    /// handed one.
164    pub fn arena(&self) -> (*const u8, usize) {
165        self.encoder.arena()
166    }
167
168    pub fn begin_frame(&mut self) {
169        self.encoder.clear();
170        self.error = None;
171    }
172
173    pub fn finish_frame(&mut self) -> Result<&[u8], ProtocolError> {
174        if let Some(error) = self.error.take() {
175            return Err(error);
176        }
177        self.encoder.finish()
178    }
179
180    pub fn handler(&self, handler_id: u64) -> Option<(ElementId, u32, &'static str)> {
181        self.handlers
182            .iter()
183            .find(|handler| handler.id == handler_id)
184            .map(|handler| (handler.element, handler.node_id, handler.name))
185    }
186
187    pub fn set_text(
188        &mut self,
189        element: ElementId,
190        text: &str,
191        selection: Option<crate::Selection>,
192    ) {
193        let Some(node_id) = self.node(element) else {
194            return;
195        };
196        self.write(Mutation::SetText {
197            node_id,
198            text,
199            selection,
200        });
201    }
202
203    /// The root theme record. Written once per rebuild, never per frame.
204    pub fn set_theme(&mut self, theme: crate::schema::Theme) {
205        self.write(Mutation::SetTheme(theme));
206    }
207
208    /// The root window record. Written once per rebuild, never per frame.
209    pub fn set_window(&mut self, window: crate::schema::Window) {
210        self.write(Mutation::SetWindow(window));
211    }
212
213    pub fn set_text_node(&mut self, node_id: u32, text: &str, selection: Option<crate::Selection>) {
214        self.write(Mutation::SetText {
215            node_id,
216            text,
217            selection,
218        });
219    }
220
221    /// Copies one asset into the batch. The bytes ride behind the records, and the
222    /// Renderer takes its own copy inside the call that carries them.
223    pub fn register_asset(&mut self, asset_id: u32, kind: crate::schema::AssetKind, bytes: &[u8]) {
224        self.write(Mutation::RegisterAsset {
225            asset_id,
226            kind,
227            bytes,
228        });
229    }
230
231    pub fn release_asset(&mut self, asset_id: u32) {
232        self.write(Mutation::ReleaseAsset { asset_id });
233    }
234
235    /// Writes one transient message into the batch.
236    ///
237    /// It names no node, because a message is not in the tree: it is a sentence with a
238    /// lifetime, and that lifetime belongs to the Renderer.
239    pub fn show_message(
240        &mut self,
241        handler_id: u64,
242        text: &str,
243        action: &str,
244        duration: crate::schema::MessageDuration,
245    ) {
246        self.write(Mutation::ShowMessage {
247            handler_id,
248            text,
249            action,
250            duration,
251        });
252    }
253
254    /// Append the streamed tail to a Text node without resending its whole value.
255    pub fn append_text_node(&mut self, node_id: u32, text: &str) {
256        self.write(Mutation::AppendText { node_id, text });
257    }
258
259    /// Turns a Modifier attribute written in `rsx!` into one slot of the node's chain.
260    ///
261    /// The Renderer applies the chain in list order, exactly as hand-written Compose does,
262    /// so the slot an attribute occupies decides what the result looks like. Padding
263    /// before a background is a margin; after it, it insets the content. Fixing the slots
264    /// here means `Card { background: .., padding: 16.0 }` behaves the way the person who
265    /// wrote it expects, whichever order they happened to type the attributes in.
266    ///
267    /// The order below reads outside in: how big the widget is, what shape it is, what
268    /// fills it, what outlines it, what lifts it, what responds to a press, and finally
269    /// how far its content sits inside all of that.
270    ///
271    /// A `false` or absent value writes `Empty` into the slot rather than dropping the
272    /// mutation, because a slot that keeps its old value would leave the removed modifier
273    /// applied.
274    fn modifier_for(
275        &mut self,
276        element: ElementId,
277        node_id: u32,
278        name: &'static str,
279        value: &AttributeValue,
280    ) -> Option<Option<(u16, crate::Modifier)>> {
281        use crate::Modifier;
282
283        // Slot assignments. Append only: an existing slot never changes meaning, because a
284        // node keeps whatever a slot held until something overwrites it.
285        const WEIGHT: u16 = 0;
286        const FILL_MAX_WIDTH: u16 = 1;
287        const FILL_MAX_HEIGHT: u16 = 2;
288        const WIDTH: u16 = 3;
289        const HEIGHT: u16 = 4;
290        const SHAPE: u16 = 5;
291        const BACKGROUND: u16 = 6;
292        const BORDER: u16 = 7;
293        const ELEVATION: u16 = 8;
294        const CLICKABLE: u16 = 9;
295        const PADDING: u16 = 10;
296        const OBSERVE_SIZE: u16 = 11;
297        const MOTION: u16 = 12;
298        const MATERIAL: u16 = 13;
299
300        let float = |value: &AttributeValue| match value {
301            AttributeValue::Float(number) => Some(*number as f32),
302            AttributeValue::Int(number) => Some(*number as f32),
303            _ => None,
304        };
305        let integer = |value: &AttributeValue| match value {
306            AttributeValue::Int(number) => Some(*number),
307            AttributeValue::Float(number) => Some(*number as i64),
308            _ => None,
309        };
310
311        let slot_of = |name: &str| match name {
312            "weight" => Some(WEIGHT),
313            "fill_max_width" => Some(FILL_MAX_WIDTH),
314            "fill_max_height" => Some(FILL_MAX_HEIGHT),
315            "width" => Some(WIDTH),
316            "height" => Some(HEIGHT),
317            "shape_role" | "corner_radius" => Some(SHAPE),
318            "background" => Some(BACKGROUND),
319            "border_width" | "border_color" => Some(BORDER),
320            "elevation" => Some(ELEVATION),
321            "onclickable" => Some(CLICKABLE),
322            "padding" | "padding_role" => Some(PADDING),
323            "observe_size" => Some(OBSERVE_SIZE),
324            "motion" => Some(MOTION),
325            "material" => Some(MATERIAL),
326            _ => None,
327        };
328        let slot = slot_of(name)?;
329
330        // An unset Modifier attribute is not a property, so it must not fall through to
331        // set_property and be rejected as an unknown one. It only produces a mutation if
332        // this slot already holds something, because clearing a slot that was never
333        // written would cost a mutation on the first frame of every widget.
334        if matches!(value, AttributeValue::None) {
335            let owners = self
336                .modifier_slots
337                .entry(node_id)
338                .or_insert([""; MODIFIER_SLOTS]);
339            // Only the attribute that wrote the slot may clear it. `corner_radius` being
340            // unset says nothing about the `shape_role` sitting in the same slot.
341            if owners[slot as usize] != name {
342                return Some(None);
343            }
344            owners[slot as usize] = "";
345            return Some(Some((slot, crate::Modifier::Empty)));
346        }
347        self.modifier_slots
348            .entry(node_id)
349            .or_insert([""; MODIFIER_SLOTS])[slot as usize] = name;
350
351        match name {
352            // The token is the application's name for this node, and it stays here: what
353            // the Renderer reports back is its own node id, so the pairing has to be
354            // remembered at the moment the two are both in hand.
355            "observe_size" => {
356                let token = u32::try_from(integer(value)?).ok()?;
357                self.size_tokens.insert(node_id, token);
358                Some(Some((slot, Modifier::ObserveSize { token })))
359            }
360            "fill_max_width" | "fill_max_height" => {
361                let AttributeValue::Bool(enabled) = value else {
362                    return Some(None);
363                };
364                Some(Some((
365                    slot,
366                    if !enabled {
367                        Modifier::Empty
368                    } else if slot == FILL_MAX_WIDTH {
369                        Modifier::FillMaxWidth
370                    } else {
371                        Modifier::FillMaxHeight
372                    },
373                )))
374            }
375            "weight" => Some(Some((WEIGHT, Modifier::Weight(float(value)?)))),
376            "width" => Some(Some((WIDTH, Modifier::Width(float(value)?)))),
377            "height" => Some(Some((HEIGHT, Modifier::Height(float(value)?)))),
378            "padding" => Some(Some((PADDING, Modifier::Padding(float(value)?)))),
379            "padding_role" => Some(Some((
380                PADDING,
381                Modifier::PaddingRole(
382                    crate::SpaceRole::try_from(u16::try_from(integer(value)?).ok()?).ok()?,
383                ),
384            ))),
385            "elevation" => Some(Some((ELEVATION, Modifier::Elevation(float(value)?)))),
386            "corner_radius" => {
387                let radius = float(value)?;
388                Some(Some((
389                    SHAPE,
390                    Modifier::Shape {
391                        top_start: radius,
392                        top_end: radius,
393                        bottom_end: radius,
394                        bottom_start: radius,
395                    },
396                )))
397            }
398            // The node says how important its changes are. Which curve and how many
399            // milliseconds that means is the design system's answer, and it is never
400            // asked here.
401            "motion" => Some(Some((
402                MOTION,
403                Modifier::Motion(
404                    crate::MotionRole::try_from(u16::try_from(integer(value)?).ok()?).ok()?,
405                ),
406            ))),
407            // What the surface is made of. Blur, tone or a flat fill is the running
408            // design system's answer, and the node does not get to ask for one of them.
409            "material" => Some(Some((
410                MATERIAL,
411                Modifier::Material(
412                    crate::MaterialRole::try_from(u16::try_from(integer(value)?).ok()?).ok()?,
413                ),
414            ))),
415            "shape_role" => Some(Some((
416                SHAPE,
417                Modifier::ShapeRole(
418                    crate::ShapeRole::try_from(u16::try_from(integer(value)?).ok()?).ok()?,
419                ),
420            ))),
421            // Colour crosses as the bits of a Paint, so a role and a literal colour take
422            // the same path and there is one wire representation of colour.
423            "background" => Some(Some((
424                BACKGROUND,
425                Modifier::Background(crate::Paint::from_bits(integer(value)? as u64)?),
426            ))),
427            // A border needs a width and a colour, which arrive as two attributes. The
428            // half that arrives first is remembered so the pair can be written as one
429            // modifier, whichever order Dioxus hands them over in.
430            "border_width" | "border_color" => {
431                let pending = self.pending_borders.entry(node_id).or_default();
432                if name == "border_width" {
433                    pending.0 = Some(float(value)?);
434                } else {
435                    pending.1 = Some(crate::Paint::from_bits(integer(value)? as u64)?);
436                }
437                let (Some(width), Some(paint)) = (pending.0, pending.1) else {
438                    return Some(None);
439                };
440                Some(Some((BORDER, Modifier::Border { width, paint })))
441            }
442            "onclickable" => {
443                let AttributeValue::Listener(_) = value else {
444                    return Some(None);
445                };
446                let handler_id = self.next_handler_id;
447                self.next_handler_id += 1;
448                self.handlers.push(Handler {
449                    id: handler_id,
450                    element,
451                    node_id,
452                    name,
453                });
454                Some(Some((CLICKABLE, Modifier::Clickable { handler_id })))
455            }
456            _ => None,
457        }
458    }
459
460    fn write(&mut self, mutation: Mutation<'_>) {
461        if self.error.is_none() {
462            if let Err(error) = self.encoder.encode(&mutation) {
463                self.error = Some(error);
464            }
465        }
466    }
467
468    fn allocate_node(&mut self, widget: WidgetKind) -> u32 {
469        let id = self.next_node_id;
470        self.next_node_id = self.next_node_id.checked_add(1).unwrap_or(1);
471        self.write(Mutation::Create {
472            node_id: id,
473            widget,
474        });
475        id
476    }
477
478    fn map_node(&mut self, element: ElementId, node_id: u32) {
479        if self.nodes.len() <= element.0 {
480            self.nodes.resize(element.0 + 1, None);
481        }
482        self.nodes[element.0] = Some(node_id);
483    }
484
485    fn node(&self, element: ElementId) -> Option<u32> {
486        self.nodes.get(element.0).copied().flatten()
487    }
488
489    fn build_template_node(
490        &mut self,
491        node: &TemplateNode,
492        path: &mut Vec<u8>,
493        paths: &mut HashMap<Vec<u8>, PathTarget>,
494    ) -> Option<u32> {
495        match node {
496            TemplateNode::Element {
497                tag,
498                attrs,
499                children,
500                ..
501            } => {
502                let widget = match widget_kind(tag) {
503                    Ok(widget) => widget,
504                    Err(error) => {
505                        self.error = Some(error);
506                        return None;
507                    }
508                };
509                let node_id = self.allocate_node(widget);
510                paths.insert(path.clone(), PathTarget::Node(node_id));
511                for attribute in *attrs {
512                    if let TemplateAttribute::Static { name, value, .. } = attribute {
513                        self.set_property(node_id, name, &AttributeValue::Text((*value).into()));
514                    }
515                }
516                for (index, child) in children.iter().enumerate() {
517                    path.push(index as u8);
518                    match child {
519                        TemplateNode::Dynamic { .. } => {
520                            let marker = self.reserve_slot(node_id);
521                            paths.insert(
522                                path.clone(),
523                                PathTarget::Slot {
524                                    parent: node_id,
525                                    marker,
526                                },
527                            );
528                        }
529                        _ => {
530                            if let Some(child_id) = self.build_template_node(child, path, paths) {
531                                let end = self.end_of(node_id);
532                                self.insert_node(node_id, child_id, end, None);
533                            }
534                        }
535                    }
536                    path.pop();
537                }
538                Some(node_id)
539            }
540            TemplateNode::Text { text } => {
541                let node_id = self.allocate_node(WidgetKind::Text);
542                self.write(Mutation::SetProp {
543                    node_id,
544                    property: PropertyKind::Text,
545                    value: PropertyValue::String(text),
546                });
547                paths.insert(path.clone(), PathTarget::Node(node_id));
548                Some(node_id)
549            }
550            TemplateNode::Dynamic { .. } => None,
551        }
552    }
553
554    /// Role tag 0 means "not sent", so a design property at its neutral value
555    /// produces no record at all. A property that was set and then cleared still sends
556    /// its zero once, which is what tells the Renderer to drop the override.
557    fn should_write_optional_property(
558        &mut self,
559        node_id: u32,
560        property: PropertyKind,
561        neutral: bool,
562    ) -> bool {
563        let tag = property as u16;
564        let index = if (FIRST_OPTIONAL_PROPERTY..=LAST_OPTIONAL_PROPERTY).contains(&tag) {
565            u32::from(tag - FIRST_OPTIONAL_PROPERTY)
566        } else if property == PropertyKind::Spans {
567            SPANS_BIT
568        } else {
569            return true;
570        };
571        let bit = 1_u64 << index;
572        let seen = self.optional_props.get(&node_id).copied().unwrap_or(0);
573        if neutral {
574            if seen & bit == 0 {
575                return false;
576            }
577            self.optional_props.insert(node_id, seen & !bit);
578        } else if seen & bit == 0 {
579            self.optional_props.insert(node_id, seen | bit);
580        }
581        true
582    }
583
584    fn set_property(&mut self, node_id: u32, name: &str, value: &AttributeValue) {
585        let Some(property) = property_kind(name) else {
586            // dioxus-core has no fallible WriteMutations methods. Preserve the
587            // protocol error and surface it when the batch is finalized.
588            self.error = Some(ProtocolError::InvalidProperty(0));
589            return;
590        };
591        let value = match value {
592            AttributeValue::Text(value) => PropertyValue::String(value),
593            AttributeValue::Float(value) => PropertyValue::Float(*value as f32),
594            AttributeValue::Int(value) => PropertyValue::Integer(*value),
595            AttributeValue::Bool(value) => PropertyValue::Bool(*value),
596            AttributeValue::None => PropertyValue::None,
597            // A drawing command list is the one value that is neither a number nor text.
598            // dioxus-core compares it before calling here, so an unchanged list never
599            // reaches this point and costs no record.
600            AttributeValue::Any(value) => {
601                let any = value.as_any();
602                if let Some(list) = any.downcast_ref::<DrawList>() {
603                    PropertyValue::Bytes(list.as_bytes())
604                } else if let Some(spans) = any.downcast_ref::<crate::spans::TextSpans>() {
605                    // Runs inside a string travel the same way a drawing does, and for the
606                    // same reason: a list that has not changed compares equal before it
607                    // reaches here and costs no record at all.
608                    PropertyValue::Bytes(spans.as_bytes())
609                } else {
610                    return;
611                }
612            }
613            AttributeValue::Listener(_) => return,
614        };
615        let neutral = matches!(
616            value,
617            PropertyValue::None | PropertyValue::Integer(0) | PropertyValue::Float(0.0)
618        );
619        if !self.should_write_optional_property(node_id, property, neutral) {
620            return;
621        }
622        self.write(Mutation::SetProp {
623            node_id,
624            property,
625            value,
626        });
627    }
628
629    /// One past the last position under `parent`.
630    fn end_of(&self, parent: u32) -> usize {
631        self.children.get(&parent).map_or(0, Vec::len)
632    }
633
634    /// Stands a marker in a template's dynamic child until its content arrives, and says
635    /// which marker it was.
636    fn reserve_slot(&mut self, parent: u32) -> u32 {
637        let marker = self.next_marker;
638        self.next_marker = self.next_marker.checked_add(1).unwrap_or(1);
639        self.children
640            .entry(parent)
641            .or_default()
642            .push(Slot::Pending(marker));
643        marker
644    }
645
646    /// Where the given slot stands: the parent it hangs under and its position in that
647    /// parent's list.
648    fn locate(&self, slot: Slot) -> Option<(u32, usize)> {
649        let parent = match slot {
650            Slot::Node(node_id) => *self.parents.get(&node_id)?,
651            Slot::Hole(element) => *self.placeholders.get(&element)?,
652            Slot::Pending(_) => return None,
653        };
654        let position = self
655            .children
656            .get(&parent)?
657            .iter()
658            .position(|standing| *standing == slot)?;
659        Some((parent, position))
660    }
661
662    /// How many drawn nodes stand before `position` under `parent`, which is the index
663    /// the Renderer understands.
664    fn drawn_before(&self, parent: u32, position: usize) -> u32 {
665        self.children.get(&parent).map_or(0, |list| {
666            list.iter()
667                .take(position)
668                .filter(|slot| matches!(slot, Slot::Node(_)))
669                .count() as u32
670        })
671    }
672
673    /// Takes the slot out of wherever it stands, and says where that was.
674    fn detach(&mut self, slot: Slot) -> Option<(u32, usize)> {
675        let (parent, position) = self.locate(slot)?;
676        if let Some(list) = self.children.get_mut(&parent) {
677            list.remove(position);
678        }
679        Some((parent, position))
680    }
681
682    /// Puts the slot under `parent` at `position`, and says which position it took.
683    fn attach(&mut self, parent: u32, slot: Slot, position: usize) -> usize {
684        let mut position = position;
685        if let Some((from, was_at)) = self.detach(slot) {
686            if from == parent && was_at < position {
687                position -= 1;
688            }
689        }
690        let list = self.children.entry(parent).or_default();
691        let position = position.min(list.len());
692        list.insert(position, slot);
693        match slot {
694            Slot::Node(node_id) => {
695                self.parents.insert(node_id, parent);
696            }
697            Slot::Hole(element) => {
698                self.placeholders.insert(element, parent);
699            }
700            Slot::Pending(_) => {}
701        }
702        position
703    }
704
705    /// Forgets a node and everything under it, as the Renderer does when it is removed.
706    /// The caller has already taken the node out of its parent's list.
707    fn forget(&mut self, node_id: u32) {
708        self.parents.remove(&node_id);
709        for slot in self.children.remove(&node_id).unwrap_or_default() {
710            match slot {
711                Slot::Node(child) => self.forget(child),
712                Slot::Hole(element) => {
713                    self.placeholders.remove(&element);
714                }
715                Slot::Pending(_) => {}
716            }
717        }
718    }
719
720    fn insert_stack(&mut self, parent: u32, position: usize, count: usize) {
721        let start = self.stack.len().saturating_sub(count);
722        let nodes: Vec<_> = self.stack.drain(start..).collect();
723        let mut at = position;
724        for node in nodes {
725            at = self.insert_node(parent, node.node_id, at, node.element) + 1;
726        }
727    }
728
729    /// Puts one entry under `parent` at `position` and tells the Renderer about it,
730    /// unless nothing is drawn for it. Says which position it took.
731    fn insert_node(
732        &mut self,
733        parent: u32,
734        node_id: u32,
735        position: usize,
736        element: Option<ElementId>,
737    ) -> usize {
738        if node_id == PLACEHOLDER_NODE {
739            // Nothing is drawn for a placeholder, so nothing is sent. It still holds its
740            // position here, so the branch that fills it in takes that position rather
741            // than the end of the list.
742            let Some(element) = element else {
743                return position;
744            };
745            return self.attach(parent, Slot::Hole(element.0), position);
746        }
747        let moving = self.parents.contains_key(&node_id);
748        let at = self.attach(parent, Slot::Node(node_id), position);
749        let index = self.drawn_before(parent, at);
750        self.write(if moving {
751            Mutation::Move {
752                parent_id: parent,
753                node_id,
754                index,
755            }
756        } else {
757            Mutation::Insert {
758                parent_id: parent,
759                node_id,
760                index,
761            }
762        });
763        at
764    }
765
766    /// Where the given element stands: its own position, or its placeholder's.
767    fn slot_of(&self, id: ElementId) -> Option<(u32, usize)> {
768        match self.node(id) {
769            Some(PLACEHOLDER_NODE) | None => self.locate(Slot::Hole(id.0)),
770            Some(node_id) => self.locate(Slot::Node(node_id)),
771        }
772    }
773}
774
775impl WriteMutations for ComposeRenderer {
776    fn append_children(&mut self, id: ElementId, m: usize) {
777        if let Some(parent) = self.node(id) {
778            let end = self.end_of(parent);
779            self.insert_stack(parent, end, m);
780        }
781    }
782
783    fn assign_node_id(&mut self, path: &'static [u8], id: ElementId) {
784        if let Some(PathTarget::Node(node_id)) = self
785            .stack
786            .last()
787            .and_then(|loaded| loaded.paths.get(path))
788            .copied()
789        {
790            self.map_node(id, node_id);
791        }
792    }
793
794    fn create_placeholder(&mut self, id: ElementId) {
795        // A Dioxus placeholder is not materialized in the Compose tree. It is
796        // represented by its eventual insertion position.
797        self.map_node(id, PLACEHOLDER_NODE);
798        self.stack.push(StackNode {
799            node_id: PLACEHOLDER_NODE,
800            element: Some(id),
801            paths: HashMap::new(),
802        });
803    }
804
805    fn create_text_node(&mut self, value: &str, id: ElementId) {
806        let node_id = self.allocate_node(WidgetKind::Text);
807        self.map_node(id, node_id);
808        self.write(Mutation::SetProp {
809            node_id,
810            property: PropertyKind::Text,
811            value: PropertyValue::String(value),
812        });
813        self.stack.push(StackNode {
814            node_id,
815            element: None,
816            paths: HashMap::new(),
817        });
818    }
819
820    fn load_template(&mut self, template: Template, index: usize, id: ElementId) {
821        let mut paths = HashMap::new();
822        let mut path = Vec::new();
823        if let Some(root) = self.build_template_node(&template.roots[index], &mut path, &mut paths)
824        {
825            self.map_node(id, root);
826            self.stack.push(StackNode {
827                node_id: root,
828                element: None,
829                paths,
830            });
831        }
832    }
833
834    fn replace_node_with(&mut self, id: ElementId, m: usize) {
835        if let Some(node_id) = self.node(id) {
836            let target = self.slot_of(id);
837            if node_id == PLACEHOLDER_NODE {
838                self.detach(Slot::Hole(id.0));
839                self.placeholders.remove(&id.0);
840            } else {
841                self.detach(Slot::Node(node_id));
842                self.write(Mutation::Remove { node_id });
843                self.forget(node_id);
844            }
845            // The slot has just been taken out, so what replaces it goes in at the
846            // position it held.
847            if let Some((parent, position)) = target {
848                self.insert_stack(parent, position, m);
849                return;
850            }
851        }
852        let keep_from = self.stack.len().saturating_sub(m);
853        self.stack.drain(..keep_from);
854    }
855
856    fn replace_placeholder_with_nodes(&mut self, path: &'static [u8], m: usize) {
857        let parent_position = self.stack.len().saturating_sub(m + 1);
858        let target = self
859            .stack
860            .get(parent_position)
861            .and_then(|loaded| loaded.paths.get(path))
862            .copied();
863        if let Some(PathTarget::Slot { parent, marker }) = target {
864            let Some(position) = self
865                .children
866                .get(&parent)
867                .and_then(|list| list.iter().position(|slot| *slot == Slot::Pending(marker)))
868            else {
869                return;
870            };
871            if let Some(list) = self.children.get_mut(&parent) {
872                list.remove(position);
873            }
874            self.insert_stack(parent, position, m);
875        }
876    }
877
878    fn insert_nodes_after(&mut self, id: ElementId, m: usize) {
879        if let Some((parent, position)) = self.slot_of(id) {
880            self.insert_stack(parent, position + 1, m);
881        }
882    }
883
884    fn insert_nodes_before(&mut self, id: ElementId, m: usize) {
885        if let Some((parent, position)) = self.slot_of(id) {
886            self.insert_stack(parent, position, m);
887        }
888    }
889
890    fn set_attribute(
891        &mut self,
892        name: &'static str,
893        _namespace: Option<&'static str>,
894        value: &AttributeValue,
895        id: ElementId,
896    ) {
897        if let Some(node_id) = self.node(id) {
898            if let Some(slot) = self.modifier_for(id, node_id, name, value) {
899                if let Some((index, modifier)) = slot {
900                    self.write(Mutation::SetModifier {
901                        node_id,
902                        index,
903                        modifier,
904                    });
905                }
906                return;
907            }
908            self.set_property(node_id, name, value);
909        }
910    }
911
912    fn set_node_text(&mut self, value: &str, id: ElementId) {
913        if let Some(node_id) = self.node(id) {
914            self.write(Mutation::SetProp {
915                node_id,
916                property: PropertyKind::Text,
917                value: PropertyValue::String(value),
918            });
919        }
920    }
921
922    fn create_event_listener(&mut self, name: &'static str, id: ElementId) {
923        let Some(node_id) = self.node(id) else {
924            return;
925        };
926        let handler_id = self.next_handler_id;
927        self.next_handler_id += 1;
928        self.handlers.push(Handler {
929            id: handler_id,
930            element: id,
931            node_id,
932            name,
933        });
934        if let Some(property) = event_property(name) {
935            self.write(Mutation::SetProp {
936                node_id,
937                property,
938                value: PropertyValue::Integer(handler_id as i64),
939            });
940        }
941    }
942
943    fn remove_event_listener(&mut self, name: &'static str, id: ElementId) {
944        let Some(position) = self
945            .handlers
946            .iter()
947            .position(|handler| handler.element == id && handler.name == name)
948        else {
949            return;
950        };
951        self.handlers.swap_remove(position);
952        if let (Some(node_id), Some(property)) = (self.node(id), event_property(name)) {
953            self.write(Mutation::SetProp {
954                node_id,
955                property,
956                value: PropertyValue::None,
957            });
958        }
959    }
960
961    fn remove_node(&mut self, id: ElementId) {
962        self.detach(Slot::Hole(id.0));
963        self.placeholders.remove(&id.0);
964        if let Some(node_id) = self.node(id) {
965            if node_id != PLACEHOLDER_NODE {
966                self.detach(Slot::Node(node_id));
967                self.write(Mutation::Remove { node_id });
968                self.forget(node_id);
969            }
970        }
971        if let Some(slot) = self.nodes.get_mut(id.0) {
972            *slot = None;
973        }
974        self.handlers.retain(|handler| handler.element != id);
975    }
976
977    fn push_root(&mut self, id: ElementId) {
978        if let Some(node_id) = self.node(id) {
979            self.stack.push(StackNode {
980                node_id,
981                element: Some(id),
982                paths: HashMap::new(),
983            });
984        }
985    }
986}
987
988fn widget_kind(name: &str) -> Result<WidgetKind, ProtocolError> {
989    WidgetKind::from_name(name).map_err(|()| ProtocolError::InvalidWidget(0))
990}
991
992fn property_kind(name: &str) -> Option<PropertyKind> {
993    PropertyKind::from_name(name).ok()
994}
995
996fn event_property(name: &str) -> Option<PropertyKind> {
997    match name {
998        "click" | "onclick" => Some(PropertyKind::OnClick),
999        "valuechange" | "onvaluechange" => Some(PropertyKind::OnValueChange),
1000        "submit" | "onsubmit" => Some(PropertyKind::OnSubmit),
1001        "focuslost" | "onfocuslost" => Some(PropertyKind::OnFocusLost),
1002        "keydown" | "onkeydown" => Some(PropertyKind::OnKeyDown),
1003        "rangerequest" | "onrangerequest" => Some(PropertyKind::OnRangeRequested),
1004        "dismiss" | "ondismiss" => Some(PropertyKind::OnDismiss),
1005        "filesentered" | "onfilesentered" => Some(PropertyKind::OnFilesEntered),
1006        "filesdropped" | "onfilesdropped" => Some(PropertyKind::OnFilesDropped),
1007        "change" | "onchange" => Some(PropertyKind::OnValueChange),
1008        _ => None,
1009    }
1010}
1011
1012#[cfg(test)]
1013mod tests {
1014    use super::*;
1015    use crate::prelude::*;
1016
1017    fn app() -> Element {
1018        rsx! {
1019            Column {
1020                Text { text: "zero" }
1021                TextField { placeholder: "type" }
1022                Button { text: "increment", on_click: move |_| {} }
1023            }
1024        }
1025    }
1026
1027    #[test]
1028    /// The M0 screen written as `rsx!` produces the same tree the protocol describes.
1029    ///
1030    /// Named after the requirement now. It was the only test standing behind that
1031    /// requirement and it did not say so, so counting the tests named for each one
1032    /// reported none and the requirement read as unimplemented.
1033    fn fr6_an_rsx_component_builds_the_same_tree() {
1034        let mut dom = VirtualDom::new(app);
1035        let mut renderer = ComposeRenderer::new();
1036        dom.rebuild(&mut renderer);
1037        let decoded = crate::protocol::decode_batch(renderer.finish_frame().unwrap()).unwrap();
1038        eprintln!("{decoded:#?}");
1039        assert!(decoded.iter().any(|mutation| matches!(
1040            mutation,
1041            Mutation::Create {
1042                widget: WidgetKind::Column,
1043                ..
1044            }
1045        )));
1046        assert!(decoded.iter().any(|mutation| matches!(
1047            mutation,
1048            Mutation::Create {
1049                widget: WidgetKind::TextField,
1050                ..
1051            }
1052        )));
1053        assert_eq!(
1054            decoded
1055                .iter()
1056                .filter(|mutation| matches!(mutation, Mutation::Insert { .. }))
1057                .count(),
1058            3
1059        );
1060    }
1061}