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                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(); // Leaf node for layout
414                (
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(); // Leaf node for layout
455                }
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}