1use crate::lowering::{LoweringContext, NodeBuilder};
2use crate::ui::traits::Lower;
3use crate::ActionEnvelope;
4use fission_ir::{
5 op::{LayoutOp, Op, PaintOp},
6 NodeId,
7};
8use serde::{Deserialize, Serialize};
9
10#[derive(Debug, Default, Clone, Serialize, Deserialize)]
28pub struct Checkbox {
29 pub id: Option<NodeId>,
31 pub checked: bool,
33 pub on_toggle: Option<ActionEnvelope>,
35 pub label: Option<String>,
37}
38
39impl Checkbox {
40 pub fn into_node(self) -> crate::ui::Node {
41 crate::ui::Node::Checkbox(self)
42 }
43}
44
45impl Lower for Checkbox {
46 fn lower(&self, cx: &mut LoweringContext) -> NodeId {
47 let id = self.id.unwrap_or_else(|| cx.next_node_id());
48 cx.push_scope(id);
49
50 let tokens = &cx.env.theme.tokens;
51 let size = 18.0;
52 let radius = tokens.radii.small;
53 let border_color = tokens.colors.text_secondary;
54 let active_color = tokens.colors.primary;
55 let text_color = tokens.colors.text_primary;
56
57 let square_id = cx.next_node_id();
59
60 let bg_paint = if self.checked {
61 Op::Paint(PaintOp::DrawRect {
62 fill: Some(fission_ir::op::Fill::Solid(active_color)),
63 stroke: None,
64 corner_radius: radius,
65 shadow: None,
66 })
67 } else {
68 Op::Paint(PaintOp::DrawRect {
69 fill: None,
70 stroke: Some(fission_ir::op::Stroke {
71 fill: fission_ir::op::Fill::Solid(border_color),
72 width: 1.5,
73 dash_array: None,
74 line_cap: fission_ir::op::LineCap::Butt,
75 line_join: fission_ir::op::LineJoin::Miter,
76 }),
77 corner_radius: radius,
78 shadow: None,
79 })
80 };
81 let bg_node = NodeBuilder::new(cx.next_node_id(), bg_paint).build(cx);
82
83 let check_node = if self.checked {
85 let check = NodeBuilder::new(
86 cx.next_node_id(),
87 Op::Paint(PaintOp::DrawRect {
88 fill: Some(fission_ir::op::Fill::Solid(tokens.colors.on_primary)),
89 stroke: None,
90 corner_radius: 1.0,
91 shadow: None,
92 }),
93 )
94 .build(cx);
95 let mut check_box = NodeBuilder::new(
96 cx.next_node_id(),
97 Op::Layout(LayoutOp::Box {
98 width: Some(10.0),
99 height: Some(10.0),
100 min_width: None,
101 max_width: None,
102 min_height: None,
103 max_height: None,
104 padding: [0.0; 4],
105 flex_grow: 0.0,
106 flex_shrink: 0.0,
107 aspect_ratio: None,
108 }),
109 );
110 check_box.add_child(check);
111 let check_box_id = check_box.build(cx);
112 let mut align = NodeBuilder::new(cx.next_node_id(), Op::Layout(LayoutOp::Align));
113 align.add_child(check_box_id);
114 Some(align.build(cx))
115 } else {
116 None
117 };
118
119 let mut square_box = NodeBuilder::new(
120 square_id,
121 Op::Layout(LayoutOp::Box {
122 width: Some(size),
123 height: Some(size),
124 min_width: None,
125 max_width: None,
126 min_height: None,
127 max_height: None,
128 padding: [0.0; 4],
129 flex_grow: 0.0,
130 flex_shrink: 0.0,
131 aspect_ratio: None,
132 }),
133 );
134 square_box.add_child(bg_node);
135 if let Some(c) = check_node {
136 square_box.add_child(c);
137 }
138 let square_final = square_box.build(cx);
139
140 let label_id = if let Some(text) = &self.label {
142 let text_id = NodeBuilder::new(
143 cx.next_node_id(),
144 Op::Paint(PaintOp::DrawText {
145 text: text.clone(),
146 size: tokens.typography.body_medium_size,
147 color: text_color,
148 underline: false,
149 wrap: false,
150 caret_index: None,
151 caret_color: None,
152 caret_width: None,
153 caret_height: None,
154 caret_radius: None,
155 paragraph_style: None,
156 }),
157 )
158 .build(cx);
159 let mut layout = NodeBuilder::new(
160 cx.next_node_id(),
161 Op::Layout(LayoutOp::Box {
162 width: None,
163 height: None,
164 min_width: None,
165 max_width: None,
166 min_height: None,
167 max_height: None,
168 padding: [tokens.spacing.s, 0.0, 0.0, 0.0],
169 flex_grow: 0.0,
170 flex_shrink: 0.0,
171 aspect_ratio: None,
172 }),
173 );
174 layout.add_child(text_id);
175 Some(layout.build(cx))
176 } else {
177 None
178 };
179
180 let layout_id = cx.next_node_id();
181 let mut row = NodeBuilder::new(
182 layout_id,
183 Op::Layout(LayoutOp::Flex {
184 direction: fission_ir::FlexDirection::Row,
185 wrap: fission_ir::op::FlexWrap::NoWrap,
186 flex_grow: 0.0,
187 flex_shrink: 1.0,
188 padding: [0.0; 4],
189 gap: Some(8.0),
190 align_items: fission_ir::op::AlignItems::Center,
191 justify_content: fission_ir::op::JustifyContent::Start,
192 }),
193 );
194 row.add_child(square_final);
195 if let Some(l) = label_id {
196 row.add_child(l);
197 }
198 row.build(cx);
199
200 cx.pop_scope();
201
202 let mut semantics = fission_ir::Semantics {
203 role: fission_ir::Role::Checkbox,
204 label: self.label.clone(),
205 identifier: None,
206 value: Some(if self.checked {
207 "true".into()
208 } else {
209 "false".into()
210 }),
211 actions: Default::default(),
212 action_scope_id: None,
213 focusable: true,
214 multiline: false,
215 masked: false,
216 input_mask: None,
217 ime_preedit_range: None,
218 checked: Some(self.checked),
219 disabled: false,
220 read_only: false,
221 autofocus: false,
222 draggable: false,
223 scrollable_x: false,
224 scrollable_y: false,
225 min_value: None,
226 max_value: None,
227 current_value: None,
228 is_focus_scope: false,
229 is_focus_barrier: false,
230 drag_payload: None,
231 hero_tag: None,
232 focus_index: None,
233 text_input_type: fission_ir::semantics::TextInputType::Text,
234 text_input_action: fission_ir::semantics::TextInputAction::Done,
235 text_capitalization: fission_ir::semantics::TextCapitalization::None,
236 max_length: None,
237 max_length_enforcement: fission_ir::semantics::MaxLengthEnforcement::Enforced,
238 input_formatters: Vec::new(),
239 autocorrect: true,
240 enable_suggestions: true,
241 spell_check: true,
242 smart_dashes: true,
243 smart_quotes: true,
244 autofill_hints: Vec::new(),
245 scroll_padding: None,
246 capture_tab: false,
247 auto_indent: false,
248 };
249 if let Some(action) = &self.on_toggle {
250 semantics.actions.entries.push(fission_ir::ActionEntry {
251 trigger: fission_ir::semantics::ActionTrigger::Default,
252 action_id: action.id.as_u128(),
253 payload_data: Some(action.payload.clone()),
254 });
255 }
256
257 let mut sem_node = NodeBuilder::new(id, Op::Semantics(semantics));
258 sem_node.add_child(layout_id);
259 sem_node.build(cx)
260 }
261}