Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Support arrays ([impl ViewSequence; N]) as ViewSequences #175

Merged
merged 2 commits into from
Jun 24, 2024
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
80 changes: 79 additions & 1 deletion xilem_core/src/sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -511,7 +511,7 @@ where
) -> MessageResult<Action, Message> {
let (start, rest) = id_path
.split_first()
.expect("Id path has elements for Option<ViewSequence>");
.expect("Id path has elements for Vec<ViewSequence>");
let (index, generation) = view_id_to_index_generation(*start);
let stored_generation = &seq_state.generations[index];
if *stored_generation != generation {
Expand All @@ -524,6 +524,84 @@ where
}
}

impl<State, Action, Context, Element, Marker, Seq, Message, const N: usize>
ViewSequence<State, Action, Context, Element, Vec<Marker>, Message> for [Seq; N]
where
Seq: ViewSequence<State, Action, Context, Element, Marker, Message>,
Context: ViewPathTracker,
Element: ViewElement,
{
type SeqState = [Seq::SeqState; N];

#[doc(hidden)]
fn seq_build(&self, ctx: &mut Context, elements: &mut AppendVec<Element>) -> Self::SeqState {
// there's no enumerate directly on an array
let mut idx = 0;
self.each_ref().map(|vs| {
let state = ctx.with_id(ViewId::new(idx), |ctx| vs.seq_build(ctx, elements));
idx += 1;
state
})
}

#[doc(hidden)]
fn seq_rebuild(
&self,
prev: &Self,
seq_state: &mut Self::SeqState,
ctx: &mut Context,
elements: &mut impl ElementSplice<Element>,
) {
for (idx, ((seq, prev_seq), state)) in self.iter().zip(prev).zip(seq_state).enumerate() {
ctx.with_id(
ViewId::new(idx.try_into().expect(
"ViewSequence arrays with more than u64::MAX + 1 elements not supported",
)),
|ctx| {
seq.seq_rebuild(prev_seq, state, ctx, elements);
},
);
}
}

#[doc(hidden)]
fn seq_message(
&self,
seq_state: &mut Self::SeqState,
id_path: &[ViewId],
message: Message,
app_state: &mut State,
) -> MessageResult<Action, Message> {
let (start, rest) = id_path
.split_first()
.expect("Id path has elements for [ViewSequence; N]");

let index: usize = start.routing_id().try_into().unwrap();
// We know the index is in bounds because it was created from an index into a value of Self
let inner_state = &mut seq_state[index];
self[index].seq_message(inner_state, rest, message, app_state)
}

#[doc(hidden)]
fn seq_teardown(
&self,
seq_state: &mut Self::SeqState,
ctx: &mut Context,
elements: &mut impl ElementSplice<Element>,
) {
for (idx, (seq, state)) in self.iter().zip(seq_state).enumerate() {
ctx.with_id(
ViewId::new(idx.try_into().expect(
"ViewSequence arrays with more than u64::MAX + 1 elements not supported",
)),
|ctx| {
seq.seq_teardown(state, ctx, elements);
},
);
}
}
}

impl<State, Action, Context, Element, Message>
ViewSequence<State, Action, Context, Element, (), Message> for ()
where
Expand Down
116 changes: 116 additions & 0 deletions xilem_core/tests/array_sequence.rs
Original file line number Diff line number Diff line change
@@ -0,0 +1,116 @@
// Copyright 2024 the Xilem Authors
// SPDX-License-Identifier: Apache-2.0

mod common;
use common::*;
use xilem_core::View;

fn record_ops(id: u32) -> OperationView<0> {
OperationView(id)
}

/// The sequence [item, item] should pass through all methods to the children
#[test]
fn two_element_passthrough() {
let view = sequence(2, [record_ops(0), record_ops(1)]);
let mut ctx = TestCtx::default();
let (mut element, mut state) = view.build(&mut ctx);
ctx.assert_empty();
assert_eq!(element.operations, &[Operation::Build(2)]);
assert_eq!(element.view_path, &[]);

let seq_children = element.children.as_ref().unwrap();
assert!(seq_children.deleted.is_empty());
assert_eq!(seq_children.active.len(), 2);
let first_child = &seq_children.active[0];
assert_eq!(first_child.operations, &[Operation::Build(0)]);
assert_eq!(first_child.view_path.len(), 1);
let second_child = &seq_children.active[1];
assert_eq!(second_child.operations, &[Operation::Build(1)]);
assert_eq!(second_child.view_path.len(), 1);

let view2 = sequence(5, [record_ops(3), record_ops(4)]);
view2.rebuild(&view, &mut state, &mut ctx, &mut element);
ctx.assert_empty();
assert_eq!(
element.operations,
&[Operation::Build(2), Operation::Rebuild { from: 2, to: 5 }]
);

let seq_children = element.children.as_ref().unwrap();
assert!(seq_children.deleted.is_empty());
assert_eq!(seq_children.active.len(), 2);
let first_child = &seq_children.active[0];
assert_eq!(
first_child.operations,
&[Operation::Build(0), Operation::Rebuild { from: 0, to: 3 }]
);
let second_child = &seq_children.active[1];
assert_eq!(
second_child.operations,
&[Operation::Build(1), Operation::Rebuild { from: 1, to: 4 }]
);

view2.teardown(&mut state, &mut ctx, &mut element);
assert_eq!(
element.operations,
&[
Operation::Build(2),
Operation::Rebuild { from: 2, to: 5 },
Operation::Teardown(5)
]
);

let seq_children = element.children.as_ref().unwrap();
// It was removed from the parent sequence when tearing down
assert_eq!(seq_children.active.len(), 0);
assert_eq!(seq_children.deleted.len(), 2);
let (first_child_idx, first_child) = &seq_children.deleted[0];
assert_eq!(*first_child_idx, 0);
assert_eq!(
first_child.operations,
&[
Operation::Build(0),
Operation::Rebuild { from: 0, to: 3 },
Operation::Teardown(3)
]
);
let (second_child_idx, second_child) = &seq_children.deleted[1];
// At the time of being deleted, this was effectively the item at index 0
assert_eq!(*second_child_idx, 0);
assert_eq!(
second_child.operations,
&[
Operation::Build(1),
Operation::Rebuild { from: 1, to: 4 },
Operation::Teardown(4)
]
);
}

/// The sequence [item, item] should route messages to the right children
#[test]
fn two_element_message() {
let view = sequence(2, [record_ops(0), record_ops(1)]);
let mut ctx = TestCtx::default();
let (element, mut state) = view.build(&mut ctx);
ctx.assert_empty();
assert_eq!(element.operations, &[Operation::Build(2)]);
assert_eq!(element.view_path, &[]);

let seq_children = element.children.as_ref().unwrap();
assert!(seq_children.deleted.is_empty());
assert_eq!(seq_children.active.len(), 2);
let first_child = &seq_children.active[0];
assert_eq!(first_child.operations, &[Operation::Build(0)]);
let first_path = first_child.view_path.to_vec();
let second_child = &seq_children.active[1];
assert_eq!(second_child.operations, &[Operation::Build(1)]);
let second_path = second_child.view_path.to_vec();

let result = view.message(&mut state, &first_path, Box::new(()), &mut ());
assert_action(result, 0);

let result = view.message(&mut state, &second_path, Box::new(()), &mut ());
assert_action(result, 1);
}
2 changes: 1 addition & 1 deletion xilem_core/tests/tuple_sequence.rs
Original file line number Diff line number Diff line change
Expand Up @@ -165,7 +165,7 @@ fn two_element_passthrough() {
);
}

/// The sequence (item, item) should pass through all methods to the children
/// The sequence (item, item) should route messages to the right children
#[test]
fn two_element_message() {
let view = sequence(2, (record_ops(0), record_ops(1)));
Expand Down