use frust_core::{
AnyView, BoxConstraints, BuildCtx, ChangeFlags, ChildPod, EventCtx, EventResult, InputEvent,
LayoutCtx, PaintCtx, PaintScene, SemanticsCtx, View, ViewSeq, Widget, any,
};
use kurbo::{Point, Rect, Size};
use crate::ChildKey;
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum Axis {
Horizontal,
Vertical,
}
impl Axis {
fn main_of(self, size: Size) -> f64 {
match self {
Axis::Horizontal => size.width,
Axis::Vertical => size.height,
}
}
fn cross_of(self, size: Size) -> f64 {
match self {
Axis::Horizontal => size.height,
Axis::Vertical => size.width,
}
}
fn size(self, main: f64, cross: f64) -> Size {
match self {
Axis::Horizontal => Size::new(main, cross),
Axis::Vertical => Size::new(cross, main),
}
}
fn point(self, main: f64, cross: f64) -> Point {
match self {
Axis::Horizontal => Point::new(main, cross),
Axis::Vertical => Point::new(cross, main),
}
}
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum CrossAxisAlignment {
Start,
Center,
Stretch,
}
#[derive(Clone, Copy, Debug, PartialEq, Eq)]
pub enum MainAxisAlignment {
Start,
}
pub struct FlexChild<State: 'static> {
view: AnyView<State>,
flex: u32,
key: Option<ChildKey>,
}
pub fn flexible<State: 'static, V: View<State>>(flex: u32, view: V) -> FlexChild<State> {
FlexChild {
view: any(view),
flex,
key: None,
}
}
pub fn inflexible<State: 'static, V: View<State>>(view: V) -> FlexChild<State> {
FlexChild {
view: any(view),
flex: 0,
key: None,
}
}
pub fn keyed<State: 'static, V: View<State>>(
key: impl Into<ChildKey>,
view: V,
) -> FlexChild<State> {
FlexChild {
view: any(view),
flex: 0,
key: Some(key.into()),
}
}
pub struct FlexView<State: 'static> {
direction: Axis,
cross: CrossAxisAlignment,
main: MainAxisAlignment,
children: Vec<FlexChild<State>>,
}
impl<State: 'static> FlexView<State> {
pub fn new(direction: Axis, children: Vec<FlexChild<State>>) -> Self {
Self {
direction,
cross: CrossAxisAlignment::Start,
main: MainAxisAlignment::Start,
children,
}
}
pub fn child<V: View<State>>(mut self, view: V) -> Self {
self.children.push(inflexible(view));
self
}
pub fn flex<V: View<State>>(mut self, flex: u32, view: V) -> Self {
self.children.push(flexible(flex, view));
self
}
pub fn keyed<V: View<State>>(mut self, key: impl Into<ChildKey>, view: V) -> Self {
self.children.push(keyed(key, view));
self
}
pub fn children<M>(mut self, children: impl ViewSeq<State, M>) -> Self {
let mut erased = Vec::new();
children.extend_views(&mut erased);
self.children
.extend(erased.into_iter().map(|view| FlexChild {
view,
flex: 0,
key: None,
}));
self
}
pub fn push(mut self, child: FlexChild<State>) -> Self {
self.children.push(child);
self
}
pub fn when(self, cond: bool, f: impl FnOnce(Self) -> Self) -> Self {
if cond { f(self) } else { self }
}
pub fn when_some<T>(self, opt: Option<T>, f: impl FnOnce(Self, T) -> Self) -> Self {
match opt {
Some(value) => f(self, value),
None => self,
}
}
pub fn cross_axis(mut self, cross: CrossAxisAlignment) -> Self {
self.cross = cross;
self
}
pub fn main_axis(mut self, main: MainAxisAlignment) -> Self {
self.main = main;
self
}
}
pub fn column<State: 'static>() -> FlexView<State> {
FlexView::new(Axis::Vertical, Vec::new())
}
pub fn row<State: 'static>() -> FlexView<State> {
FlexView::new(Axis::Horizontal, Vec::new())
}
#[allow(non_snake_case)]
pub fn Row<State: 'static, M>(children: impl ViewSeq<State, M>) -> FlexView<State> {
FlexView::new(Axis::Horizontal, Vec::new()).children(children)
}
#[allow(non_snake_case)]
pub fn Column<State: 'static, M>(children: impl ViewSeq<State, M>) -> FlexView<State> {
FlexView::new(Axis::Vertical, Vec::new()).children(children)
}
pub struct FlexWidget {
direction: Axis,
cross: CrossAxisAlignment,
main: MainAxisAlignment,
children: Vec<ChildPod>,
flex: Vec<u32>,
}
fn child_constraints(
axis: Axis,
main_min: f64,
main_max: f64,
cross_bound: f64,
stretch: bool,
cross_max: f64,
) -> BoxConstraints {
let cross_min = if stretch { cross_bound } else { 0.0 };
let cross_hi = if stretch { cross_bound } else { cross_max };
BoxConstraints::new(
axis.size(main_min, cross_min),
axis.size(main_max, cross_hi),
)
}
impl<State: 'static> View<State> for FlexView<State> {
type Element = FlexWidget;
fn build(&self, ctx: &mut BuildCtx<'_>) -> FlexWidget {
let mut children = Vec::with_capacity(self.children.len());
let mut flex = Vec::with_capacity(self.children.len());
for child in &self.children {
children.push(crate::authoring::build_child(&child.view, ctx));
flex.push(child.flex);
}
FlexWidget {
direction: self.direction,
cross: self.cross,
main: self.main,
children,
flex,
}
}
fn rebuild(
&self,
prev: &Self,
element: &mut FlexWidget,
ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
let mut flags = ChangeFlags::NONE;
if prev.direction != self.direction || prev.cross != self.cross || prev.main != self.main {
element.direction = self.direction;
element.cross = self.cross;
element.main = self.main;
flags |= ChangeFlags::LAYOUT;
}
flags |= crate::authoring::rebuild_children(
&prev.children,
&self.children,
&mut element.children,
ctx,
|child| &child.view,
|child| child.key,
);
let new_flex: Vec<u32> = self.children.iter().map(|child| child.flex).collect();
if new_flex != element.flex {
element.flex = new_flex;
flags |= ChangeFlags::LAYOUT;
}
flags
}
fn teardown(&self, element: &mut FlexWidget, ctx: &mut BuildCtx<'_>) {
for (child, pod) in self.children.iter().zip(element.children.iter_mut()) {
crate::authoring::teardown_child(&child.view, pod, ctx);
}
}
}
impl Widget for FlexWidget {
fn layout(&mut self, ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
let axis = self.direction;
let max_main = axis.main_of(bc.max());
let max_cross = axis.cross_of(bc.max());
let stretch = self.cross == CrossAxisAlignment::Stretch;
let cross_bound = if max_cross.is_finite() {
max_cross
} else {
0.0
};
let mut allocated_main = 0.0f64;
let mut total_flex = 0u32;
let mut max_child_cross = 0.0f64;
for (i, pod) in self.children.iter_mut().enumerate() {
if self.flex[i] == 0 {
let cbc =
child_constraints(axis, 0.0, f64::INFINITY, cross_bound, stretch, max_cross);
let size = pod.layout_child(ctx, &cbc);
allocated_main += axis.main_of(size);
max_child_cross = max_child_cross.max(axis.cross_of(size));
} else {
total_flex += self.flex[i];
}
}
let free = if max_main.is_finite() {
(max_main - allocated_main).max(0.0)
} else {
0.0
};
if total_flex > 0 {
for (i, pod) in self.children.iter_mut().enumerate() {
if self.flex[i] > 0 {
let share = free * (self.flex[i] as f64) / (total_flex as f64);
let cbc =
child_constraints(axis, share, share, cross_bound, stretch, max_cross);
let size = pod.layout_child(ctx, &cbc);
max_child_cross = max_child_cross.max(axis.cross_of(size));
}
}
}
let main_size = if total_flex > 0 && max_main.is_finite() {
max_main
} else {
allocated_main
};
let cross_size = if stretch && max_cross.is_finite() {
max_cross
} else {
max_child_cross
};
let leading = match self.main {
MainAxisAlignment::Start => 0.0,
};
let mut main_pos = leading;
for pod in &mut self.children {
let child_cross = axis.cross_of(pod.size());
let cross_pos = match self.cross {
CrossAxisAlignment::Start | CrossAxisAlignment::Stretch => 0.0,
CrossAxisAlignment::Center => (cross_size - child_cross) / 2.0,
};
pod.set_origin(axis.point(main_pos, cross_pos));
main_pos += axis.main_of(pod.size());
}
bc.constrain(axis.size(main_size, cross_size))
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
let cull = ctx
.visible_rect()
.map(|vr| vr.inflate(vr.width(), vr.height()));
let hero_in_flight = ctx.hero_active();
for pod in &mut self.children {
if let Some(warm) = cull {
let exempt = hero_in_flight || pod.is_focused();
if !exempt {
let child_abs =
Rect::from_origin_size(ctx.origin() + pod.origin().to_vec2(), pod.size());
if !child_abs.overlaps(warm) {
continue;
}
}
}
pod.paint_child(ctx, scene);
}
}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
crate::authoring::route_event(&mut self.children, ctx, event)
}
fn semantics(&self, ctx: &mut SemanticsCtx) {
for pod in &self.children {
pod.semantics_child(ctx);
}
}
crate::authoring::visit_children!(children);
}
#[cfg(test)]
mod tests {
use super::*;
use crate::test_support::{RecordingScene, leaf};
fn ctx(counter: &mut u64) -> BuildCtx<'_> {
BuildCtx::new(counter)
}
fn build<S: 'static>(view: &FlexView<S>) -> FlexWidget {
let mut counter = 0u64;
view.build(&mut ctx(&mut counter))
}
#[test]
fn distributes_free_space_by_flex_factors() {
let view: FlexView<()> = FlexView::new(
Axis::Horizontal,
vec![
flexible(2, leaf(1000.0, 20.0)),
flexible(1, leaf(1000.0, 20.0)),
],
);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(300.0, 100.0)));
assert_eq!(w.children[0].size().width, 200.0);
assert_eq!(w.children[1].size().width, 100.0);
assert_eq!(w.children[0].origin().x, 0.0);
assert_eq!(w.children[1].origin().x, 200.0);
assert_eq!(size.width, 300.0);
}
#[test]
fn mixes_inflexible_and_flexible() {
let view: FlexView<()> = FlexView::new(
Axis::Horizontal,
vec![
inflexible(leaf(50.0, 20.0)),
flexible(1, leaf(1000.0, 20.0)),
],
);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(200.0, 100.0)));
assert_eq!(w.children[0].size().width, 50.0);
assert_eq!(w.children[1].size().width, 150.0);
assert_eq!(w.children[0].origin().x, 0.0);
assert_eq!(w.children[1].origin().x, 50.0);
assert_eq!(size.width, 200.0);
}
#[test]
fn shrink_wraps_main_axis_without_flexible_children() {
let view: FlexView<()> = row()
.child(leaf(40.0, 10.0).into_any())
.child(leaf(20.0, 10.0).into_any());
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(300.0, 100.0)));
assert_eq!(size.width, 60.0);
assert_eq!(w.children[1].origin().x, 40.0);
}
#[test]
fn cross_axis_stretch_tightens_children() {
let view: FlexView<()> = row()
.child(leaf(30.0, 10.0).into_any())
.cross_axis(CrossAxisAlignment::Stretch);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(200.0, 80.0)));
assert_eq!(w.children[0].size().height, 80.0);
assert_eq!(size.height, 80.0);
}
#[test]
fn cross_axis_start_packs_at_zero() {
let view: FlexView<()> = row()
.child(leaf(10.0, 40.0).into_any())
.child(leaf(10.0, 20.0).into_any())
.cross_axis(CrossAxisAlignment::Start);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(200.0, 100.0)));
assert_eq!(size.height, 40.0);
assert_eq!(w.children[1].origin().y, 0.0);
}
#[test]
fn cross_axis_center_centers_shorter_children() {
let view: FlexView<()> = row()
.child(leaf(10.0, 40.0).into_any())
.child(leaf(10.0, 20.0).into_any())
.cross_axis(CrossAxisAlignment::Center);
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
w.layout(&mut lctx, &BoxConstraints::loose(Size::new(200.0, 100.0)));
assert_eq!(w.children[1].origin().y, 10.0);
}
#[test]
fn column_lays_out_along_vertical_axis() {
let view: FlexView<()> = column()
.child(leaf(30.0, 15.0).into_any())
.child(leaf(30.0, 25.0).into_any());
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
let size = w.layout(&mut lctx, &BoxConstraints::loose(Size::new(100.0, 300.0)));
assert_eq!(size.height, 40.0); assert_eq!(w.children[0].origin().y, 0.0);
assert_eq!(w.children[1].origin().y, 15.0);
}
#[test]
fn paints_children_in_order() {
let view: FlexView<()> = row()
.child(leaf(20.0, 20.0).into_any())
.child(leaf(20.0, 20.0).into_any());
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
w.layout(&mut lctx, &BoxConstraints::loose(Size::new(200.0, 200.0)));
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(200.0, 200.0));
w.paint(&mut pctx, &mut scene);
assert_eq!(scene.rects[0].0, Point::new(0.0, 0.0));
assert_eq!(scene.rects[1].0, Point::new(20.0, 0.0));
}
fn culling_column() -> FlexWidget {
let view: FlexView<()> = column()
.child(leaf(100.0, 100.0).into_any())
.child(leaf(100.0, 100.0).into_any())
.child(leaf(100.0, 100.0).into_any())
.child(leaf(100.0, 100.0).into_any())
.child(leaf(100.0, 100.0).into_any());
let mut w = build(&view);
let mut lctx = LayoutCtx::new();
w.layout(&mut lctx, &BoxConstraints::loose(Size::new(100.0, 500.0)));
w
}
#[test]
fn no_visible_rect_paints_every_child() {
let mut w = culling_column();
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(100.0, 500.0));
w.paint(&mut pctx, &mut scene);
assert_eq!(scene.rects.len(), 5, "no cull → all five rows painted");
}
#[test]
fn culls_children_fully_outside_visible_rect_plus_margin() {
let mut w = culling_column();
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(100.0, 500.0));
pctx.constrain_visible_rect(Rect::from_origin_size(Point::ZERO, Size::new(100.0, 100.0)));
w.paint(&mut pctx, &mut scene);
assert_eq!(scene.rects.len(), 3, "two below-the-warm-band rows culled");
assert_eq!(scene.rects[0].0, Point::new(0.0, 0.0));
assert_eq!(scene.rects[1].0, Point::new(0.0, 100.0));
assert_eq!(scene.rects[2].0, Point::new(0.0, 200.0));
}
#[test]
fn margin_boundary_row_just_past_the_warm_band_is_culled() {
let mut w = culling_column();
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(100.0, 500.0));
pctx.constrain_visible_rect(Rect::from_origin_size(Point::ZERO, Size::new(100.0, 50.0)));
w.paint(&mut pctx, &mut scene);
assert_eq!(scene.rects.len(), 2, "only the top band rows survive");
assert_eq!(scene.rects[0].0, Point::new(0.0, 0.0));
assert_eq!(scene.rects[1].0, Point::new(0.0, 100.0));
}
#[test]
fn culled_child_still_receives_events_at_its_layout_geometry() {
let mut counter = 0u64;
let view: FlexView<Vec<u32>> = column()
.child(captor(0))
.child(captor(1))
.child(captor(2))
.child(captor(3))
.child(captor(4))
.child(captor(5))
.child(captor(6))
.child(captor(7));
let mut w = view.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(ROW_W, ROW_H * 8.0));
pctx.constrain_visible_rect(Rect::from_origin_size(Point::ZERO, Size::new(ROW_W, ROW_H)));
w.paint(&mut pctx, &mut scene);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(7)));
assert!(
w.children[7].is_active(),
"a culled row still captures on Down"
);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(7)));
assert_eq!(log, vec![7], "a culled-but-laid-out row still fires on Up");
}
struct ImeLeaf {
value: String,
painted: Rc<Cell<u32>>,
}
struct ImeLeafWidget {
value: String,
painted: Rc<Cell<u32>>,
}
impl View<()> for ImeLeaf {
type Element = ImeLeafWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> ImeLeafWidget {
ImeLeafWidget {
value: self.value.clone(),
painted: self.painted.clone(),
}
}
fn rebuild(
&self,
_prev: &Self,
element: &mut ImeLeafWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
element.value = self.value.clone();
ChangeFlags::NONE
}
}
impl Widget for ImeLeafWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(Size::new(ROW_W, ROW_H))
}
fn paint(&mut self, ctx: &mut PaintCtx, scene: &mut dyn PaintScene) {
use frust_core::{EditingState, ImeState};
self.painted.set(self.painted.get() + 1);
scene.fill_rect(ctx.origin(), ctx.size(), peniko::Color::BLACK);
ctx.publish_ime_state(ImeState {
active: true,
editing: EditingState {
text: self.value.clone(),
selection_base: -1,
selection_extent: -1,
composing_base: -1,
composing_extent: -1,
},
caret: None,
content_type: Default::default(),
suppress_soft_keyboard: false,
});
}
}
fn ime_column() -> (FlexWidget, [Rc<Cell<u32>>; 5]) {
let counts: [Rc<Cell<u32>>; 5] = std::array::from_fn(|_| Rc::new(Cell::new(0)));
let view: FlexView<()> = Column(
(0..5)
.map(|i| {
any(ImeLeaf {
value: format!("row{i}"),
painted: counts[i].clone(),
})
})
.collect::<Vec<_>>(),
);
let mut counter = 0u64;
let mut w = view.build(&mut ctx(&mut counter));
layout_column(&mut w);
(w, counts)
}
fn top_row_rect() -> Rect {
Rect::from_origin_size(Point::ZERO, Size::new(ROW_W, ROW_H))
}
#[test]
fn focused_child_beyond_warm_band_still_paints_and_republishes_ime() {
let (mut w, counts) = ime_column();
w.children[4].set_focused(true);
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(ROW_W, ROW_H * 5.0));
pctx.constrain_visible_rect(top_row_rect());
w.paint(&mut pctx, &mut scene);
assert_eq!(counts[0].get(), 1, "warm row 0 paints");
assert_eq!(counts[1].get(), 1, "warm row 1 paints");
assert_eq!(counts[2].get(), 1, "warm boundary row 2 paints");
assert_eq!(
counts[3].get(),
0,
"unfocused offscreen row 3 stays culled (suppression intact)"
);
assert_eq!(
counts[4].get(),
1,
"focused offscreen row 4 is exempt from culling and paints"
);
let ime = pctx
.take_ime_state()
.expect("focused row republished its IME");
assert_eq!(ime.editing.text, "row4");
}
#[test]
fn focused_cull_exemption_republishes_a_rebuild_mutation_immediately() {
let far = Rect::from_origin_size(Point::new(0.0, 10_000.0), Size::new(ROW_W, ROW_H));
let mut counter = 0u64;
let prev: FlexView<()> = column().child(ImeLeaf {
value: "v1".to_string(),
painted: Rc::new(Cell::new(0)),
});
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let next: FlexView<()> = column().child(ImeLeaf {
value: "v2".to_string(),
painted: Rc::new(Cell::new(0)),
});
next.rebuild(&prev, &mut w, &mut ctx(&mut counter));
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(ROW_W, ROW_H * 5.0));
pctx.constrain_visible_rect(far);
w.paint(&mut pctx, &mut scene);
assert!(
pctx.take_ime_state().is_none(),
"an unfocused, culled field never republishes — its IME goes stale"
);
let mut counter = 0u64;
let prev: FlexView<()> = column().child(ImeLeaf {
value: "v1".to_string(),
painted: Rc::new(Cell::new(0)),
});
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
w.children[0].set_focused(true);
let next: FlexView<()> = column().child(ImeLeaf {
value: "v2".to_string(),
painted: Rc::new(Cell::new(0)),
});
next.rebuild(&prev, &mut w, &mut ctx(&mut counter));
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(ROW_W, ROW_H * 5.0));
pctx.constrain_visible_rect(far);
w.paint(&mut pctx, &mut scene);
let ime = pctx
.take_ime_state()
.expect("focused field republishes even while offscreen");
assert_eq!(
ime.editing.text, "v2",
"the rebuild-mutated value republished immediately, not stale 'v1'"
);
}
#[test]
fn animated_scale_child_straddling_the_cull_boundary_does_not_pop() {
use frust_core::Curve;
use std::time::Duration;
let snap = crate::Timing::Duration(Duration::ZERO, Curve::Linear);
let view: FlexView<()> = column()
.child(leaf(ROW_W, ROW_H).into_any())
.child(leaf(ROW_W, ROW_H).into_any())
.child(crate::motion::AnimatedScale(2.8, leaf(ROW_W, ROW_H)).timing(snap))
.child(leaf(ROW_W, ROW_H).into_any())
.child(crate::motion::AnimatedScale(2.8, leaf(ROW_W, ROW_H)).timing(snap));
let mut counter = 0u64;
let mut w = view.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut scene = RecordingScene::default();
let mut pctx = PaintCtx::new(Point::ZERO, Size::new(ROW_W, ROW_H * 5.0));
pctx.constrain_visible_rect(top_row_rect());
w.paint(&mut pctx, &mut scene);
assert_eq!(
scene.rects.len(),
3,
"the boundary AnimatedScale row paints on its layout box, the two \
fully-outside rows (one of them also AnimatedScale) are culled"
);
assert!(
scene.transforms.iter().any(|t| {
let c = t.as_coeffs();
(c[0] - 2.8).abs() < 1e-6 && (c[3] - 2.8).abs() < 1e-6
}),
"the boundary row composited at 2.8× without being culled by its \
transform-overflowed visual bounds"
);
}
#[test]
fn children_vec_diff_adds_removes_and_type_swaps() {
let mut counter = 0u64;
let prev: FlexView<()> = row()
.child(leaf(10.0, 10.0).into_any())
.child(leaf(10.0, 10.0).into_any());
let mut w = prev.build(&mut ctx(&mut counter));
assert_eq!(w.children.len(), 2);
let grown: FlexView<()> = row()
.child(leaf(10.0, 10.0).into_any())
.child(leaf(10.0, 10.0).into_any())
.child(leaf(10.0, 10.0).into_any());
let flags = grown.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 3);
assert!(flags.needs_layout());
let shrunk: FlexView<()> = row().child(leaf(10.0, 10.0).into_any());
shrunk.rebuild(&grown, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 1);
assert_eq!(w.flex.len(), 1);
let swapped: FlexView<()> = row().child(crate::test_support::swap_leaf().into_any());
swapped.rebuild(&shrunk, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 1);
let mut lctx = LayoutCtx::new();
w.layout(&mut lctx, &BoxConstraints::loose(Size::new(500.0, 500.0)));
assert_eq!(w.children[0].size(), Size::new(7.0, 7.0));
}
use std::any::Any;
use std::cell::Cell;
use std::rc::Rc;
use frust_core::{
EventCtx, Key, KeyEvent, Modifiers, PointerButton, PointerEvent, PointerPhase, any,
};
const ROW_W: f64 = 50.0;
const ROW_H: f64 = 20.0;
struct Captor {
id: u32,
}
struct CaptorWidget {
id: u32,
armed: bool,
}
fn captor(id: u32) -> impl View<Vec<u32>> {
Captor { id }
}
impl View<Vec<u32>> for Captor {
type Element = CaptorWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> CaptorWidget {
CaptorWidget {
id: self.id,
armed: false,
}
}
fn rebuild(
&self,
_prev: &Self,
element: &mut CaptorWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
element.id = self.id;
ChangeFlags::NONE
}
}
impl Widget for CaptorWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(Size::new(ROW_W, ROW_H))
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
let InputEvent::Pointer(p) = event else {
return EventResult::Ignored;
};
match p.phase {
PointerPhase::Down => {
self.armed = true;
ctx.capture_pointer();
EventResult::Handled
}
PointerPhase::Move => EventResult::Handled,
PointerPhase::Up => {
if self.armed {
ctx.state_mut::<Vec<u32>>().push(self.id);
}
self.armed = false;
EventResult::Handled
}
PointerPhase::Cancel => {
self.armed = false;
EventResult::Handled
}
}
}
}
struct Recorder {
seen: Rc<Cell<u32>>,
}
struct RecorderWidget {
seen: Rc<Cell<u32>>,
}
impl View<Vec<u32>> for Recorder {
type Element = RecorderWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> RecorderWidget {
RecorderWidget {
seen: self.seen.clone(),
}
}
fn rebuild(
&self,
_prev: &Self,
_element: &mut RecorderWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
ChangeFlags::NONE
}
}
impl Widget for RecorderWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(Size::new(ROW_W, ROW_H))
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
fn event(&mut self, _ctx: &mut EventCtx, _event: &InputEvent) -> EventResult {
self.seen.set(self.seen.get() + 1);
EventResult::Handled
}
}
fn ev(phase: PointerPhase, x: f64, y: f64) -> InputEvent {
InputEvent::Pointer(PointerEvent {
phase,
position: Point::new(x, y),
button: PointerButton::Primary,
})
}
#[allow(clippy::ptr_arg)]
fn dispatch(w: &mut FlexWidget, log: &mut Vec<u32>, event: &InputEvent) {
let state: &mut dyn Any = log;
let mut ectx = EventCtx::new(state, Point::ZERO, Size::new(ROW_W, ROW_H * 8.0));
w.event(&mut ectx, event);
}
fn layout_column(w: &mut FlexWidget) {
let mut lctx = LayoutCtx::new();
w.layout(
&mut lctx,
&BoxConstraints::loose(Size::new(ROW_W, ROW_H * 8.0)),
);
}
fn row_y(i: usize) -> f64 {
i as f64 * ROW_H + ROW_H / 2.0
}
#[test]
fn append_after_preserves_captured_drag_before_change() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column().child(captor(0)).child(captor(1)).child(captor(2));
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert!(w.children[1].is_active(), "row 1 captured the pointer");
let appended: FlexView<Vec<u32>> = column()
.child(captor(0))
.child(captor(1))
.child(captor(2))
.child(captor(3));
appended.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children[1].is_active(),
"append-after preserves the captured row's active path (stable prefix)"
);
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(1)));
assert_eq!(log, vec![1], "captured row fires on Up as normal");
}
#[test]
fn type_swap_before_armed_index_cancels_with_synthetic_cancel() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column().child(captor(0)).child(captor(1)).child(captor(2));
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(2)));
assert!(w.children[2].is_active(), "row 2 captured the pointer");
let seen = Rc::new(Cell::new(0u32));
let swapped: FlexView<Vec<u32>> = column()
.child(Recorder { seen })
.child(captor(1))
.child(captor(2));
swapped.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children.iter().all(|p| !p.is_active()),
"swap before the armed index cancelled the tail's active path"
);
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(2)));
assert!(
log.is_empty(),
"no fire on Up — the armed row was synthetically cancelled"
);
}
#[test]
fn structural_truncation_of_active_row_unwinds_without_panic() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column()
.child(captor(0))
.child(captor(1))
.child(captor(2))
.child(captor(3));
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(2)));
assert!(w.children[2].is_active());
let truncated: FlexView<Vec<u32>> = column().child(captor(0)).child(captor(1));
truncated.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 2);
assert_eq!(w.flex.len(), 2);
assert!(w.children.iter().all(|p| !p.is_active()));
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(2)));
assert!(log.is_empty());
}
#[test]
fn type_swap_at_active_index_clears_without_notifying_fresh_widget() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column().child(captor(0)).child(captor(1));
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert!(w.children[1].is_active());
let seen = Rc::new(Cell::new(0u32));
let swapped: FlexView<Vec<u32>> = column()
.child(captor(0))
.child(Recorder { seen: seen.clone() });
swapped.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(!w.children[1].is_active(), "stale capture path dropped");
assert_eq!(seen.get(), 0, "fresh widget received no synthetic event");
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert_eq!(seen.get(), 1, "fresh widget responds to a new gesture");
}
#[test]
fn content_only_rebuild_preserves_captured_drag() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column().child(captor(0)).child(captor(1));
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert!(w.children[1].is_active());
let same: FlexView<Vec<u32>> = column().child(captor(0)).child(captor(1));
same.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children[1].is_active(),
"content-only rebuild must NOT clear an in-flight capture"
);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(1)));
assert_eq!(log, vec![1], "captured row fires on Up as normal");
}
#[test]
fn focus_on_child_survives_append_after() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column().child(FocusRow { id: 0 }).child(FocusRow { id: 1 });
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
assert!(w.children[0].is_focused(), "child 0 took focus");
let appended: FlexView<Vec<u32>> = column()
.child(FocusRow { id: 0 })
.child(FocusRow { id: 1 })
.child(FocusRow { id: 2 });
appended.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children[0].is_focused(),
"append-after preserves the focused child's recorded path"
);
layout_column(&mut w);
dispatch(&mut w, &mut log, &key_event());
assert_eq!(log, vec![0], "Key still routes to the focused row");
}
#[test]
fn focus_on_child_survives_remove_after() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column()
.child(FocusRow { id: 0 })
.child(FocusRow { id: 1 })
.child(FocusRow { id: 2 });
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
assert!(w.children[0].is_focused());
let removed: FlexView<Vec<u32>> =
column().child(FocusRow { id: 0 }).child(FocusRow { id: 1 });
removed.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children[0].is_focused(),
"remove-after preserves the focused child's recorded path"
);
layout_column(&mut w);
dispatch(&mut w, &mut log, &key_event());
assert_eq!(log, vec![0], "Key still routes to the focused row");
}
#[test]
fn focus_cleared_when_focused_index_type_swaps() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = column().child(FocusRow { id: 0 }).child(FocusRow { id: 1 });
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert!(w.children[1].is_focused(), "child 1 took focus");
let swapped: FlexView<Vec<u32>> = column().child(FocusRow { id: 0 }).child(captor(9));
swapped.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
!w.children[1].is_focused(),
"a type swap at the focused index clears its focus path"
);
layout_column(&mut w);
dispatch(&mut w, &mut log, &key_event());
assert!(
log.is_empty(),
"Key reaches nobody after the focused index swaps"
);
}
struct Counter {
id: u32,
}
struct CounterWidget {
id: u32,
count: u32,
}
impl View<Vec<u32>> for Counter {
type Element = CounterWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> CounterWidget {
CounterWidget {
id: self.id,
count: 0,
}
}
fn rebuild(
&self,
_prev: &Self,
element: &mut CounterWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
element.id = self.id;
ChangeFlags::NONE
}
}
impl Widget for CounterWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(Size::new(ROW_W, ROW_H))
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
let InputEvent::Pointer(p) = event else {
return EventResult::Ignored;
};
if p.phase == PointerPhase::Down {
self.count += 1;
ctx.state_mut::<Vec<u32>>().push(self.count);
EventResult::Handled
} else {
EventResult::Ignored
}
}
}
fn kcounter(key: u64, id: u32) -> FlexChild<Vec<u32>> {
keyed(key, Counter { id })
}
fn keyed_column(children: Vec<FlexChild<Vec<u32>>>) -> FlexView<Vec<u32>> {
FlexView::new(Axis::Vertical, children)
}
#[test]
fn keyed_reorder_preserves_widget_state() {
let mut counter = 0u64;
let prev = keyed_column(vec![kcounter(1, 1), kcounter(2, 2)]);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
assert_eq!(log, vec![1, 2, 3], "count accumulates on the original row");
log.clear();
let reordered = keyed_column(vec![kcounter(2, 2), kcounter(1, 1)]);
reordered.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 2);
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert_eq!(
log,
vec![4],
"reordered row kept its internal state (4, not a reset 1)"
);
}
#[test]
fn keyed_insert_above_preserves_existing_widget_state() {
let mut counter = 0u64;
let prev = keyed_column(vec![kcounter(1, 1), kcounter(2, 2)]);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert_eq!(log, vec![1, 2]);
log.clear();
let inserted = keyed_column(vec![kcounter(9, 9), kcounter(1, 1), kcounter(2, 2)]);
inserted.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 3);
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(2)));
assert_eq!(log, vec![3], "shifted row preserved its state");
log.clear();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
assert_eq!(log, vec![1], "newly-inserted key builds a fresh widget");
}
#[test]
fn keyed_swap_preserves_both_widgets() {
let mut counter = 0u64;
let prev = keyed_column(vec![kcounter(1, 1), kcounter(2, 2)]);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert_eq!(log, vec![1, 1, 2]);
log.clear();
let swapped = keyed_column(vec![kcounter(2, 2), kcounter(1, 1)]);
swapped.rebuild(&prev, &mut w, &mut ctx(&mut counter));
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert_eq!(log, vec![3, 2], "both swapped rows kept their state");
}
#[test]
fn keyed_same_order_rebuild_is_not_structural() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![keyed(1u64, Captor { id: 0 }), keyed(2u64, Captor { id: 1 })],
);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert!(w.children[1].is_active());
let same: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![keyed(1u64, Captor { id: 0 }), keyed(2u64, Captor { id: 1 })],
);
same.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children[1].is_active(),
"same-order keyed rebuild must not clear an in-flight capture"
);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(1)));
assert_eq!(log, vec![1], "captured row fires on Up as normal");
}
#[test]
fn keyed_reorder_preserves_captured_drag() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![keyed(1u64, Captor { id: 0 }), keyed(2u64, Captor { id: 1 })],
);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
assert!(w.children[0].is_active());
let reordered: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![keyed(2u64, Captor { id: 1 }), keyed(1u64, Captor { id: 0 })],
);
reordered.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children[1].is_active(),
"keyed reorder carries the captured row's active path to its new index"
);
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(1)));
assert_eq!(log, vec![0], "the relocated captured row fires on Up");
}
#[test]
fn keyed_removed_active_key_unwinds_without_fire() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![
keyed(1u64, Captor { id: 0 }),
keyed(2u64, Captor { id: 1 }),
keyed(3u64, Captor { id: 2 }),
],
);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert!(w.children[1].is_active());
let removed: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![keyed(1u64, Captor { id: 0 }), keyed(3u64, Captor { id: 2 })],
);
removed.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 2);
assert_eq!(w.flex.len(), 2);
assert!(w.children.iter().all(|p| !p.is_active()));
layout_column(&mut w);
dispatch(&mut w, &mut log, &ev(PointerPhase::Up, 10.0, row_y(1)));
assert!(log.is_empty(), "removed active row does not fire on Up");
}
#[test]
fn keyed_reorder_preserves_focus_and_key_routing() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![
keyed(1u64, FocusRow { id: 1 }),
keyed(2u64, FocusRow { id: 2 }),
],
);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(0)));
assert!(w.children[0].is_focused());
let reordered: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![
keyed(2u64, FocusRow { id: 2 }),
keyed(1u64, FocusRow { id: 1 }),
],
);
reordered.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert!(
w.children[1].is_focused(),
"the focused key-1 row keeps focus at its new index"
);
assert!(!w.children[0].is_focused());
layout_column(&mut w);
dispatch(&mut w, &mut log, &key_event());
assert_eq!(log, vec![1], "Key routes to the relocated focused row");
}
#[test]
fn keyed_removed_focused_key_clears_focus() {
let mut counter = 0u64;
let prev: FlexView<Vec<u32>> = FlexView::new(
Axis::Vertical,
vec![
keyed(1u64, FocusRow { id: 1 }),
keyed(2u64, FocusRow { id: 2 }),
],
);
let mut w = prev.build(&mut ctx(&mut counter));
layout_column(&mut w);
let mut log: Vec<u32> = Vec::new();
dispatch(&mut w, &mut log, &ev(PointerPhase::Down, 10.0, row_y(1)));
assert!(w.children[1].is_focused());
let removed: FlexView<Vec<u32>> =
FlexView::new(Axis::Vertical, vec![keyed(1u64, FocusRow { id: 1 })]);
removed.rebuild(&prev, &mut w, &mut ctx(&mut counter));
assert_eq!(w.children.len(), 1);
assert!(
w.children.iter().all(|p| !p.is_focused()),
"the removed focused row leaves no focus path behind"
);
layout_column(&mut w);
dispatch(&mut w, &mut log, &key_event());
assert!(
log.is_empty(),
"Key reaches nobody after the focused key is removed"
);
}
struct FocusRow {
id: u32,
}
struct FocusRowWidget {
id: u32,
}
impl View<Vec<u32>> for FocusRow {
type Element = FocusRowWidget;
fn build(&self, _ctx: &mut BuildCtx<'_>) -> FocusRowWidget {
FocusRowWidget { id: self.id }
}
fn rebuild(
&self,
_prev: &Self,
element: &mut FocusRowWidget,
_ctx: &mut BuildCtx<'_>,
) -> ChangeFlags {
element.id = self.id;
ChangeFlags::NONE
}
}
impl Widget for FocusRowWidget {
fn layout(&mut self, _ctx: &mut LayoutCtx, bc: &BoxConstraints) -> Size {
bc.constrain(Size::new(ROW_W, ROW_H))
}
fn paint(&mut self, _ctx: &mut PaintCtx, _scene: &mut dyn PaintScene) {}
fn event(&mut self, ctx: &mut EventCtx, event: &InputEvent) -> EventResult {
match event {
InputEvent::Pointer(p) if p.phase == PointerPhase::Down => {
ctx.request_focus();
EventResult::Handled
}
InputEvent::Key(_) => {
ctx.state_mut::<Vec<u32>>().push(self.id);
EventResult::Handled
}
_ => EventResult::Ignored,
}
}
}
fn key_event() -> InputEvent {
InputEvent::Key(KeyEvent {
key: Key::Character("a".to_string()),
modifiers: Modifiers::default(),
repeat: false,
})
}
}