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
9const FIRST_OPTIONAL_PROPERTY: u16 = PropertyKind::TypeRole as u16;
11const LAST_OPTIONAL_PROPERTY: u16 = PropertyKind::Variant as u16;
12
13const 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 Slot {
35 parent: u32,
36 marker: u32,
37 },
38}
39
40#[derive(Clone, Copy, Debug, PartialEq, Eq)]
48enum Slot {
49 Node(u32),
51 Hole(usize),
54 Pending(u32),
57}
58
59#[derive(Debug)]
60struct StackNode {
61 node_id: u32,
62 element: Option<ElementId>,
68 paths: HashMap<Vec<u8>, PathTarget>,
69}
70
71const MODIFIER_SLOTS: usize = 14;
74
75const PLACEHOLDER_NODE: u32 = 0;
78
79pub 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 parents: HashMap<u32, u32>,
88 placeholders: HashMap<usize, u32>,
96 children: HashMap<u32, Vec<Slot>>,
104 next_marker: u32,
106 optional_props: HashMap<u32, u64>,
108 pending_borders: HashMap<u32, (Option<f32>, Option<crate::Paint>)>,
111 modifier_slots: HashMap<u32, [&'static str; MODIFIER_SLOTS]>,
122 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 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 size_tokens: HashMap::new(),
157 stack: Vec::with_capacity(64),
158 error: None,
159 }
160 }
161
162 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 pub fn set_theme(&mut self, theme: crate::schema::Theme) {
205 self.write(Mutation::SetTheme(theme));
206 }
207
208 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 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 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 pub fn append_text_node(&mut self, node_id: u32, text: &str) {
256 self.write(Mutation::AppendText { node_id, text });
257 }
258
259 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 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 if matches!(value, AttributeValue::None) {
335 let owners = self
336 .modifier_slots
337 .entry(node_id)
338 .or_insert([""; MODIFIER_SLOTS]);
339 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 "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 "motion" => Some(Some((
402 MOTION,
403 Modifier::Motion(
404 crate::MotionRole::try_from(u16::try_from(integer(value)?).ok()?).ok()?,
405 ),
406 ))),
407 "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 "background" => Some(Some((
424 BACKGROUND,
425 Modifier::Background(crate::Paint::from_bits(integer(value)? as u64)?),
426 ))),
427 "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 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 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 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 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 fn end_of(&self, parent: u32) -> usize {
631 self.children.get(&parent).map_or(0, Vec::len)
632 }
633
634 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 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 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 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 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 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 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 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 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 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 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 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}