1use crate::env::{Env, RuntimeState};
2use fission_diagnostics::prelude as diag;
3use fission_ir::{CoreIR, FlexDirection, GridPlacement, LayoutOp, NodeId, Op, PaintOp, WidgetNodeId};
4use fission_ir::op::{TextRun, TextStyle};
5use fission_layout::{LayoutInputNode, TextMeasurer, LayoutSnapshot};
6use std::collections::HashMap;
7use std::sync::Arc;
8
9pub struct LoweringContext<'a> {
10 pub env: &'a Env,
11 pub runtime_state: &'a RuntimeState,
12 pub ir: CoreIR,
13 pub measurer: Option<&'a Arc<dyn TextMeasurer>>,
14 pub layout: Option<&'a LayoutSnapshot>,
15 id_stack: Vec<(NodeId, u32)>,
16 global_seq: u32,
17}
18
19impl<'a> LoweringContext<'a> {
20 pub fn new(
21 env: &'a Env,
22 runtime_state: &'a RuntimeState,
23 measurer: Option<&'a Arc<dyn TextMeasurer>>,
24 layout: Option<&'a LayoutSnapshot>,
25 ) -> Self {
26 Self {
27 env,
28 runtime_state,
29 ir: CoreIR::new(),
30 measurer,
31 layout,
32 id_stack: Vec::new(),
33 global_seq: 0,
34 }
35 }
36
37 pub fn next_node_id(&mut self) -> NodeId {
38 if let Some((base_id, seq)) = self.id_stack.last_mut() {
39 let next_id = NodeId::derived(base_id.as_u128(), &[*seq]);
40 *seq += 1;
41 next_id
42 } else {
43 let next_id = NodeId::derived(0x1337_C0DE_0000_0000, &[self.global_seq]);
44 self.global_seq += 1;
45 next_id
46 }
47 }
48
49 pub fn push_scope(&mut self, node_id: NodeId) {
50 self.id_stack.push((node_id, 0));
51 }
52
53 pub fn pop_scope(&mut self) {
54 self.id_stack.pop().expect("Lowering stack underflow");
55 }
56
57 pub fn widget_node_id(&self, widget_id: WidgetNodeId) -> NodeId {
58 widget_id.into()
59 }
60
61 fn insert_node(&mut self, node_id: NodeId, op: Op, children: Vec<NodeId>) {
62 use std::hash::{Hash, Hasher};
63 let mut hasher = std::collections::hash_map::DefaultHasher::new();
64 op.hash(&mut hasher);
65
66 for child_id in &children {
67 if let Some(child) = self.ir.nodes.get(child_id) {
68 child.hash.hash(&mut hasher);
69 }
70 child_id.hash(&mut hasher);
71 }
72
73 let hash = hasher.finish();
74
75 self.ir.add_node(node_id, op, children);
76
77 if let Some(node) = self.ir.nodes.get_mut(&node_id) {
78 node.hash = hash;
79 }
80 }
81}
82
83pub struct NodeBuilder {
84 node_id: NodeId,
85 op: Op,
86 children: Vec<NodeId>,
87}
88
89impl NodeBuilder {
90 pub fn new(node_id: NodeId, op: Op) -> Self {
91 Self {
92 node_id,
93 op,
94 children: Vec::new(),
95 }
96 }
97
98 pub fn add_child(&mut self, child: NodeId) {
99 self.children.push(child);
100 }
101
102 pub fn add_children<I>(&mut self, children: I)
103 where
104 I: IntoIterator<Item = NodeId>,
105 {
106 self.children.extend(children);
107 }
108
109 pub fn build(self, cx: &mut LoweringContext) -> NodeId {
110 cx.insert_node(self.node_id, self.op, self.children);
111 self.node_id
112 }
113}
114
115pub fn wrap_zstack_child(cx: &mut LoweringContext, child_id: NodeId) -> NodeId {
116 let mut item = NodeBuilder::new(
117 cx.next_node_id(),
118 Op::Layout(LayoutOp::GridItem {
119 row_start: GridPlacement::Line(1),
120 row_end: GridPlacement::Auto,
121 col_start: GridPlacement::Line(1),
122 col_end: GridPlacement::Auto,
123 }),
124 );
125 item.add_child(child_id);
126 item.build(cx)
127}
128
129pub fn build_layout_tree(ir: &CoreIR, env: &Env) -> Vec<LayoutInputNode> {
130 let mut input_nodes = Vec::new();
131
132 let mut parent_map = HashMap::new();
133 for (id, node) in &ir.nodes {
134 for child in &node.children {
135 parent_map.insert(*child, *id);
136 }
137 }
138
139 for (id, node) in &ir.nodes {
140 let mut rich_text_content: Option<Vec<fission_ir::op::TextRun>> = None;
141 let mut inherited_box = None;
142 if let Some(parent_id) = parent_map.get(id) {
143 if let Some(parent) = ir.nodes.get(parent_id) {
144 if parent.children.len() == 1 {
145 if let Op::Layout(LayoutOp::Box {
146 width,
147 height,
148 min_width,
149 max_width,
150 min_height,
151 max_height,
152 ..
153 }) = &parent.op
154 {
155 inherited_box = Some((*width, *height, *min_width, *max_width, *min_height, *max_height));
156 }
157 }
158 }
159 }
160
161 let mut children_to_visit = node.children.clone();
162
163 let (layout_op_variant, width, height, flex_grow, flex_shrink) = match &node.op {
164 Op::Layout(layout_op) => match layout_op {
165 LayoutOp::Box {
166 width,
167 height,
168 min_width,
169 max_width,
170 min_height,
171 max_height,
172 padding,
173 flex_grow,
174 flex_shrink: _,
175 aspect_ratio,
176 } => (
177 LayoutOp::Box {
178 width: *width,
179 height: *height,
180 min_width: *min_width,
181 max_width: *max_width,
182 min_height: *min_height,
183 max_height: *max_height,
184 padding: *padding,
185 flex_grow: *flex_grow,
186 flex_shrink: 1.0,
187 aspect_ratio: *aspect_ratio,
188 },
189 *width,
190 *height,
191 *flex_grow,
192 1.0,
193 ),
194 LayoutOp::Flex {
195 direction,
196 wrap,
197 flex_grow,
198 flex_shrink: _,
199 padding,
200 gap,
201 align_items,
202 justify_content,
203 } => (
204 LayoutOp::Flex {
205 direction: *direction,
206 wrap: *wrap,
207 flex_grow: *flex_grow,
208 flex_shrink: 1.0,
209 padding: *padding,
210 gap: *gap,
211 align_items: *align_items,
212 justify_content: *justify_content,
213 },
214 None,
215 None,
216 *flex_grow,
217 1.0,
218 ),
219 LayoutOp::Grid {
220 columns,
221 rows,
222 column_gap,
223 row_gap,
224 padding,
225 } => (
226 LayoutOp::Grid {
227 columns: columns.clone(),
228 rows: rows.clone(),
229 column_gap: *column_gap,
230 row_gap: *row_gap,
231 padding: *padding,
232 },
233 None,
234 None,
235 0.0,
236 1.0,
237 ),
238 LayoutOp::GridItem {
239 row_start,
240 row_end,
241 col_start,
242 col_end,
243 } => (
244 LayoutOp::GridItem {
245 row_start: *row_start,
246 row_end: *row_end,
247 col_start: *col_start,
248 col_end: *col_end,
249 },
250 None,
251 None,
252 0.0,
253 1.0,
254 ),
255 LayoutOp::Scroll {
256 direction,
257 width,
258 height,
259 min_width,
260 max_width,
261 min_height,
262 max_height,
263 padding,
264 flex_grow,
265 flex_shrink: _,
266 show_scrollbar,
267 } => (
268 LayoutOp::Scroll {
269 direction: *direction,
270 width: *width,
271 height: *height,
272 min_width: *min_width,
273 max_width: *max_width,
274 min_height: *min_height,
275 max_height: *max_height,
276 padding: *padding,
277 flex_grow: *flex_grow,
278 flex_shrink: 1.0,
279 show_scrollbar: *show_scrollbar,
280 },
281 *width,
282 *height,
283 *flex_grow,
284 1.0,
285 ),
286 LayoutOp::AbsoluteFill => (LayoutOp::AbsoluteFill, None, None, 1.0, 1.0),
287 LayoutOp::Positioned { top, left, bottom, right, width, height } => (
288 LayoutOp::Positioned {
289 top: *top,
290 left: *left,
291 bottom: *bottom,
292 right: *right,
293 width: *width,
294 height: *height,
295 },
296 *width,
297 *height,
298 0.0,
299 0.0,
300 ),
301 LayoutOp::ZStack => (LayoutOp::ZStack, None, None, 1.0, 1.0),
302 LayoutOp::Embed { kind, widget_id, width, height } => (
303 LayoutOp::Embed {
304 kind: *kind,
305 widget_id: *widget_id,
306 width: *width,
307 height: *height,
308 },
309 *width,
310 *height,
311 1.0,
312 1.0,
313 ),
314 LayoutOp::Align => (
315 LayoutOp::Align,
316 None,
317 None,
318 0.0,
319 0.0,
320 ),
321 LayoutOp::Transform { transform } => (
322 LayoutOp::Transform {
323 transform: *transform,
324 },
325 None,
326 None,
327 0.0,
328 0.0,
329 ),
330 LayoutOp::Flyout { anchor, content } => (
331 LayoutOp::Flyout {
332 anchor: *anchor,
333 content: *content,
334 },
335 None,
336 None,
337 0.0,
338 0.0,
339 ),
340 LayoutOp::Clip { path } => (
341 LayoutOp::Clip {
342 path: path.clone(),
343 },
344 None,
345 None,
346 0.0,
347 0.0,
348 ),
349 },
350 Op::Paint(PaintOp::DrawText { text, size, color, underline, caret_index: _ }) => {
351 let (inherit_width, inherit_height, inherit_min_width, inherit_max_width, inherit_min_height, inherit_max_height) =
352 inherited_box.unwrap_or((None, None, None, None, None, None));
353
354 let (measured_w, measured_h): (f32, f32) = if let Some(m) = &env.measurer {
355 m.measure(text, *size, None)
356 } else {
357 (0.0, 0.0)
358 };
359
360 rich_text_content = Some(vec![fission_ir::op::TextRun {
361 text: text.clone(),
362 style: fission_ir::op::TextStyle { font_size: *size, color: *color, underline: *underline, background_color: None },
363 }]);
364 children_to_visit.clear(); (
366 LayoutOp::Box {
367 width: inherit_width.or(Some(measured_w)),
368 height: inherit_height.or(Some(measured_h)),
369 min_width: inherit_min_width,
370 max_width: inherit_max_width,
371 min_height: inherit_min_height,
372 max_height: inherit_max_height,
373 padding: [0.0; 4],
374 flex_grow: 0.0,
375 flex_shrink: 1.0,
376 aspect_ratio: None,
377 },
378 inherit_width.or(Some(measured_w)),
379 inherit_height.or(Some(measured_h)),
380 0.0,
381 1.0,
382 )
383 }
384 Op::Paint(PaintOp::DrawRichText { runs, caret_index: _ }) => {
385 let (inherit_width, inherit_height, inherit_min_width, inherit_max_width, inherit_min_height, inherit_max_height) =
386 inherited_box.unwrap_or((None, None, None, None, None, None));
387
388 let (measured_w, measured_h): (f32, f32) = if let Some(m) = &env.measurer {
389 m.measure_rich_text(runs, None)
390 } else {
391 (0.0, 0.0)
392 };
393
394 rich_text_content = Some(runs.clone());
395 children_to_visit.clear(); (
397 LayoutOp::Box {
398 width: inherit_width.or(Some(measured_w)),
399 height: inherit_height.or(Some(measured_h)),
400 min_width: inherit_min_width,
401 max_width: inherit_max_width,
402 min_height: inherit_min_height,
403 max_height: inherit_max_height,
404 padding: [0.0; 4],
405 flex_grow: 0.0,
406 flex_shrink: 1.0,
407 aspect_ratio: None,
408 },
409 inherit_width.or(Some(measured_w)),
410 inherit_height.or(Some(measured_h)),
411 0.0,
412 1.0,
413 )
414 }
415
416 Op::Paint(PaintOp::DrawImage { .. }) => {
417 children_to_visit.clear();
418 (LayoutOp::AbsoluteFill, None, None, 0.0, 0.0)
419 }
420
421 Op::Paint(_) => {
422 children_to_visit.clear();
423 (LayoutOp::AbsoluteFill, None, None, 0.0, 0.0)
424 }
425
426 _ => (
427 LayoutOp::Box {
428 width: None,
429 height: None,
430 min_width: None,
431 max_width: None,
432 min_height: None,
433 max_height: None,
434 padding: [0.0; 4],
435 flex_grow: 0.0,
436 flex_shrink: 1.0,
437 aspect_ratio: None,
438 },
439 None,
440 None,
441 0.0,
442 1.0,
443 ),
444 };
445
446 input_nodes.push(LayoutInputNode {
447 id: *id,
448 parent_id: parent_map.get(id).copied(),
449 op: layout_op_variant,
450 children_ids: children_to_visit,
451 debug_name: format!("{:?} ({:?})", node.id, node.op),
452 width,
453 height,
454 flex_grow,
455 flex_shrink,
456 rich_text: rich_text_content,
457 });
458 }
459
460 input_nodes
461}