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