diff --git a/crates/haku/src/render.rs b/crates/haku/src/render.rs index e91bea9..6232863 100644 --- a/crates/haku/src/render.rs +++ b/crates/haku/src/render.rs @@ -1,6 +1,7 @@ use alloc::vec::Vec; use tiny_skia::{ - BlendMode, Color, LineCap, Paint, PathBuilder, Pixmap, Shader, Stroke as SStroke, Transform, + BlendMode, Color, LineCap, Paint, PathBuilder, Pixmap, Rect, Shader, Stroke as SStroke, + Transform, }; use crate::{ @@ -132,6 +133,46 @@ impl<'a> Renderer<'a> { None, ); } + + Shape::Rect(position, size) => { + let mut pb = PathBuilder::new(); + if let Some(rect) = + tiny_skia::Rect::from_xywh(position.x, position.y, size.x, size.y) + { + pb.push_rect(rect); + } + let path = pb.finish().unwrap(); + + self.pixmap_mut().stroke_path( + &path, + &paint, + &SStroke { + width: stroke.thickness, + line_cap: LineCap::Square, + ..Default::default() + }, + transform, + None, + ); + } + + Shape::Circle(position, radius) => { + let mut pb = PathBuilder::new(); + pb.push_circle(position.x, position.y, radius); + let path = pb.finish().unwrap(); + + self.pixmap_mut().stroke_path( + &path, + &paint, + &SStroke { + width: stroke.thickness, + line_cap: LineCap::Square, + ..Default::default() + }, + transform, + None, + ); + } } Ok(()) diff --git a/crates/haku/src/system.rs b/crates/haku/src/system.rs index 29f3a2b..159ead0 100644 --- a/crates/haku/src/system.rs +++ b/crates/haku/src/system.rs @@ -102,7 +102,7 @@ pub mod fns { use alloc::vec::Vec; use crate::{ - value::{List, Ref, Rgba, Scribble, Shape, Stroke, Value, Vec4}, + value::{List, Ref, Rgba, Scribble, Shape, Stroke, Value, Vec2, Vec4}, vm::{Exception, FnArgs, Vm}, }; @@ -139,6 +139,8 @@ pub mod fns { 0xc0 "to-shape" => to_shape_f, 0xc1 "line" => line, + 0xc2 "rect" => rect, + 0xc3 "circle" => circle, 0xe0 "stroke" => stroke, } } @@ -410,7 +412,7 @@ pub mod fns { None } } - Value::Vec4(vec) => Some(Shape::Point(vec)), + Value::Vec4(vec) => Some(Shape::Point(vec.into())), } } @@ -436,7 +438,50 @@ pub mod fns { let start = args.get_vec4(vm, 0, ERROR)?; let end = args.get_vec4(vm, 1, ERROR)?; - let id = vm.create_ref(Ref::Shape(Shape::Line(start, end)))?; + let id = vm.create_ref(Ref::Shape(Shape::Line(start.into(), end.into())))?; + Ok(Value::Ref(id)) + } + + pub fn rect(vm: &mut Vm, args: FnArgs) -> Result { + static ARGS2: &str = "arguments to 2-argument (rect) must be (vec)"; + static ARGS4: &str = "arguments to 4-argument (rect) must be numbers"; + + let (position, size) = match args.num() { + 2 => (args.get_vec4(vm, 0, ARGS2)?.into(), args.get_vec4(vm, 1, ARGS2)?.into()), + 4 => ( + Vec2 { + x: args.get_number(vm, 0, ARGS4)?, + y: args.get_number(vm, 1, ARGS4)?, + }, + Vec2 { + x: args.get_number(vm, 2, ARGS4)?, + y: args.get_number(vm, 3, ARGS4)?, + }, + ), + _ => return Err(vm.create_exception("(rect) expects 2 arguments (rect position size) or 4 arguments (rect x y width height)")) + }; + + let id = vm.create_ref(Ref::Shape(Shape::Rect(position, size)))?; + Ok(Value::Ref(id)) + } + + pub fn circle(vm: &mut Vm, args: FnArgs) -> Result { + static ARGS2: &str = "arguments to 2-argument (circle) must be (vec) and a number"; + static ARGS3: &str = "arguments to 3-argument (circle) must be numbers"; + + let (position, radius) = match args.num() { + 2 => (args.get_vec4(vm, 0, ARGS2)?.into(), args.get_number(vm, 1, ARGS2)?), + 3 => ( + Vec2 { + x: args.get_number(vm, 0, ARGS3)?, + y: args.get_number(vm, 1, ARGS3)?, + }, + args.get_number(vm, 2, ARGS3)? + ), + _ => return Err(vm.create_exception("(circle) expects 2 arguments (circle position radius) or 3 arguments (circle x y radius)")) + }; + + let id = vm.create_ref(Ref::Shape(Shape::Circle(position, radius)))?; Ok(Value::Ref(id)) } diff --git a/crates/haku/src/value.rs b/crates/haku/src/value.rs index 6490eaa..36761ee 100644 --- a/crates/haku/src/value.rs +++ b/crates/haku/src/value.rs @@ -67,6 +67,21 @@ impl From for Value { } } +#[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] +pub struct Vec2 { + pub x: f32, + pub y: f32, +} + +impl From for Vec2 { + fn from(value: Vec4) -> Self { + Self { + x: value.x, + y: value.y, + } + } +} + #[derive(Debug, Clone, Copy, PartialEq, PartialOrd, Default)] pub struct Vec4 { pub x: f32, @@ -151,8 +166,10 @@ pub struct List { #[derive(Debug, Clone)] pub enum Shape { - Point(Vec4), - Line(Vec4, Vec4), + Point(Vec2), + Line(Vec2, Vec2), + Rect(Vec2, Vec2), + Circle(Vec2, f32), } #[derive(Debug, Clone)] diff --git a/crates/haku/src/vm.rs b/crates/haku/src/vm.rs index 71bf5e9..49e1b10 100644 --- a/crates/haku/src/vm.rs +++ b/crates/haku/src/vm.rs @@ -32,6 +32,8 @@ pub struct Vm { #[derive(Debug, Clone, Copy)] pub struct VmImage { + stack: usize, + call_stack: usize, refs: usize, defs: usize, fuel: usize, @@ -76,6 +78,8 @@ impl Vm { "cannot image VM while running code" ); VmImage { + stack: self.stack.len(), + call_stack: self.call_stack.len(), refs: self.refs.len(), defs: self.defs.len(), fuel: self.fuel, @@ -84,10 +88,21 @@ impl Vm { } pub fn restore_image(&mut self, image: &VmImage) { - assert!( - self.stack.is_empty() && self.call_stack.is_empty(), - "cannot restore VM image while running code" - ); + // NOTE: My initial assumption here was that system functions should not be able to restore + // the VM if it's running code. + // Turns out that was a bad assumption to make, because the VM may fail with an exception, + // in which case the call stack and stack may not be empty. + // assert!( + // self.stack.is_empty() && self.call_stack.is_empty(), + // "cannot restore VM image while running code" + // ); + + self.stack.resize_with(image.stack, || { + panic!("image must be a subset of the current VM") + }); + self.call_stack.resize_with(image.call_stack, || { + panic!("image must be a subset of the current VM") + }); self.refs.resize_with(image.refs, || { panic!("image must be a subset of the current VM") }); @@ -95,6 +110,7 @@ impl Vm { panic!("image must be a subset of the current VM") }); self.fuel = image.fuel; + self.memory = image.memory; } pub fn apply_defs(&mut self, defs: &Defs) {