Skip to main content

fission_core/
lowering.rs

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