1use crate::lowering::{LoweringContext, NodeBuilder, wrap_zstack_child};
2use crate::ui::traits::Lower;
3use crate::ActionEnvelope;
4use fission_ir::{
5 op::{Color, Fill, LayoutOp, Op, PaintOp, Stroke},
6 NodeId,
7};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Default, Clone, Serialize, Deserialize)]
26pub struct Switch {
27 pub id: Option<NodeId>,
29 pub checked: bool,
31 pub on_toggle: Option<ActionEnvelope>,
33}
34
35impl Switch {
36 pub fn into_node(self) -> crate::ui::Node {
37 crate::ui::Node::Switch(self)
38 }
39}
40
41impl Lower for Switch {
42 fn lower(&self, cx: &mut LoweringContext) -> NodeId {
43 let id = self.id.unwrap_or_else(|| cx.next_node_id());
44 cx.push_scope(id);
45
46 let tokens = &cx.env.theme.tokens;
47 let width = 36.0;
48 let height = 20.0;
49 let thumb_size = 16.0;
50 let padding = 2.0;
51
52 let track_color = if self.checked { tokens.colors.primary } else { tokens.colors.border };
53 let thumb_color = tokens.colors.on_primary;
54
55 let track_paint = Op::Paint(PaintOp::DrawRect {
57 fill: Some(Fill { color: track_color }),
58 stroke: None,
59 corner_radius: height / 2.0,
60 shadow: None,
61 });
62 let track_node = NodeBuilder::new(cx.next_node_id(), track_paint).build(cx);
63
64 let thumb_paint = Op::Paint(PaintOp::DrawRect {
66 fill: Some(Fill { color: thumb_color }),
67 stroke: None,
68 corner_radius: thumb_size / 2.0,
69 shadow: Some(fission_ir::op::BoxShadow {
70 color: Color { r:0, g:0, b:0, a:50 },
71 blur_radius: 2.0,
72 offset: (0.0, 1.0)
73 }),
74 });
75 let thumb_paint_node = NodeBuilder::new(cx.next_node_id(), thumb_paint).build(cx);
76
77 let left_padding = if self.checked { width - thumb_size - padding } else { padding };
78
79 let mut thumb_wrapper = NodeBuilder::new(
80 cx.next_node_id(),
81 Op::Layout(LayoutOp::Box {
82 width: Some(thumb_size), height: Some(thumb_size),
83 min_width: None, max_width: None, min_height: None, max_height: None,
84 padding: [0.0; 4],
85 flex_grow: 0.0,
86 flex_shrink: 0.0,
87 aspect_ratio: None,
88 })
89 );
90 thumb_wrapper.add_child(thumb_paint_node);
91 let thumb_id = thumb_wrapper.build(cx);
92
93 let layout_id = cx.next_node_id();
95 let bg_id = {
96 let mut bg_fill = NodeBuilder::new(cx.next_node_id(), Op::Layout(LayoutOp::AbsoluteFill));
97 bg_fill.add_child(track_node);
98 bg_fill.build(cx)
99 };
100
101 let content_id = {
102 let mut thumb_track = NodeBuilder::new(
103 cx.next_node_id(),
104 Op::Layout(LayoutOp::Box {
105 width: Some(width), height: Some(height),
106 min_width: None, max_width: None, min_height: None, max_height: None,
107 padding: [left_padding, 0.0, padding, 0.0],
108 flex_grow: 0.0,
109 flex_shrink: 0.0,
110 aspect_ratio: None,
111 })
112 );
113 thumb_track.add_child(thumb_id);
114 thumb_track.build(cx)
115 };
116
117 cx.push_scope(layout_id);
118 let bg_wrapped = wrap_zstack_child(cx, bg_id);
119 let content_wrapped = wrap_zstack_child(cx, content_id);
120 cx.pop_scope();
121
122 let mut root = NodeBuilder::new(layout_id, Op::Layout(LayoutOp::ZStack));
123 root.add_child(bg_wrapped);
124 root.add_child(content_wrapped);
125 root.build(cx);
126
127 cx.pop_scope();
128
129 let mut semantics = fission_ir::Semantics {
130 role: fission_ir::Role::Switch,
131 label: None,
132 value: Some(if self.checked { "true".into() } else { "false".into() }),
133 actions: Default::default(),
134 focusable: true,
135 multiline: false,
136 masked: false,
137 input_mask: None,
138 ime_preedit_range: None,
139 checked: Some(self.checked),
140 disabled: false,
141 draggable: false,
142 scrollable_x: false,
143 scrollable_y: false,
144 min_value: None,
145 max_value: None,
146 current_value: None,
147 is_focus_scope: false,
148 is_focus_barrier: false,
149 drag_payload: None,
150 hero_tag: None,
151 focus_index: None, capture_tab: false, auto_indent: false,
152 };
153 if let Some(action) = &self.on_toggle {
154 semantics.actions.entries.push(fission_ir::ActionEntry {
155 trigger: fission_ir::semantics::ActionTrigger::Default,
156 action_id: action.id.as_u128(),
157 payload_data: Some(action.payload.clone())
158 });
159 }
160
161 let mut sem_node = NodeBuilder::new(id, Op::Semantics(semantics));
162 sem_node.add_child(layout_id);
163 sem_node.build(cx)
164 }
165}
166