rust_elm/compose/
component.rs1use crate::cmd::Cmd;
2
3pub struct Slot<ParentState, ParentAction, ChildState, ChildAction> {
5 pub get: fn(&ParentState) -> Option<&ChildState>,
6 pub get_mut: fn(&mut ParentState) -> Option<&mut ChildState>,
7 pub embed: fn(ChildAction) -> ParentAction,
8 pub child_update: fn(&mut ChildState, ChildAction) -> Cmd<ChildAction>,
9}
10
11impl<PS, PA: Send + 'static, CS, CA: Send + 'static> Slot<PS, PA, CS, CA> {
12 pub fn new(
13 get: fn(&PS) -> Option<&CS>,
14 get_mut: fn(&mut PS) -> Option<&mut CS>,
15 embed: fn(CA) -> PA,
16 child_update: fn(&mut CS, CA) -> Cmd<CA>,
17 ) -> Self {
18 Self {
19 get,
20 get_mut,
21 embed,
22 child_update,
23 }
24 }
25
26 pub fn lift(&self, parent: &mut PS, action: PA, extract: fn(PA) -> Option<CA>) -> Cmd<PA> {
27 let Some(child_action) = extract(action) else {
28 return Cmd::none();
29 };
30 let Some(child) = (self.get_mut)(parent) else {
31 return Cmd::none();
32 };
33 (self.child_update)(child, child_action).map(self.embed)
34 }
35}
36
37pub fn lift<PA: Send + 'static, CA: Send + 'static>(
39 cmd: Cmd<CA>,
40 embed: fn(CA) -> PA,
41) -> Cmd<PA> {
42 cmd.map(embed)
43}
44
45#[cfg(test)]
46#[allow(dead_code, clippy::bool_assert_comparison)]
47mod tests {
48 use super::*;
49
50 #[derive(Default)]
51 struct Parent {
52 child: Option<Child>,
53 }
54
55 #[derive(Default)]
56 struct Child {
57 n: i32,
58 }
59
60 #[derive(Clone, Copy)]
61 enum PMsg {
62 Child(CMsg),
63 }
64
65 #[derive(Clone, Copy)]
66 enum CMsg {
67 Inc,
68 }
69
70 fn child_update(c: &mut Child, msg: CMsg) -> Cmd<CMsg> {
71 match msg {
72 CMsg::Inc => c.n += 1,
73 }
74 Cmd::none()
75 }
76
77 #[test]
78 fn slot_lift_updates_nested_state() {
79 let slot = Slot::new(
80 |p: &Parent| p.child.as_ref(),
81 |p: &mut Parent| p.child.as_mut(),
82 PMsg::Child,
83 child_update,
84 );
85 let mut parent = Parent {
86 child: Some(Child::default()),
87 };
88 let extract = |m: PMsg| match m {
89 PMsg::Child(c) => Some(c),
90 };
91 let _ = slot.lift(&mut parent, PMsg::Child(CMsg::Inc), extract);
92 assert_eq!(parent.child.as_ref().map(|c| c.n), Some(1));
93 }
94}