1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
//! [`Keyed`]: a **transparent** wrapper that gives a widget a **stable identity**,
//! through a key, independent of its position among its siblings.
//!
//! Without a key, identity is positional: removing an item from the middle of a list
//! shifts the identity of everything after it, and their retained state — hover, focus,
//! caret, animations, the leaving fade — jumps. Wrapping each item in a `Keyed`, keyed
//! on its stable domain id, fixes that.
//!
//! It is the reference's `KeyedSubtree` — a key around a subtree and nothing else — and
//! [`Keyed::wrap`] is that widget's own constructor for the case where somebody else's
//! children are being keyed in bulk.
use std::hash::{Hash, Hasher};
use crate::widget::Widget;
/// Wraps a widget in a stable identity key, delegating everything else.
pub struct Keyed<Msg = crate::callback::Callback> {
key: u64,
inner: Box<dyn Widget<Msg>>,
}
impl<Msg> Keyed<Msg> {
/// Wraps `inner` with the key `key`, which may be any hashable type.
pub fn new(key: impl Hash, inner: impl Widget<Msg> + 'static) -> Self {
let mut hasher = std::collections::hash_map::DefaultHasher::new();
key.hash(&mut hasher);
Self {
key: hasher.finish(),
inner: Box::new(inner),
}
}
/// Keeps the key `inner` **already has**, and falls back to `index` when it has
/// none — the reference's `KeyedSubtree.wrap`.
///
/// It is for keying somebody else's children: a helper that wraps every item of a
/// list it was handed cannot use [`Keyed::new`], because that would overwrite the
/// keys the caller set on the items they cared about. A position is a poor identity,
/// and it is still better than none for the items nobody named.
pub fn wrap(index: usize, inner: impl Widget<Msg> + 'static) -> Self {
match inner.key() {
Some(key) => Self {
key,
inner: Box::new(inner),
},
None => Self::new(index, inner),
}
}
/// A key is an identity, not a box: the child's own, unchanged.
fn restyle(&self, base: frus_layout::Style) -> frus_layout::Style {
base
}
}
crate::transparent::forward_transparent!(Keyed {
/// The one thing a `Keyed` does not delegate: the identity it exists to give.
fn key(&self) -> Option<u64> {
Some(self.key)
}
/// Forwarded: a key is not a place — `Keyed(Positioned(…))` must keep its pins.
fn positioned(&self) -> Option<crate::positioned::Positioning> {
self.inner.positioned()
}
/// And the one it does — a key says nothing about a theme.
fn theme_override(
&self,
inherited: &crate::theme::Theme,
) -> Option<Box<crate::theme::Theme>> {
self.inner.theme_override(inherited)
}
/// Forwarded: a wrapper is its child, and a scoped surface is the child's to impose.
fn media_override(&self, inherited: crate::MediaQuery) -> Option<crate::MediaQuery> {
self.inner.media_override(inherited)
}
fn scaffold_override(&self) -> Option<crate::ScaffoldInfo> {
self.inner.scaffold_override()
}
fn scroll_behavior_override(&self) -> Option<crate::ScrollBehavior> {
self.inner.scroll_behavior_override()
}
/// Forwarded: a key is not a form, and a keyed form is still one (milestone 512).
fn autofill_group(&self) -> bool {
self.inner.autofill_group()
}
/// Forwarded: the area's bar is the area's, whatever wraps it.
fn area_toolbar(
&self,
context: crate::ToolbarContext,
) -> Option<Option<&dyn crate::widget::Widget<Msg>>> {
self.inner.area_toolbar(context)
}
});
#[cfg(test)]
mod tests {
use super::*;
use crate::widget::ReorderAxis;
use crate::{Container, Text};
/// A reorderable that is **not** the default in either respect: dragged vertically,
/// and a drop target only.
struct Card;
impl Widget<()> for Card {
fn style(&self) -> frus_layout::Style {
frus_layout::Style::default()
}
fn children(&self) -> &[Box<dyn Widget<()>>] {
&[]
}
fn paint(
&self,
_bounds: frus_core::Rect,
_status: crate::interaction::Status,
_theme: &crate::theme::Theme,
_scene: &mut frus_core::Scene,
) {
}
fn on_click(&self) -> Option<()> {
None
}
fn reorder_axis(&self) -> ReorderAxis {
ReorderAxis::Vertical
}
fn reorder_draggable(&self) -> bool {
false
}
}
/// Found by the forwarding test in [`crate::transparent`], not by a device: the
/// hand-written version of this wrapper forwarded `reorder_index` and `on_reorder`
/// and stopped there, so a keyed card in a board dragged along the wrong axis and a
/// drop-only slot could be lifted.
#[test]
fn a_keyed_reorderable_keeps_its_axis_and_its_refusal_to_be_lifted() {
let keyed = Keyed::new(1u64, Card);
assert_eq!(Widget::<()>::reorder_axis(&keyed), ReorderAxis::Vertical);
assert!(!Widget::<()>::reorder_draggable(&keyed));
}
/// The structural questions decide **how the content is laid out**, so a
/// transparent wrapper must pass them through. Answering them for itself made a
/// keyed stack lay its layers out in flow instead of on top of one another — found
/// on a device, wrapping a swipeable row in `keyed(...)`.
#[test]
fn structural_questions_pass_through() {
let stack = crate::Stack::<()>::new()
.width(100.0)
.height(50.0)
.layer(Container::new())
.layer(Container::new());
assert!(Widget::<()>::stack(&stack));
assert!(
Widget::<()>::stack(&Keyed::new(1u64, stack)),
"a keyed stack is still a stack"
);
let spinner = crate::CircularProgressIndicator::new();
assert!(Widget::<()>::continuous(&spinner));
assert!(Widget::<()>::continuous(&Keyed::new(2u64, spinner)));
}
#[test]
fn reports_key_and_delegates() {
let inner = Container::<()>::new().width(40.0).child(Text::new("x"));
let keyed = Keyed::new(99u64, inner);
// It returns a key.
assert!(Widget::<()>::key(&keyed).is_some());
// It delegates the children; the Container has one.
assert_eq!(Widget::<()>::children(&keyed).len(), 1);
}
#[test]
fn same_key_same_hash() {
let a: Keyed<()> = Keyed::new("todo-3", Container::new());
let b: Keyed<()> = Keyed::new("todo-3", Container::new());
assert_eq!(Widget::<()>::key(&a), Widget::<()>::key(&b));
}
/// Wrapping in bulk keeps the keys somebody already chose, and only names the ones
/// nobody did.
#[test]
fn wrapping_keeps_a_key_the_child_already_had() {
let named: Keyed<()> = Keyed::new("row-7", Text::new("seven"));
let key = Widget::<()>::key(&named).expect("a key of its own");
let wrapped = Keyed::wrap(3, named);
assert_eq!(
Widget::<()>::key(&wrapped),
Some(key),
"the position did not overwrite the name"
);
let plain: Keyed<()> = Keyed::wrap(3, Text::new("nobody named me"));
let other: Keyed<()> = Keyed::wrap(4, Text::new("nobody named me"));
assert_ne!(
Widget::<()>::key(&plain),
Widget::<()>::key(&other),
"and an unnamed child is at least distinguished by where it is"
);
}
}