1use std::any::{Any, TypeId};
2use std::cell::Cell;
3use std::sync::Arc;
4
5use smallvec::SmallVec;
6
7use crate::callback::ScopeId;
8use crate::core::memo::MemoElement;
9use crate::overlay::Portal;
10use crate::style::{LayoutConstraints, Length, ShrinkPriority, Theme};
11#[cfg(feature = "big-text")]
12use crate::widgets::BigText;
13#[cfg(feature = "image")]
14use crate::widgets::Image;
15#[cfg(feature = "terminal")]
16use crate::widgets::Terminal;
17use crate::widgets::{
18 Animated, AsciiCanvas, Button, Canvas, Center, CenterPin, Chart, Checkbox, ClassDiagram,
19 Divider, DocumentView, DragSource, DraggableTabBar, DropTarget, EffectScope, ErDiagram, Flow,
20 Flowchart, Frame, GanttDiagram, Graph, Grid, HStack, Heatmap, HexArea, Input, List,
21 MouseRegion, PanView, ProgressBar, ScrollView, SequenceDiagram, Spacer, Spinner, Splitter,
22 StateDiagram, StatusBarLayout, Tabs, Text, VStack, ZStack,
23};
24
25#[derive(Clone, Copy, Debug, PartialEq, Eq)]
26pub(crate) struct MeasureCacheEntry {
27 pub max_w: Option<u16>,
28 pub max_h: Option<u16>,
29 pub size: (u16, u16),
30}
31
32#[derive(Clone, Debug, PartialEq, Eq, Hash)]
34pub struct Key(Arc<str>);
35
36impl From<&'static str> for Key {
37 fn from(s: &'static str) -> Self {
38 Self(Arc::from(s))
39 }
40}
41
42impl From<String> for Key {
43 fn from(s: String) -> Self {
44 Self(Arc::from(s))
45 }
46}
47
48impl From<Arc<str>> for Key {
49 fn from(s: Arc<str>) -> Self {
50 Self(s)
51 }
52}
53
54impl AsRef<str> for Key {
55 fn as_ref(&self) -> &str {
56 &self.0
57 }
58}
59
60impl std::fmt::Display for Key {
61 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
62 write!(f, "{}", self.0)
63 }
64}
65
66#[derive(Clone)]
68pub struct Element {
69 pub(crate) key: Option<Key>,
71 pub(crate) kind: ElementKind,
73 pub(crate) layout: LayoutConstraints,
75 pub(crate) layout_hash_cache: Cell<Option<u64>>,
77 pub(crate) measure_cache: Cell<[Option<MeasureCacheEntry>; 2]>,
79 pub(crate) split_wrap_probe_cache: Cell<Option<bool>>,
81}
82
83impl Element {
84 pub(crate) fn new(kind: ElementKind) -> Self {
86 Self {
87 key: None,
88 kind,
89 layout: LayoutConstraints::default(),
90 layout_hash_cache: Cell::new(None),
91 measure_cache: Cell::new([None, None]),
92 split_wrap_probe_cache: Cell::new(None),
93 }
94 }
95
96 pub(crate) fn clear_caches(&self) {
97 self.layout_hash_cache.set(None);
98 self.measure_cache.set([None, None]);
99 self.split_wrap_probe_cache.set(None);
100 }
101
102 pub fn key(mut self, key: impl Into<Key>) -> Self {
108 self.key = Some(key.into());
109 self
110 }
111
112 pub fn component_state_key(mut self, key: impl Into<Key>) -> Self {
123 if let ElementKind::Component(component) = &mut self.kind {
124 component.state_key = Some(key.into());
125 }
126 self
127 }
128
129 pub fn min_width(mut self, w: Length) -> Self {
131 self.layout.min_w = w;
132 self.clear_caches();
133 self
134 }
135
136 pub fn min_height(mut self, h: Length) -> Self {
138 self.layout.min_h = h;
139 self.clear_caches();
140 self
141 }
142
143 pub fn max_width(mut self, w: Length) -> Self {
148 self.layout.max_w = Some(w);
149 if let (Length::Px(max_px), Length::Px(min_px)) = (w, self.layout.min_w)
150 && min_px > max_px
151 {
152 self.layout.min_w = Length::Px(max_px);
153 }
154 self.clear_caches();
155 self
156 }
157
158 pub fn max_height(mut self, h: Length) -> Self {
163 self.layout.max_h = Some(h);
164 if let (Length::Px(max_px), Length::Px(min_px)) = (h, self.layout.min_h)
165 && min_px > max_px
166 {
167 self.layout.min_h = Length::Px(max_px);
168 }
169 self.clear_caches();
170 self
171 }
172
173 pub fn reflows(mut self, reflows: bool) -> Self {
176 self.layout.reflows = reflows;
177 self.clear_caches();
178 self
179 }
180
181 pub fn shrink_priority(mut self, priority: ShrinkPriority) -> Self {
184 self.layout.shrink_priority = priority;
185 self.clear_caches();
186 self
187 }
188
189 pub(crate) fn with_layout(mut self, layout: LayoutConstraints) -> Self {
190 self.layout = layout;
191 self.clear_caches();
192 self
193 }
194
195 pub(crate) fn layout_constraints(&self) -> LayoutConstraints {
196 match &self.kind {
197 ElementKind::Group(group) => group.child.layout_constraints(),
198 ElementKind::EffectScope(scope) => scope
199 .child
200 .as_deref()
201 .map(Element::layout_constraints)
202 .unwrap_or(self.layout),
203 ElementKind::Animated(animated) => {
204 if animated.height.is_some() {
205 let mut layout = animated.child.layout_constraints();
206 layout.min_h = self.layout.min_h;
207 layout.max_h = self.layout.max_h;
208 layout.focus_min_h = self.layout.focus_min_h;
209 layout.collapse_h = self.layout.collapse_h;
210 layout.force_compact = false;
211 layout
212 } else {
213 animated.child.layout_constraints()
214 }
215 }
216 ElementKind::DragSource(source) => source
217 .child
218 .as_deref()
219 .map(Element::layout_constraints)
220 .unwrap_or(self.layout),
221 ElementKind::DropTarget(target) => target
222 .child
223 .as_deref()
224 .map(Element::layout_constraints)
225 .unwrap_or(self.layout),
226 ElementKind::ThemeProvider(tp) => tp.child.layout_constraints(),
227 ElementKind::ContextProvider(cp) => cp.child.layout_constraints(),
228 ElementKind::Memo(_) => self.layout,
229 _ => self.layout,
230 }
231 }
232
233 pub(crate) fn contains_unexpanded_component(&self) -> bool {
234 matches!(self.kind, ElementKind::Component(_))
235 || self
236 .kind
237 .children()
238 .into_iter()
239 .any(Self::contains_unexpanded_component)
240 }
241
242 pub(crate) fn replace_group_child_by_scope(
245 &mut self,
246 scope: ScopeId,
247 replacement: &mut Option<Element>,
248 ) -> bool {
249 if let ElementKind::Group(group) = &mut self.kind
250 && group.scope == scope
251 {
252 if let Some(replacement) = replacement.take() {
253 *group.child = replacement;
254 self.clear_caches();
255 return true;
256 }
257 return false;
258 }
259
260 let replaced = self
261 .kind
262 .children_mut()
263 .iter_mut()
264 .any(|child| child.replace_group_child_by_scope(scope, replacement));
265 if replaced {
266 self.clear_caches();
267 }
268 replaced
269 }
270}
271
272#[derive(Clone)]
274#[allow(clippy::large_enum_variant)]
275pub(crate) enum ElementKind {
276 Text(Text),
278 #[cfg(feature = "big-text")]
280 BigText(BigText),
281 AsciiCanvas(AsciiCanvas),
283
284 Button(Box<Button>),
286 Input(Box<Input>),
288 #[cfg(feature = "image")]
290 Image(Image),
291 List(Box<List>),
293 TextArea(Box<crate::widgets::TextArea>),
295 HexArea(Box<HexArea>),
297 #[cfg(feature = "terminal")]
299 Terminal(Terminal),
300 Popover(crate::widgets::Popover),
302 Portal(Portal),
303 Table(Box<crate::widgets::Table>),
305 Tabs(Tabs),
307 DraggableTabBar(Box<DraggableTabBar>),
309 Component(crate::core::nested::ComponentElement),
311 Group(Group),
313 EffectScope(EffectScope),
315 Animated(Animated),
317 DragSource(DragSource),
319 DropTarget(DropTarget),
321 MouseRegion(MouseRegion),
323 ScrollView(Box<ScrollView>),
325 PanView(PanView),
327 VStack(VStack),
329 HStack(HStack),
331 Grid(Grid),
333 Flow(Flow),
335 Canvas(Canvas),
337 Flowchart(Box<Flowchart>),
339 ZStack(ZStack),
341 Center(Center),
343 CenterPin(CenterPin),
345 Frame(Frame),
347 Divider(Divider),
349 Spacer(Spacer),
351 Sparkline(crate::widgets::Sparkline),
353 Chart(Box<Chart>),
355 Graph(Box<Graph>),
357 SequenceDiagram(Box<SequenceDiagram>),
359 ClassDiagram(Box<ClassDiagram>),
361 StateDiagram(Box<StateDiagram>),
363 ErDiagram(Box<ErDiagram>),
365 GanttDiagram(Box<GanttDiagram>),
367 StatusBarLayout(StatusBarLayout),
369 Heatmap(Heatmap),
371 Checkbox(Checkbox),
373 ProgressBar(ProgressBar),
375 Slider(crate::widgets::Slider),
377 Spinner(Spinner),
379 Splitter(Splitter),
381 DocumentView(Box<DocumentView>),
383 ThemeProvider(Box<ThemeProviderElement>),
385 ContextProvider(Box<ContextProviderElement>),
387 Memo(MemoElement),
389}
390
391macro_rules! impl_element_dimensions {
393 (
394 @direct [ $($v:ident,)* ]
395 @direct_gated [ $($gv:ident => $gf:literal,)* ]
396 @direct_no_hash [ $($dnh:ident,)* ]
397 @direct_no_hash_gated [ $($dnhg:ident => $dnhgf:literal,)* ]
398 @props_dims [ $($pd:ident,)* ]
399 @const_auto_hash [ $($cah:ident,)* ]
400 @const_auto_hash_gated [ $($cahg:ident => $cahgf:literal,)* ]
401 @const_flex [ $($cf:ident,)* ]
402 @const_flex_no_hash [ $($cfnh:ident,)* ]
403 @no_dims [ $($nd:ident,)* ]
404 @element_only_const_auto [ $($eo:ident,)* ]
405 ) => {
406 impl ElementKind {
407 pub(crate) fn dimensions(&self) -> Option<(Length, Length)> {
412 match self {
413 $( Self::$v(w) => Some((w.width, w.height)), )*
415 $( Self::$dnh(w) => Some((w.width, w.height)), )*
416 $( #[cfg(feature = $gf)] Self::$gv(w) => Some((w.width, w.height)), )*
417 $( #[cfg(feature = $dnhgf)] Self::$dnhg(w) => Some((w.width, w.height)), )*
418
419 $( Self::$pd(w) => Some((w.props.width, w.props.height)), )*
421
422 $( Self::$cah(_) )|* $( | Self::$eo(_) )* => {
424 Some((Length::Auto, Length::Auto))
425 }
426 $( #[cfg(feature = $cahgf)] Self::$cahg(_) => {
427 Some((Length::Auto, Length::Auto))
428 } )*
429
430 $( Self::$cf(_) )|* $( | Self::$cfnh(_) )* => {
432 Some((Length::Flex(1), Length::Flex(1)))
433 }
434
435 $( Self::$nd(_) )|* => None,
437 }
438 }
439 }
440 };
441}
442
443for_all_widget_variants!(impl_element_dimensions);
444
445impl ElementKind {
446 pub(crate) fn children(&self) -> SmallVec<[∈ 4]> {
453 match self {
454 Self::Group(g) => SmallVec::from_buf_and_len([g.child.as_ref(); 4], 1),
455 Self::ThemeProvider(tp) => SmallVec::from_buf_and_len([&tp.child; 4], 1),
456 Self::ContextProvider(cp) => SmallVec::from_buf_and_len([&cp.child; 4], 1),
457 Self::Portal(p) => SmallVec::from_buf_and_len([p.content.as_ref(); 4], 1),
458 Self::Popover(p) => {
459 smallvec::smallvec![p.trigger.as_ref(), p.content.as_ref()]
460 }
461 Self::EffectScope(e) => match &e.child {
462 Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
463 None => SmallVec::new(),
464 },
465 Self::Animated(a) => SmallVec::from_buf_and_len([a.child.as_ref(); 4], 1),
466 Self::DragSource(ds) => match &ds.child {
467 Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
468 None => SmallVec::new(),
469 },
470 Self::DropTarget(dt) => match &dt.child {
471 Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
472 None => SmallVec::new(),
473 },
474 Self::MouseRegion(m) => match &m.child {
475 Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
476 None => SmallVec::new(),
477 },
478 Self::Center(c) => match &c.child {
479 Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
480 None => SmallVec::new(),
481 },
482 Self::CenterPin(cp) => {
483 let mut out = SmallVec::new();
484 if let Some(c) = &cp.top {
485 out.push(c.as_ref());
486 }
487 if let Some(c) = &cp.center {
488 out.push(c.as_ref());
489 }
490 if let Some(c) = &cp.bottom {
491 out.push(c.as_ref());
492 }
493 out
494 }
495 Self::StatusBarLayout(layout) => smallvec::smallvec![
496 layout.left.as_ref(),
497 layout.center.as_ref(),
498 layout.right.as_ref()
499 ],
500 Self::Frame(f) => {
501 let mut out = SmallVec::new();
502 if let Some(c) = &f.header {
503 out.push(c.as_ref());
504 }
505 if let Some(c) = &f.child {
506 out.push(c.as_ref());
507 }
508 out
509 }
510 Self::Divider(d) => match &d.label {
511 Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
512 None => SmallVec::new(),
513 },
514 Self::ScrollView(sv) => sv.children.iter().collect(),
515 Self::PanView(pan) => match &pan.child {
516 Some(c) => SmallVec::from_buf_and_len([c.as_ref(); 4], 1),
517 None => SmallVec::new(),
518 },
519 Self::VStack(v) => v.children.iter().collect(),
520 Self::HStack(h) => h.children.iter().collect(),
521 Self::Grid(g) => g.items.iter().map(|i| &i.element).collect(),
522 Self::Flow(f) => f.children.iter().collect(),
523 Self::Canvas(c) => c.items.iter().map(|i| &i.element).collect(),
524 Self::ZStack(z) => z.children.iter().collect(),
525 Self::Splitter(sp) => sp.children.iter().collect(),
526 _ => SmallVec::new(),
527 }
528 }
529
530 pub(crate) fn children_mut(&mut self) -> SmallVec<[&mut Element; 4]> {
534 match self {
535 Self::Group(g) => smallvec::smallvec![g.child.as_mut()],
536 Self::ThemeProvider(tp) => smallvec::smallvec![&mut tp.child],
537 Self::ContextProvider(cp) => smallvec::smallvec![&mut cp.child],
538 Self::Portal(p) => smallvec::smallvec![p.content.as_mut()],
539 Self::Popover(p) => {
540 smallvec::smallvec![p.trigger.as_mut(), p.content.as_mut()]
541 }
542 Self::EffectScope(e) => match &mut e.child {
543 Some(c) => smallvec::smallvec![c.as_mut()],
544 None => SmallVec::new(),
545 },
546 Self::Animated(a) => smallvec::smallvec![a.child.as_mut()],
547 Self::DragSource(ds) => match &mut ds.child {
548 Some(c) => smallvec::smallvec![c.as_mut()],
549 None => SmallVec::new(),
550 },
551 Self::DropTarget(dt) => match &mut dt.child {
552 Some(c) => smallvec::smallvec![c.as_mut()],
553 None => SmallVec::new(),
554 },
555 Self::MouseRegion(m) => match &mut m.child {
556 Some(c) => smallvec::smallvec![c.as_mut()],
557 None => SmallVec::new(),
558 },
559 Self::Center(c) => match &mut c.child {
560 Some(c) => smallvec::smallvec![c.as_mut()],
561 None => SmallVec::new(),
562 },
563 Self::CenterPin(cp) => {
564 let mut out = SmallVec::new();
565 if let Some(c) = &mut cp.top {
566 out.push(c.as_mut());
567 }
568 if let Some(c) = &mut cp.center {
569 out.push(c.as_mut());
570 }
571 if let Some(c) = &mut cp.bottom {
572 out.push(c.as_mut());
573 }
574 out
575 }
576 Self::StatusBarLayout(layout) => smallvec::smallvec![
577 layout.left.as_mut(),
578 layout.center.as_mut(),
579 layout.right.as_mut()
580 ],
581 Self::Frame(f) => {
582 let mut out = SmallVec::new();
583 if let Some(c) = &mut f.header {
584 out.push(c.as_mut());
585 }
586 if let Some(c) = &mut f.child {
587 out.push(c.as_mut());
588 }
589 out
590 }
591 Self::Divider(d) => match &mut d.label {
592 Some(c) => smallvec::smallvec![c.as_mut()],
593 None => SmallVec::new(),
594 },
595 Self::ScrollView(sv) => sv.children.iter_mut().collect(),
596 Self::PanView(pan) => match &mut pan.child {
597 Some(c) => smallvec::smallvec![c.as_mut()],
598 None => SmallVec::new(),
599 },
600 Self::VStack(v) => v.children.iter_mut().collect(),
601 Self::HStack(h) => h.children.iter_mut().collect(),
602 Self::Grid(g) => g.items.iter_mut().map(|i| &mut i.element).collect(),
603 Self::Flow(f) => f.children.iter_mut().collect(),
604 Self::Canvas(c) => c.items.iter_mut().map(|i| &mut i.element).collect(),
605 Self::ZStack(z) => z.children.iter_mut().collect(),
606 Self::Splitter(sp) => sp.children.iter_mut().collect(),
607 _ => SmallVec::new(),
608 }
609 }
610}
611
612#[derive(Clone)]
614pub(crate) struct ThemeProviderElement {
615 pub theme: Theme,
616 pub child: Element,
617}
618
619#[derive(Clone)]
621pub(crate) struct ContextProviderElement {
622 pub type_id: TypeId,
623 pub value: Arc<dyn Any>,
624 pub equals: fn(&dyn Any, &dyn Any) -> bool,
625 pub generation: u64,
626 pub child: Element,
627}
628
629impl ContextProviderElement {
630 pub(crate) fn new<T>(value: T, child: Element) -> Self
631 where
632 T: Clone + PartialEq + 'static,
633 {
634 Self {
635 type_id: TypeId::of::<T>(),
636 value: Arc::new(value),
637 equals: context_value_equals::<T>,
638 generation: 1,
639 child,
640 }
641 }
642}
643
644fn context_value_equals<T: PartialEq + 'static>(a: &dyn Any, b: &dyn Any) -> bool {
645 a.downcast_ref::<T>() == b.downcast_ref::<T>()
646}
647
648#[derive(Clone)]
650pub(crate) struct Group {
651 pub scope: ScopeId,
652 pub child: Box<Element>,
653}
654
655impl crate::layout::hash::LayoutHash for Group {
656 fn layout_hash(
657 &self,
658 hasher: &mut impl std::hash::Hasher,
659 recurse: &dyn Fn(&Element) -> Option<u64>,
660 ) -> Option<()> {
661 use std::hash::Hash;
662 recurse(self.child.as_ref())?.hash(hasher);
663 Some(())
664 }
665}
666
667pub trait IntoElement: Into<Element> + Sized {
677 fn key(self, key: impl Into<Key>) -> Element {
680 self.into().key(key)
681 }
682
683 fn component_state_key(self, key: impl Into<Key>) -> Element {
686 self.into().component_state_key(key)
687 }
688
689 fn min_width(self, w: Length) -> Element {
691 self.into().min_width(w)
692 }
693
694 fn min_height(self, h: Length) -> Element {
696 self.into().min_height(h)
697 }
698
699 fn max_width(self, w: Length) -> Element {
701 self.into().max_width(w)
702 }
703
704 fn max_height(self, h: Length) -> Element {
706 self.into().max_height(h)
707 }
708
709 fn reflows(self, reflows: bool) -> Element {
711 self.into().reflows(reflows)
712 }
713
714 fn shrink_priority(self, priority: ShrinkPriority) -> Element {
716 self.into().shrink_priority(priority)
717 }
718}
719
720impl<T> IntoElement for T where T: Into<Element> + Sized {}
721
722impl Default for Element {
723 fn default() -> Self {
728 Element::new(ElementKind::Spacer(Spacer::default()))
729 }
730}
731
732#[cfg(test)]
733mod tests {
734 use super::{Element, ElementKind, Group, MeasureCacheEntry};
735 use crate::callback::ScopeId;
736 use crate::core::memo::Memo;
737 use crate::overlay::{DismissPolicy, OverlayLayer, OverlayPlacement, PointerCapture, Portal};
738 use crate::style::Theme;
739 use crate::widgets::{ContextProvider, Divider, Grid, Splitter, Text, ThemeProvider};
740
741 const TARGET_SCOPE: ScopeId = ScopeId(42);
742
743 fn scoped_group(label: &str) -> Element {
744 Element::new(ElementKind::Group(Group {
745 scope: TARGET_SCOPE,
746 child: Box::new(Text::new(label).into()),
747 }))
748 }
749
750 fn replacement() -> Element {
751 Text::new("new").into()
752 }
753
754 fn mark_caches(element: &Element) {
755 element.layout_hash_cache.set(Some(99));
756 element.measure_cache.set([
757 Some(MeasureCacheEntry {
758 max_w: Some(1),
759 max_h: Some(1),
760 size: (1, 1),
761 }),
762 None,
763 ]);
764 element.split_wrap_probe_cache.set(Some(true));
765 }
766
767 fn text_contents(element: &Element, out: &mut Vec<String>) {
768 if let ElementKind::Text(text) = &element.kind {
769 out.push(text.plain_content());
770 }
771 for child in element.kind.children() {
772 text_contents(child, out);
773 }
774 }
775
776 fn group_for_scope(element: &Element, scope: ScopeId) -> Option<&Element> {
777 if let ElementKind::Group(group) = &element.kind
778 && group.scope == scope
779 {
780 return Some(element);
781 }
782 element
783 .kind
784 .children()
785 .into_iter()
786 .find_map(|child| group_for_scope(child, scope))
787 }
788
789 fn assert_replaces_through_wrapper(mut root: Element) {
790 mark_caches(&root);
791 let group = group_for_scope(&root, TARGET_SCOPE).expect("target group before replace");
792 mark_caches(group);
793
794 let mut replacement = Some(replacement());
795 assert!(
796 root.replace_group_child_by_scope(TARGET_SCOPE, &mut replacement),
797 "scope replacement should succeed"
798 );
799 assert!(replacement.is_none());
800
801 let mut texts = Vec::new();
802 text_contents(&root, &mut texts);
803 assert!(texts.contains(&"new".to_string()), "texts: {texts:?}");
804 assert!(!texts.contains(&"old".to_string()), "texts: {texts:?}");
805 assert_eq!(root.layout_hash_cache.get(), None);
806 assert_eq!(root.measure_cache.get(), [None, None]);
807 assert_eq!(root.split_wrap_probe_cache.get(), None);
808
809 let group = group_for_scope(&root, TARGET_SCOPE).expect("target group after replace");
810 assert_eq!(group.layout_hash_cache.get(), None);
811 assert_eq!(group.measure_cache.get(), [None, None]);
812 assert_eq!(group.split_wrap_probe_cache.get(), None);
813 }
814
815 #[test]
816 fn replace_group_child_traverses_children_mut_wrappers() {
817 let portal = Element::new(ElementKind::Portal(Portal {
818 layer: OverlayLayer::Modal,
819 content: Box::new(scoped_group("old")),
820 placement: OverlayPlacement::Center,
821 dismiss_policy: DismissPolicy::None,
822 on_close: None,
823 backdrop: None,
824 captures_focus: false,
825 captures_pointer: PointerCapture::None,
826 }));
827
828 let cases: Vec<(&str, Element)> = vec![
829 ("grid", Grid::new().child(scoped_group("old")).into()),
830 (
831 "splitter",
832 Splitter::vertical().child(scoped_group("old")).into(),
833 ),
834 (
835 "divider_label",
836 Divider::horizontal().label(scoped_group("old")).into(),
837 ),
838 (
839 "theme_provider",
840 ThemeProvider::new(Theme::default())
841 .child(scoped_group("old"))
842 .into(),
843 ),
844 (
845 "context_provider",
846 ContextProvider::new(7u32).child(scoped_group("old")).into(),
847 ),
848 ("portal", portal),
849 ];
850
851 for (_name, root) in cases {
852 assert_replaces_through_wrapper(root);
853 }
854 }
855
856 #[test]
857 fn replace_group_child_does_not_expand_unresolved_memo_builders() {
858 let mut root = Memo::with_call_site(1, 1).build(|| scoped_group("old"));
859 let mut replacement = Some(replacement());
860 assert!(!root.replace_group_child_by_scope(TARGET_SCOPE, &mut replacement));
861
862 let mut texts = Vec::new();
863 text_contents(
864 &replacement.expect("unexpanded memo keeps replacement"),
865 &mut texts,
866 );
867 assert_eq!(texts, ["new".to_string()]);
868 }
869}