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