Skip to main content

fission_core/
lowering.rs

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                let (measured_w, measured_h): (f32, f32) = if let Some(m) = &env.measurer {
399                    m.measure(text, *size, None)
400                } else {
401                    (0.0, 0.0)
402                };
403
404                rich_text_content = Some(vec![fission_ir::op::TextRun {
405                    text: text.clone(),
406                    style: fission_ir::op::TextStyle {
407                        font_size: *size,
408                        color: *color,
409                        underline: *underline,
410                        font_family: None,
411                        locale: None,
412                        font_weight: 400,
413                        font_style: fission_ir::op::FontStyle::Normal,
414                        line_height: None,
415                        letter_spacing: 0.0,
416                        background_color: None,
417                    },
418                }]);
419                children_to_visit.clear(); // Leaf node for layout
420                (
421                    LayoutOp::Box {
422                        width: inherit_width.or(Some(measured_w)),
423                        height: inherit_height.or(Some(measured_h)),
424                        min_width: inherit_min_width,
425                        max_width: inherit_max_width,
426                        min_height: inherit_min_height,
427                        max_height: inherit_max_height,
428                        padding: [0.0; 4],
429                        flex_grow: 0.0,
430                        flex_shrink: 1.0,
431                        aspect_ratio: None,
432                    },
433                    inherit_width.or(Some(measured_w)),
434                    inherit_height.or(Some(measured_h)),
435                    0.0,
436                    1.0,
437                )
438            }
439            Op::Paint(PaintOp::DrawRichText {
440                runs,
441                wrap: _,
442                caret_index: _,
443                caret_color: _,
444                caret_width: _,
445                ..
446            }) => {
447                let (
448                    inherit_width,
449                    inherit_height,
450                    inherit_min_width,
451                    inherit_max_width,
452                    inherit_min_height,
453                    inherit_max_height,
454                ) = inherited_box.unwrap_or((None, None, None, None, None, None));
455
456                let (measured_w, measured_h): (f32, f32) = if let Some(m) = &env.measurer {
457                    m.measure_rich_text(runs, None)
458                } else {
459                    (0.0, 0.0)
460                };
461
462                rich_text_content = Some(runs.clone());
463                if !runs.iter().any(|run| {
464                    decode_inline_widget_marker(run.style.font_family.as_deref()).is_some()
465                }) {
466                    children_to_visit.clear(); // Leaf node for layout
467                }
468                (
469                    LayoutOp::Box {
470                        width: inherit_width.or(Some(measured_w)),
471                        height: inherit_height.or(Some(measured_h)),
472                        min_width: inherit_min_width,
473                        max_width: inherit_max_width,
474                        min_height: inherit_min_height,
475                        max_height: inherit_max_height,
476                        padding: [0.0; 4],
477                        flex_grow: 0.0,
478                        flex_shrink: 1.0,
479                        aspect_ratio: None,
480                    },
481                    inherit_width.or(Some(measured_w)),
482                    inherit_height.or(Some(measured_h)),
483                    0.0,
484                    1.0,
485                )
486            }
487
488            Op::Paint(PaintOp::DrawImage { .. }) => {
489                children_to_visit.clear();
490                (LayoutOp::AbsoluteFill, None, None, 0.0, 0.0)
491            }
492
493            Op::Paint(_) => {
494                children_to_visit.clear();
495                (LayoutOp::AbsoluteFill, None, None, 0.0, 0.0)
496            }
497
498            _ => (
499                LayoutOp::Box {
500                    width: None,
501                    height: None,
502                    min_width: None,
503                    max_width: None,
504                    min_height: None,
505                    max_height: None,
506                    padding: [0.0; 4],
507                    flex_grow: 0.0,
508                    flex_shrink: 1.0,
509                    aspect_ratio: None,
510                },
511                None,
512                None,
513                0.0,
514                1.0,
515            ),
516        };
517
518        input_nodes.push(LayoutInputNode {
519            id: *id,
520            parent_id: parent_map.get(id).copied(),
521            op: layout_op_variant,
522            children_ids: children_to_visit,
523            debug_name: format!("{:?} ({:?})", node.id, node.op),
524            width,
525            height,
526            flex_grow,
527            flex_shrink,
528            rich_text: rich_text_content,
529        });
530    }
531
532    input_nodes
533}