1use std::hash::{Hash, Hasher};
14
15use crate::widget::Widget;
16
17pub struct Keyed<Msg = crate::callback::Callback> {
19 key: u64,
20 inner: Box<dyn Widget<Msg>>,
21}
22
23impl<Msg> Keyed<Msg> {
24 pub fn new(key: impl Hash, inner: impl Widget<Msg> + 'static) -> Self {
26 let mut hasher = std::collections::hash_map::DefaultHasher::new();
27 key.hash(&mut hasher);
28 Self {
29 key: hasher.finish(),
30 inner: Box::new(inner),
31 }
32 }
33
34 pub fn wrap(index: usize, inner: impl Widget<Msg> + 'static) -> Self {
42 match inner.key() {
43 Some(key) => Self {
44 key,
45 inner: Box::new(inner),
46 },
47 None => Self::new(index, inner),
48 }
49 }
50
51 fn restyle(&self, base: frus_layout::Style) -> frus_layout::Style {
53 base
54 }
55}
56
57crate::transparent::forward_transparent!(Keyed {
58 fn key(&self) -> Option<u64> {
60 Some(self.key)
61 }
62
63 fn positioned(&self) -> Option<crate::positioned::Positioning> {
65 self.inner.positioned()
66 }
67
68 fn theme_override(
70 &self,
71 inherited: &crate::theme::Theme,
72 ) -> Option<Box<crate::theme::Theme>> {
73 self.inner.theme_override(inherited)
74 }
75
76 fn media_override(&self, inherited: crate::MediaQuery) -> Option<crate::MediaQuery> {
78 self.inner.media_override(inherited)
79 }
80 fn scaffold_override(&self) -> Option<crate::ScaffoldInfo> {
81 self.inner.scaffold_override()
82 }
83 fn autofill_group(&self) -> bool {
85 self.inner.autofill_group()
86 }
87});
88
89#[cfg(test)]
90mod tests {
91 use super::*;
92 use crate::widget::ReorderAxis;
93 use crate::{Container, Text};
94
95 struct Card;
98
99 impl Widget<()> for Card {
100 fn style(&self) -> frus_layout::Style {
101 frus_layout::Style::default()
102 }
103 fn children(&self) -> &[Box<dyn Widget<()>>] {
104 &[]
105 }
106 fn paint(
107 &self,
108 _bounds: frus_core::Rect,
109 _status: crate::interaction::Status,
110 _theme: &crate::theme::Theme,
111 _scene: &mut frus_core::Scene,
112 ) {
113 }
114 fn on_click(&self) -> Option<()> {
115 None
116 }
117 fn reorder_axis(&self) -> ReorderAxis {
118 ReorderAxis::Vertical
119 }
120 fn reorder_draggable(&self) -> bool {
121 false
122 }
123 }
124
125 #[test]
130 fn a_keyed_reorderable_keeps_its_axis_and_its_refusal_to_be_lifted() {
131 let keyed = Keyed::new(1u64, Card);
132 assert_eq!(Widget::<()>::reorder_axis(&keyed), ReorderAxis::Vertical);
133 assert!(!Widget::<()>::reorder_draggable(&keyed));
134 }
135
136 #[test]
141 fn structural_questions_pass_through() {
142 let stack = crate::Stack::<()>::new()
143 .width(100.0)
144 .height(50.0)
145 .layer(Container::new())
146 .layer(Container::new());
147 assert!(Widget::<()>::stack(&stack));
148 assert!(
149 Widget::<()>::stack(&Keyed::new(1u64, stack)),
150 "a keyed stack is still a stack"
151 );
152
153 let spinner = crate::CircularProgressIndicator::new();
154 assert!(Widget::<()>::continuous(&spinner));
155 assert!(Widget::<()>::continuous(&Keyed::new(2u64, spinner)));
156 }
157
158 #[test]
159 fn reports_key_and_delegates() {
160 let inner = Container::<()>::new().width(40.0).child(Text::new("x"));
161 let keyed = Keyed::new(99u64, inner);
162 assert!(Widget::<()>::key(&keyed).is_some());
164 assert_eq!(Widget::<()>::children(&keyed).len(), 1);
166 }
167
168 #[test]
169 fn same_key_same_hash() {
170 let a: Keyed<()> = Keyed::new("todo-3", Container::new());
171 let b: Keyed<()> = Keyed::new("todo-3", Container::new());
172 assert_eq!(Widget::<()>::key(&a), Widget::<()>::key(&b));
173 }
174
175 #[test]
178 fn wrapping_keeps_a_key_the_child_already_had() {
179 let named: Keyed<()> = Keyed::new("row-7", Text::new("seven"));
180 let key = Widget::<()>::key(&named).expect("a key of its own");
181 let wrapped = Keyed::wrap(3, named);
182 assert_eq!(
183 Widget::<()>::key(&wrapped),
184 Some(key),
185 "the position did not overwrite the name"
186 );
187
188 let plain: Keyed<()> = Keyed::wrap(3, Text::new("nobody named me"));
189 let other: Keyed<()> = Keyed::wrap(4, Text::new("nobody named me"));
190 assert_ne!(
191 Widget::<()>::key(&plain),
192 Widget::<()>::key(&other),
193 "and an unnamed child is at least distinguished by where it is"
194 );
195 }
196}