diff --git a/sd-graphics/src/layout.rs b/sd-graphics/src/layout.rs index 80e3de9..227845e 100644 --- a/sd-graphics/src/layout.rs +++ b/sd-graphics/src/layout.rs @@ -70,6 +70,29 @@ impl LayoutInternal { pub type Layout = LayoutInternal; impl Layout { + pub fn width(&self) -> f32 { + self.max - self.min + } + + pub fn height(&self) -> f32 { + (0..self.nodes.len()) + .map(|j| self.slice_height(j)) + .sum::() + + 1.0 + } + + pub fn slice_height(&self, j: usize) -> f32 { + self.nodes[j] + .iter() + .map(|n| match n { + Node::Atom(_) => 0.0, + Node::Thunk(body) => body.height(), + }) + .max_by(|x, y| x.partial_cmp(y).unwrap()) + .unwrap() + + 1.0 + } + fn from_solution(layout: LayoutInternal, solution: &impl Solution) -> Self { Layout { min: solution.value(layout.min) as f32, diff --git a/sd-graphics/src/render.rs b/sd-graphics/src/render.rs index c9e9782..e7125e2 100644 --- a/sd-graphics/src/render.rs +++ b/sd-graphics/src/render.rs @@ -1,7 +1,6 @@ use epaint::{ emath::{Align2, RectTransform}, - vec2, CircleShape, Color32, CubicBezierShape, Fonts, Pos2, Rect, RectShape, Rounding, Shape, - Stroke, Vec2, + vec2, CircleShape, Color32, CubicBezierShape, Fonts, Pos2, Rect, Rounding, Shape, Stroke, Vec2, }; use sd_core::monoidal::{MonoidalGraph, MonoidalOp}; @@ -10,7 +9,6 @@ use crate::layout::Layout; pub const SCALE: f32 = 50.0; pub const STROKE_WIDTH: f32 = 1.0; -pub const BOX_SIZE: Vec2 = vec2(20.0, 20.0); pub const RADIUS_COPY: f32 = 5.0; pub const RADIUS_OPERATION: f32 = 10.0; @@ -18,6 +16,22 @@ pub fn default_stroke() -> Stroke { Stroke::new(STROKE_WIDTH, Color32::BLACK) } +// Specifies how to transform a layout position to a screen position. +struct Transform { + layout_bounds: Vec2, + bounds: Vec2, + to_screen: RectTransform, +} + +impl Transform { + fn apply(&self, x: f32, y: f32) -> Pos2 { + // Scale by a constant and translate to the centre of the bounding box. + self.to_screen.transform_pos( + Pos2::new(x * SCALE, y * SCALE) + (self.bounds - self.layout_bounds * SCALE) / 2.0, + ) + } +} + pub fn render( layout: &Layout, graph: &MonoidalGraph, @@ -25,37 +39,39 @@ pub fn render( bounds: Vec2, to_screen: RectTransform, ) -> Vec { - let n = graph.slices.len(); - - let min_x = layout.min; - let max_x = layout.max; - let height = n as f32 + 1.0; - - // Scale by a constant and translate to the centre of the bounding box. - let pos2 = |x: f32, y: f32| { - to_screen.transform_pos(Pos2::new( - (x - min_x) * SCALE + (bounds.x - (max_x - min_x) * SCALE) / 2.0, - y * SCALE + (bounds.y - height * SCALE) / 2.0, - )) + let transform = Transform { + bounds, + to_screen, + layout_bounds: vec2(layout.width(), layout.height()), }; - let mut shapes: Vec = Vec::new(); + let mut shapes = Vec::default(); + generate_shapes(&mut shapes, 0.0, layout, graph, fonts, &transform); + shapes +} +fn generate_shapes( + shapes: &mut Vec, + mut y_offset: f32, + layout: &Layout, + graph: &MonoidalGraph, + fonts: &Fonts, + transform: &Transform, +) { // Source for &x in layout.inputs() { - let start = pos2(x, 0.0); - let end = pos2(x, 0.5); + let start = transform.apply(x, y_offset); + let end = transform.apply(x, y_offset + 0.5); shapes.push(Shape::line_segment([start, end], default_stroke())); } - // Target - for &x in layout.outputs() { - let start = pos2(x, n as f32 + 0.5); - let end = pos2(x, n as f32 + 1.0); - shapes.push(Shape::line_segment([start, end], default_stroke())); - } + y_offset += 0.5; for (j, slice) in graph.slices.iter().enumerate() { + let slice_height = layout.slice_height(j); + let y_in = y_offset; + let y_out = y_offset + slice_height; + let mut offset_i = 0; let mut offset_o = 0; for (i, (op, _)) in slice.ops.iter().enumerate() { @@ -66,16 +82,12 @@ pub fn render( let x_ins = &layout.wires[j][offset_i..offset_i + ni]; let x_outs = &layout.wires[j + 1][offset_o..offset_o + no]; - let y_op = j as f32 + 1.0; - let y_in = j as f32 + 0.5; - let y_out = j as f32 + 1.5; - match op { MonoidalOp::Swap => { - let in1 = pos2(x_ins[0], y_in); - let in2 = pos2(x_ins[1], y_in); - let out1 = pos2(x_outs[0], y_out); - let out2 = pos2(x_outs[1], y_out); + let in1 = transform.apply(x_ins[0], y_in); + let in2 = transform.apply(x_ins[1], y_in); + let out1 = transform.apply(x_outs[0], y_out); + let out2 = transform.apply(x_outs[1], y_out); shapes.push(Shape::CubicBezier(CubicBezierShape::from_points_stroke( vertical_out_vertical_in(in1, out2), @@ -90,34 +102,38 @@ pub fn render( default_stroke(), ))); } - MonoidalOp::Thunk { .. } => { + MonoidalOp::Thunk { body, .. } => { let x_op = x_op.unwrap_thunk(); + let diff = (slice_height - x_op.height()) as f32 / 2.0; + let y_min = y_in + diff; + let y_max = y_out - diff; for &x in x_ins { - let thunk = pos2(x, y_op); - let input = pos2(x, y_in); + let thunk = transform.apply(x, y_min); + let input = transform.apply(x, y_in); shapes.push(Shape::line_segment([input, thunk], default_stroke())); } for &x in x_outs { - let thunk = pos2(x, y_op); - let output = pos2(x, y_out); + let thunk = transform.apply(x, y_max); + let output = transform.apply(x, y_out); shapes.push(Shape::line_segment([thunk, output], default_stroke())); } - shapes.push(Shape::Rect(RectShape { - rect: Rect::from_min_max( - pos2(x_op.min, y_op) - BOX_SIZE / 2.0, - pos2(x_op.max, y_op) + BOX_SIZE / 2.0, + shapes.push(Shape::rect_stroke( + Rect::from_min_max( + transform.apply(x_op.min, y_min), + transform.apply(x_op.max, y_max), ), - rounding: Rounding::none(), - fill: Color32::WHITE, - stroke: default_stroke(), - })); + Rounding::none(), + default_stroke(), + )); + generate_shapes(shapes, y_min, x_op, body, fonts, transform); } _ => { let x_op = *x_op.unwrap_atom(); - let center = pos2(x_op, y_op); + let y_op = (y_in + y_out) / 2.0; + let center = transform.apply(x_op, y_op); for &x in x_ins { - let input = pos2(x, y_in); + let input = transform.apply(x, y_in); shapes.push(Shape::CubicBezier(CubicBezierShape::from_points_stroke( vertical_out_horizontal_in(input, center), false, @@ -127,7 +143,7 @@ pub fn render( } for &x in x_outs { - let output = pos2(x, y_out); + let output = transform.apply(x, y_out); shapes.push(Shape::CubicBezier(CubicBezierShape::from_points_stroke( horizontal_out_vertical_in(center, output), false, @@ -164,9 +180,16 @@ pub fn render( offset_i += ni; offset_o += no; } + + y_offset = y_out; } - shapes + // Target + for &x in layout.outputs() { + let start = transform.apply(x, y_offset); + let end = transform.apply(x, y_offset + 0.5); + shapes.push(Shape::line_segment([start, end], default_stroke())); + } } fn vertical_out_horizontal_in(start: Pos2, end: Pos2) -> [Pos2; 4] {