This repository has no description
0

Configure Feed

Select the types of activity you want to include in your feed.

๐Ÿšš Rename Object -> Shape and ColoredObject -> Object

+621 -627
+3 -3
examples/dna-analysis-machine/src/main.rs
··· 47 47 48 48 // Red dot 49 49 50 - let red_dot = Object::BigCircle( 50 + let red_dot = Shape::BigCircle( 51 51 Region::from_topright(draw_in.topright().translated(-3, 1), (4, 3)) 52 52 .unwrap() 53 53 .random_point(&mut rng), ··· 66 66 continue; 67 67 } 68 68 if rng.random() { 69 - Object::BigCircle(point) 69 + Shape::BigCircle(point) 70 70 } else { 71 - Object::Rectangle(point, point) 71 + Shape::Rectangle(point, point) 72 72 } 73 73 .filled(White.hatches( 74 74 Angle::from_degrees(45.0),
+2 -2
examples/playground/src/main.rs
··· 59 59 .filter(|&&p| p != point) 60 60 .filter(|&&p| p.distance_to(&point).norm() <= 5.0) 61 61 .filter(|&&p| { 62 - !connections.has_object_that(|obj| match obj.object { 63 - Line(..) => obj.object.intersects_with(Line(point, p, 0.)), 62 + !connections.has_object_that(|co| match co.shape { 63 + Line(..) => co.shape.intersects_with(Line(point, p, 0.)), 64 64 _ => false, 65 65 }) 66 66 })
+1 -1
examples/schedule-hell/src/main.rs
··· 93 93 let world = canvas.world_region; 94 94 canvas.root().set( 95 95 "credits text", 96 - Object::Text( 96 + Shape::Text( 97 97 world.start.translated(2, 2), 98 98 "Postamble / Schedule Hell".into(), 99 99 12.0,
+6 -6
examples/schedule-hell/src/scenes/backbone.rs
··· 74 74 { 75 75 canvas.root().set( 76 76 format!("grid-rows-{point}"), 77 - Object::Line( 77 + Shape::Line( 78 78 P(point.x(), world.topleft().y()), 79 79 P(point.x(), world.bottomleft().y() + 1), 80 80 grid_thickness * 0.75, ··· 88 88 { 89 89 canvas.root().set( 90 90 format!("grid-cols-{point}"), 91 - Object::Line( 91 + Shape::Line( 92 92 P(world.topleft().x(), point.y()), 93 93 P(world.bottomright().x() + 1, point.y()), 94 94 grid_thickness * 0.75, ··· 102 102 for point in world.enlarged(1, 1) { 103 103 occlusions.set( 104 104 format!("occlusion-{point}"), 105 - Object::Dot(point).colored(Color::Black), 105 + Shape::Dot(point).colored(Color::Black), 106 106 ); 107 107 } 108 108 ··· 111 111 for point in world { 112 112 flickers.set( 113 113 format!("crosses-SWNE-{point}"), 114 - Object::Line(point, point.translated(1, 1), grid_thickness) 114 + Shape::Line(point, point.translated(1, 1), grid_thickness) 115 115 .colored(Color::Purple) 116 116 .flickering(rng, 0.25) 117 117 .clipped_to((point, point)), 118 118 ); 119 119 flickers.set( 120 120 format!("crosses-NWSE-{point}"), 121 - Object::Line( 121 + Shape::Line( 122 122 point.translated(0, 1), 123 123 point.translated(1, 0), 124 124 grid_thickness, ··· 135 135 for point in world.enlarged(1, 1) { 136 136 flickers_occlusions.set( 137 137 format!("crosses-occlusions-{point}"), 138 - Object::Dot(point).colored(Color::Black), 138 + Shape::Dot(point).colored(Color::Black), 139 139 ) 140 140 } 141 141
+7 -7
examples/schedule-hell/src/scenes/first_break.rs
··· 1 1 use crate::State; 2 2 use shapemaker::*; 3 3 4 - fn random_shape(at: Point) -> ColoredObject { 4 + fn random_shape(at: Point) -> Object { 5 5 let thickness = 7.0; 6 6 match rand::random_range(1..=5) { 7 - 1 => Object::BigCircle(at), 8 - 2 => Object::CurveInward(at, at.translated(1, 1), thickness), 9 - 3 => Object::CurveOutward(at, at.translated(1, 1), thickness), 10 - 4 => Object::Line(at, at.translated(1, 1), thickness), 11 - 5 => Object::Line(at.translated(0, 1), at.translated(1, 0), thickness), 7 + 1 => Shape::BigCircle(at), 8 + 2 => Shape::CurveInward(at, at.translated(1, 1), thickness), 9 + 3 => Shape::CurveOutward(at, at.translated(1, 1), thickness), 10 + 4 => Shape::Line(at, at.translated(1, 1), thickness), 11 + 5 => Shape::Line(at.translated(0, 1), at.translated(1, 0), thickness), 12 12 _ => panic!("souhldn't happend, update rand:: call"), 13 13 } 14 14 .colored(Color::Black) ··· 45 45 46 46 tiling.add( 47 47 format!("tile{i}"), 48 - Object::Rectangle(point, point).colored(bgcolor), 48 + Shape::Rectangle(point, point).colored(bgcolor), 49 49 ); 50 50 51 51 if shapes_area.contains(&point) {
+1 -1
examples/schedule-hell/src/scenes/intro.rs
··· 41 41 42 42 ctx.animate(200, &|t, canvas, _| { 43 43 canvas.layer("anchor kick")?.objects.values_mut().for_each( 44 - |ColoredObject { fill, .. }| { 44 + |Object { fill, .. }| { 45 45 *fill = fill.opacify(1.0 - t); 46 46 }, 47 47 );
+2 -2
examples/schedule-hell/src/scenes/starry_sky.rs
··· 56 56 for point in background_stars_in { 57 57 canvas 58 58 .root() 59 - .add_anon(Object::Dot(point).filled(Fill::Translucent( 59 + .add_anon(Shape::Dot(point).filled(Fill::Translucent( 60 60 Color::White, 61 61 rand::random_range(if rand::random_bool(0.01) { 62 62 0.8..=1.0 ··· 74 74 75 75 for _ in 1..=rand::random_range(2..=5) { 76 76 layer.add_anon( 77 - Object::random( 77 + Shape::random( 78 78 &mut rand::rng(), 79 79 &Region::from_center_and_size( 80 80 at.rotated(&world.center(), rotation),
+2 -2
examples/specimen/src/main.rs
··· 48 48 ]; 49 49 50 50 for (color, point) in std::iter::zip(all_colors, canvas.world_region) { 51 - Object::Rectangle(point, point) 51 + Shape::Rectangle(point, point) 52 52 .colored(color) 53 53 .add_to(canvas.root()); 54 54 } ··· 62 62 canvas.set_background(Color::White); 63 63 64 64 for point in canvas.world_region { 65 - Object::Dot(point) 65 + Shape::Dot(point) 66 66 .colored(Color::Black) 67 67 .add_to(canvas.root()); 68 68 }
+3 -3
src/geometry/region.rs
··· 1 - use crate::{Object, Point}; 1 + use crate::{Shape, Point}; 2 2 use anyhow::{Error, Result, anyhow, format_err}; 3 3 #[cfg(feature = "web")] 4 4 use wasm_bindgen::prelude::*; ··· 87 87 Ok(()) 88 88 } 89 89 90 - pub fn rectangle(&self) -> Object { 91 - Object::Rectangle(self.start, self.end) 90 + pub fn rectangle(&self) -> Shape { 91 + Shape::Rectangle(self.start, self.end) 92 92 } 93 93 94 94 pub fn center_coords(&self, cell_size: usize) -> (f32, f32) {
+11 -12
src/graphics/canvas.rs
··· 7 7 use measure_time::debug_time; 8 8 9 9 use crate::{ 10 - Color, ColorMapping, Fill, Filter, Layer, Object, ObjectSizes, Point, Region, 10 + Color, ColorMapping, Fill, Filter, Layer, ObjectSizes, Point, Region, Shape, 11 11 fonts::{FontOptions, load_fonts}, 12 12 }; 13 13 14 - use super::ColoredObject; 14 + use super::Object; 15 15 16 16 #[derive(Debug, Clone)] 17 17 pub struct Canvas { ··· 194 194 &mut self, 195 195 layer: &str, 196 196 name: &str, 197 - object: Object, 197 + shape: Shape, 198 198 fill: Option<Fill>, 199 199 ) -> Result<()> { 200 - self.layer(layer)? 201 - .set(name, ColoredObject::from((object, fill))); 200 + self.layer(layer)?.set(name, Object::from((shape, fill))); 202 201 203 202 Ok(()) 204 203 } ··· 317 316 318 317 layer.set( 319 318 format!("{}_corner_sw", region).as_str(), 320 - Object::Dot(region.topleft()).colored(color), 319 + Shape::Dot(region.topleft()).colored(color), 321 320 ); 322 321 layer.set( 323 322 format!("{}_corner_se", region).as_str(), 324 - Object::Dot(region.topright().translated(1, 0)).colored(color), 323 + Shape::Dot(region.topright().translated(1, 0)).colored(color), 325 324 ); 326 325 layer.set( 327 326 format!("{}_corner_ne", region).as_str(), 328 - Object::Dot(region.bottomright().translated(1, 1)).colored(color), 327 + Shape::Dot(region.bottomright().translated(1, 1)).colored(color), 329 328 ); 330 329 layer.set( 331 330 format!("{}_corner_nw", region).as_str(), 332 - Object::Dot(region.bottomleft().translated(0, 1)).colored(color), 331 + Shape::Dot(region.bottomleft().translated(0, 1)).colored(color), 333 332 ); 334 333 layer.set( 335 334 format!("{}_region", region).as_str(), 336 - Object::Rectangle(region.start, region.end) 335 + Shape::Rectangle(region.start, region.end) 337 336 .filled(Fill::Translucent(color, 0.25)), 338 337 ) 339 338 } ··· 351 350 352 351 layer.set( 353 352 format!("grid_vertical_{x}"), 354 - Object::Line(Point::Corner(x, 0), Point::Corner(x, ymax), 1.0) 353 + Shape::Line(Point::Corner(x, 0), Point::Corner(x, ymax), 1.0) 355 354 .colored(color) 356 355 .opacified(0.25), 357 356 ); 358 357 359 358 layer.set( 360 359 format!("grid_horizontal_{y}"), 361 - Object::Line(Point::Corner(0, y), Point::Corner(xmax, y), 1.0) 360 + Shape::Line(Point::Corner(0, y), Point::Corner(xmax, y), 1.0) 362 361 .colored(color) 363 362 .opacified(0.25), 364 363 );
+21 -29
src/graphics/layer.rs
··· 1 - use crate::{ 2 - ColoredObject, Fill, Filter, ObjectSizes, Point, Region, Toggleable, 3 - }; 1 + use crate::{Fill, Filter, Object, ObjectSizes, Point, Region, Toggleable}; 4 2 use std::{collections::HashMap, fmt::Display}; 5 3 6 4 #[derive(Debug, Clone, Default)] 7 5 // #[wasm_bindgen(getter_with_clone)] 8 6 pub struct Layer { 9 7 pub object_sizes: ObjectSizes, 10 - pub objects: HashMap<String, ColoredObject>, 8 + pub objects: HashMap<String, Object>, 11 9 pub name: String, 12 10 pub hidden: bool, 13 11 } ··· 34 32 self.hidden.toggle(); 35 33 } 36 34 37 - pub fn object(&mut self, name: &str) -> &mut ColoredObject { 35 + pub fn object(&mut self, name: &str) -> &mut Object { 38 36 self.safe_object(name).unwrap() 39 37 } 40 38 41 - pub fn safe_object(&mut self, name: &str) -> Option<&mut ColoredObject> { 39 + pub fn safe_object(&mut self, name: &str) -> Option<&mut Object> { 42 40 self.objects.get_mut(name) 43 41 } 44 42 45 43 pub fn objects_in( 46 44 &mut self, 47 45 region: Region, 48 - ) -> impl Iterator<Item = (&String, &mut ColoredObject)> { 46 + ) -> impl Iterator<Item = (&String, &mut Object)> { 49 47 self.objects 50 48 .iter_mut() 51 - .filter(move |(_, obj)| obj.object.region().within(&region)) 49 + .filter(move |(_, obj)| obj.shape.region().within(&region)) 52 50 } 53 51 54 - pub fn object_at(&mut self, point: Point) -> Option<&mut ColoredObject> { 52 + pub fn object_at(&mut self, point: Point) -> Option<&mut Object> { 55 53 self.objects 56 54 .values_mut() 57 - .find(|obj| obj.object.region().start == point) 55 + .find(|obj| obj.shape.region().start == point) 58 56 } 59 57 60 - pub fn has_object_that(&self, pred: impl Fn(&ColoredObject) -> bool) -> bool { 58 + pub fn has_object_that(&self, pred: impl Fn(&Object) -> bool) -> bool { 61 59 self.objects.values().any(|obj| pred(obj)) 62 60 } 63 61 ··· 71 69 } 72 70 73 71 pub fn remove_all_objects_in(&mut self, region: &Region) { 74 - self.objects.retain(|_, ColoredObject { object, .. }| { 72 + self.objects.retain(|_, Object { shape: object, .. }| { 75 73 !object.region().within(region) 76 74 }) 77 75 } ··· 91 89 pub fn move_all_objects(&mut self, dx: i32, dy: i32) { 92 90 self.objects 93 91 .iter_mut() 94 - .for_each(|(_, ColoredObject { object, .. })| { 92 + .for_each(|(_, Object { shape: object, .. })| { 95 93 object.translate(dx, dy) 96 94 }); 97 95 } 98 96 99 - pub fn add(&mut self, name: impl Display, object: impl Into<ColoredObject>) { 97 + pub fn add(&mut self, name: impl Display, object: impl Into<Object>) { 100 98 let name_str = format!("{}", name); 101 99 102 100 if self.objects.contains_key(&name_str) { ··· 106 104 self.set(name_str, object); 107 105 } 108 106 109 - pub fn add_anon(&mut self, object: impl Into<ColoredObject>) { 107 + pub fn add_anon(&mut self, object: impl Into<Object>) { 110 108 self.add(format!("anon-{}", self.objects.len()), object); 111 109 } 112 110 113 111 pub fn add_many( 114 112 &mut self, 115 - objects: impl IntoIterator<Item = (impl Display, ColoredObject)>, 113 + objects: impl IntoIterator<Item = (impl Display, Object)>, 116 114 ) { 117 115 for (name, obj) in objects { 118 116 self.add(name, obj); 119 117 } 120 118 } 121 119 122 - pub fn add_many_anon( 123 - &mut self, 124 - objects: impl IntoIterator<Item = ColoredObject>, 125 - ) { 120 + pub fn add_many_anon(&mut self, objects: impl IntoIterator<Item = Object>) { 126 121 for obj in objects { 127 122 self.add_anon(obj); 128 123 } 129 124 } 130 125 131 - pub fn set(&mut self, name: impl Display, object: impl Into<ColoredObject>) { 126 + pub fn set(&mut self, name: impl Display, object: impl Into<Object>) { 132 127 let name_str = format!("{}", name); 133 128 134 129 self.objects.insert(name_str, object.into()); ··· 152 147 self.objects.remove(name); 153 148 } 154 149 155 - pub fn replace_object(&mut self, name: &str, object: ColoredObject) { 150 + pub fn replace_object(&mut self, name: &str, object: Object) { 156 151 self.remove_object(name); 157 152 self.add(name, object); 158 153 } 159 154 160 - pub fn add_objects( 161 - &mut self, 162 - objects: impl IntoIterator<Item = ColoredObject>, 163 - ) { 155 + pub fn add_objects(&mut self, objects: impl IntoIterator<Item = Object>) { 164 156 for obj in objects { 165 157 self.add_anon(obj); 166 158 } ··· 169 161 pub fn objects_with_tag( 170 162 &mut self, 171 163 tag: impl Display, 172 - ) -> impl Iterator<Item = (&String, &mut ColoredObject)> { 164 + ) -> impl Iterator<Item = (&String, &mut Object)> { 173 165 let tag_str = format!("{}", tag); 174 166 self.objects 175 167 .iter_mut() ··· 179 171 pub fn tag_objects( 180 172 &mut self, 181 173 tag: impl Display, 182 - objects: impl Fn(&String, &ColoredObject) -> bool, 174 + objects: impl Fn(&String, &Object) -> bool, 183 175 ) { 184 176 let tag_str = format!("{}", tag); 185 177 for (_, obj) in ··· 198 190 } 199 191 } 200 192 201 - impl ColoredObject { 193 + impl Object { 202 194 pub fn add_to(self, layer: &mut Layer) { 203 195 layer.add_anon(self); 204 196 }
+3 -1
src/graphics/mod.rs
··· 5 5 pub mod layer; 6 6 pub mod objects; 7 7 pub mod region; 8 + pub mod shapes; 8 9 pub mod transform; 9 10 10 11 pub use canvas::Canvas; ··· 12 13 pub use fill::{Fill, FillOperations}; 13 14 pub use filter::{Filter, FilterType}; 14 15 pub use layer::Layer; 15 - pub use objects::{ColoredObject, LineSegment, Object, ObjectSizes}; 16 + pub use objects::{Object, ObjectSizes}; 17 + pub use shapes::{LineSegment, Shape}; 16 18 pub use transform::{Transformation, TransformationType};
+26 -249
src/graphics/objects.rs
··· 1 - use crate::{Angle, Containable, Fill, Filter, Point, Region, Transformation}; 2 - use anyhow::anyhow; 1 + use super::shapes::Shape; 2 + use crate::{Angle, Fill, Filter, Region, Transformation}; 3 3 use itertools::Itertools; 4 4 use std::fmt::Display; 5 5 #[cfg(feature = "web")] ··· 7 7 8 8 use super::{Color, fill::FillOperations}; 9 9 10 - #[derive(Debug, Clone, PartialEq, Eq)] 11 - pub enum LineSegment { 12 - Straight(Point), 13 - InwardCurve(Point), 14 - OutwardCurve(Point), 15 - } 16 - 17 - #[derive(Debug, Clone)] 18 - pub enum Object { 19 - Polygon(Point, Vec<LineSegment>), 20 - Line(Point, Point, f32), 21 - CurveOutward(Point, Point, f32), 22 - CurveInward(Point, Point, f32), 23 - SmallCircle(Point), 24 - BigDot(Point), 25 - Dot(Point), 26 - BigCircle(Point), 27 - Text(Point, String, f32), 28 - CenteredText(Point, String, f32), 29 - // FittedText(Region, String), 30 - Rectangle(Point, Point), 31 - Image(Region, String), 32 - RawSVG(String), 33 - // Tiling(Region, Box<Object>), 34 - } 35 - 36 - impl Object { 37 - pub fn filled(self, fill: Fill) -> ColoredObject { 38 - ColoredObject::from((self, Some(fill))) 10 + impl Shape { 11 + pub fn filled(self, fill: Fill) -> Object { 12 + Object::from((self, Some(fill))) 39 13 } 40 14 41 - pub fn colored(self, color: Color) -> ColoredObject { 42 - ColoredObject::from((self, None)).colored(color) 15 + pub fn colored(self, color: Color) -> Object { 16 + Object::from((self, None)).colored(color) 43 17 } 44 18 45 - pub fn filtered(self, filter: Filter) -> ColoredObject { 46 - ColoredObject::from((self, None)).filtered(filter) 19 + pub fn filtered(self, filter: Filter) -> Object { 20 + Object::from((self, None)).filtered(filter) 47 21 } 48 22 49 - pub fn transform(self, transformation: Transformation) -> ColoredObject { 50 - ColoredObject::from((self, None)).transformed(transformation) 23 + pub fn transform(self, transformation: Transformation) -> Object { 24 + Object::from((self, None)).transformed(transformation) 51 25 } 52 26 } 53 27 54 28 #[derive(Debug, Clone)] 55 - pub struct ColoredObject { 56 - pub object: Object, 29 + pub struct Object { 30 + pub shape: Shape, 57 31 pub fill: Option<Fill>, 58 32 pub filters: Vec<Filter>, 59 33 pub transformations: Vec<Transformation>, ··· 61 35 pub clip_to: Option<Region>, 62 36 } 63 37 64 - impl ColoredObject { 38 + impl Object { 65 39 pub fn filtered(mut self, filter: Filter) -> Self { 66 40 self.filters.push(filter); 67 41 self ··· 123 97 } 124 98 125 99 pub fn region(&self) -> Region { 126 - self.object.region() 100 + self.shape.region() 127 101 } 128 102 129 103 pub fn tag(&mut self, tag: impl Display) { ··· 146 120 } 147 121 } 148 122 149 - impl std::fmt::Display for ColoredObject { 123 + impl std::fmt::Display for Object { 150 124 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 151 - let ColoredObject { 152 - object, 125 + let Object { 126 + shape: object, 153 127 fill, 154 128 filters, 155 129 transformations, ··· 183 157 } 184 158 } 185 159 186 - impl From<Object> for ColoredObject { 187 - fn from(value: Object) -> Self { 188 - ColoredObject { 189 - object: value, 160 + impl From<Shape> for Object { 161 + fn from(value: Shape) -> Self { 162 + Object { 163 + shape: value, 190 164 fill: None, 191 165 filters: vec![], 192 166 transformations: vec![], ··· 196 170 } 197 171 } 198 172 199 - impl From<(Object, Option<Fill>)> for ColoredObject { 200 - fn from((object, fill): (Object, Option<Fill>)) -> Self { 201 - ColoredObject { 202 - object, 173 + impl From<(Shape, Option<Fill>)> for Object { 174 + fn from((object, fill): (Shape, Option<Fill>)) -> Self { 175 + Object { 176 + shape: object, 203 177 fill, 204 178 filters: vec![], 205 179 transformations: vec![], ··· 228 202 } 229 203 } 230 204 } 231 - 232 - impl Object { 233 - pub fn translate(&mut self, dx: i32, dy: i32) { 234 - match self { 235 - Object::Polygon(start, lines) => { 236 - start.translate(dx, dy); 237 - for line in lines { 238 - match line { 239 - LineSegment::InwardCurve(anchor) 240 - | LineSegment::OutwardCurve(anchor) 241 - | LineSegment::Straight(anchor) => { 242 - anchor.translate(dx, dy) 243 - } 244 - } 245 - } 246 - } 247 - Object::Line(start, end, _) 248 - | Object::CurveInward(start, end, _) 249 - | Object::CurveOutward(start, end, _) 250 - | Object::Rectangle(start, end) => { 251 - start.translate(dx, dy); 252 - end.translate(dx, dy); 253 - } 254 - Object::Text(anchor, _, _) 255 - | Object::CenteredText(anchor, ..) 256 - | Object::Dot(anchor) 257 - | Object::BigDot(anchor) => anchor.translate(dx, dy), 258 - Object::BigCircle(center) | Object::SmallCircle(center) => { 259 - center.translate(dx, dy) 260 - } 261 - Object::Image(region, ..) => region.translate(dx, dy), 262 - Object::RawSVG(_) => { 263 - unimplemented!() 264 - } 265 - } 266 - } 267 - 268 - pub fn translate_with(&mut self, delta: (i32, i32)) { 269 - self.translate(delta.0, delta.1) 270 - } 271 - 272 - pub fn teleport(&mut self, x: i32, y: i32) { 273 - let (current_x, current_y) = self.region().start.xy::<i32>(); 274 - let delta_x = x - current_x; 275 - let delta_y = y - current_y; 276 - self.translate(delta_x, delta_y); 277 - } 278 - 279 - pub fn teleport_with(&mut self, position: (i32, i32)) { 280 - self.teleport(position.0, position.1) 281 - } 282 - 283 - pub fn region(&self) -> Region { 284 - match self { 285 - Object::Polygon(start, lines) => { 286 - let mut region: Region = (start, start).into(); 287 - for line in lines { 288 - match line { 289 - LineSegment::InwardCurve(anchor) 290 - | LineSegment::OutwardCurve(anchor) 291 - | LineSegment::Straight(anchor) => { 292 - // println!( 293 - // "extending region {} with {}", 294 - // region, 295 - // Region::from((start, anchor)) 296 - // ); 297 - region = *region.max(&(start, anchor).into()) 298 - } 299 - } 300 - } 301 - // println!("region for {:?} -> {}", self, region); 302 - region 303 - } 304 - Object::Line(s, e, _) 305 - | Object::CurveInward(s, e, _) 306 - | Object::CurveOutward(s, e, _) => { 307 - let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x()); 308 - 309 - let region = Region::new( 310 - (x1.min(x2), y1.min(y2)), 311 - (x1.max(x2), y1.max(y2)), 312 - ) 313 - .map_err(|e| { 314 - anyhow!("Could not construct region of {self:?}: {e:?}") 315 - }) 316 - .unwrap(); 317 - 318 - region.enlarged( 319 - if region.width() > 1 { -1 } else { 0 }, 320 - if region.height() > 1 { -1 } else { 0 }, 321 - ) 322 - } 323 - Object::Rectangle(start, end) => { 324 - Region::new(*start, *end).unwrap().enlarged(-1, -1) 325 - } 326 - Object::Text(anchor, _, _) 327 - | Object::CenteredText(anchor, ..) 328 - | Object::Dot(anchor) 329 - | Object::BigDot(anchor) 330 - | Object::SmallCircle(anchor) => anchor.region(), 331 - Object::BigCircle(center) => center.region(), 332 - Object::Image(region, ..) => *region, 333 - Object::RawSVG(_) => { 334 - unimplemented!() 335 - } 336 - } 337 - } 338 - 339 - pub fn fillable(&self) -> bool { 340 - !matches!( 341 - self, 342 - Object::Line(..) | Object::CurveInward(..) | Object::CurveOutward(..) 343 - ) 344 - } 345 - 346 - pub fn hatchable(&self) -> bool { 347 - self.fillable() && !matches!(self, Object::Dot(..)) 348 - } 349 - 350 - pub fn point_is_on_line(self, point: Point) -> bool { 351 - match (&self, point) { 352 - (Object::Line(s, e, _), Point::Corner(x, y)) => { 353 - if !self.region().contains(&point) { 354 - return false; 355 - } 356 - 357 - let (sx, sy) = s.xy::<f32>(); 358 - let (ex, ey) = e.xy::<f32>(); 359 - 360 - let m = (ey - sy) / (ex - sx); 361 - let p = sy - m * sx; 362 - 363 - (m * x as f32 + p) as usize == y 364 - } 365 - (Object::Line(..), _) => panic!("Point type not supported"), 366 - _ => panic!("{self:?} is not a line object"), 367 - } 368 - } 369 - 370 - // 371 - // ```rs 372 - // use shapemaker::{Object::Line, Point::Corner}; 373 - // let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0); 374 - // assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1))); 375 - // assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4))); 376 - // ``` 377 - pub fn intersects_with(&self, line: Object) -> bool { 378 - match (self, &line) { 379 - (&Object::Line(s1, e1, _), &Object::Line(s2, e2, _)) => { 380 - let (dx1, dy1) = e1 - s1; 381 - let (dx2, dy2) = e2 - s2; 382 - let (dx3, dy3) = s2 - s1; 383 - 384 - let det = dx1 * dy2 - dy1 * dx2; 385 - 386 - let det1 = dx1 * dy3 - dx3 * dy1; 387 - let det2 = dx2 * dy3 - dx3 * dy2; 388 - 389 - if det == 0 { 390 - if det1 != 0 || det2 != 0 { 391 - return false; 392 - } 393 - 394 - let (x1, y1) = s1.xy::<isize>(); 395 - let (x2, y2) = e1.xy::<isize>(); 396 - let (x3, y3) = s2.xy::<isize>(); 397 - 398 - if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) { 399 - return true; 400 - } 401 - 402 - if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) { 403 - return true; 404 - } 405 - 406 - return false; 407 - } 408 - 409 - let frac_less_than_one = |num: isize, den: isize| { 410 - if num.signum() != den.signum() { 411 - return false; 412 - } 413 - 414 - num.abs() <= den.abs() 415 - }; 416 - 417 - frac_less_than_one(det1, det) && frac_less_than_one(det2, det) 418 - } 419 - _ => { 420 - unimplemented!( 421 - "Intersection not implemented for {self:?} and {:?}", 422 - line.clone() 423 - ) 424 - } 425 - } 426 - } 427 - }
+219
src/graphics/shapes.rs
··· 1 + use self::Shape::*; 2 + use anyhow::anyhow; 3 + use crate::{Point, Region, Containable}; 4 + 5 + #[derive(Debug, Clone, PartialEq, Eq)] 6 + pub enum LineSegment { 7 + Straight(Point), 8 + InwardCurve(Point), 9 + OutwardCurve(Point), 10 + } 11 + 12 + #[derive(Debug, Clone)] 13 + pub enum Shape { 14 + Polygon(Point, Vec<LineSegment>), 15 + Line(Point, Point, f32), 16 + CurveOutward(Point, Point, f32), 17 + CurveInward(Point, Point, f32), 18 + SmallCircle(Point), 19 + BigDot(Point), 20 + Dot(Point), 21 + BigCircle(Point), 22 + Text(Point, String, f32), 23 + CenteredText(Point, String, f32), 24 + // FittedText(Region, String), 25 + Rectangle(Point, Point), 26 + Image(Region, String), 27 + RawSVG(String), 28 + // Tiling(Region, Box<Object>), 29 + } 30 + 31 + impl Shape { 32 + pub fn translate(&mut self, dx: i32, dy: i32) { 33 + match self { 34 + Polygon(start, lines) => { 35 + start.translate(dx, dy); 36 + for line in lines { 37 + match line { 38 + LineSegment::InwardCurve(anchor) 39 + | LineSegment::OutwardCurve(anchor) 40 + | LineSegment::Straight(anchor) => { 41 + anchor.translate(dx, dy) 42 + } 43 + } 44 + } 45 + } 46 + Line(start, end, _) 47 + | CurveInward(start, end, _) 48 + | CurveOutward(start, end, _) 49 + | Rectangle(start, end) => { 50 + start.translate(dx, dy); 51 + end.translate(dx, dy); 52 + } 53 + Text(anchor, _, _) 54 + | CenteredText(anchor, ..) 55 + | Dot(anchor) 56 + | BigDot(anchor) => anchor.translate(dx, dy), 57 + BigCircle(center) | SmallCircle(center) => center.translate(dx, dy), 58 + Image(region, ..) => region.translate(dx, dy), 59 + RawSVG(_) => { 60 + unimplemented!() 61 + } 62 + } 63 + } 64 + 65 + pub fn translate_with(&mut self, delta: (i32, i32)) { 66 + self.translate(delta.0, delta.1) 67 + } 68 + 69 + pub fn teleport(&mut self, x: i32, y: i32) { 70 + let (current_x, current_y) = self.region().start.xy::<i32>(); 71 + let delta_x = x - current_x; 72 + let delta_y = y - current_y; 73 + self.translate(delta_x, delta_y); 74 + } 75 + 76 + pub fn teleport_with(&mut self, position: (i32, i32)) { 77 + self.teleport(position.0, position.1) 78 + } 79 + 80 + pub fn region(&self) -> Region { 81 + match self { 82 + Polygon(start, lines) => { 83 + let mut region: Region = (start, start).into(); 84 + for line in lines { 85 + match line { 86 + LineSegment::InwardCurve(anchor) 87 + | LineSegment::OutwardCurve(anchor) 88 + | LineSegment::Straight(anchor) => { 89 + // println!( 90 + // "extending region {} with {}", 91 + // region, 92 + // Region::from((start, anchor)) 93 + // ); 94 + region = *region.max(&(start, anchor).into()) 95 + } 96 + } 97 + } 98 + // println!("region for {:?} -> {}", self, region); 99 + region 100 + } 101 + Line(s, e, _) | CurveInward(s, e, _) | CurveOutward(s, e, _) => { 102 + let (x1, y1, x2, y2) = (s.x(), s.y(), e.y(), e.x()); 103 + 104 + let region = Region::new( 105 + (x1.min(x2), y1.min(y2)), 106 + (x1.max(x2), y1.max(y2)), 107 + ) 108 + .map_err(|e| { 109 + anyhow!("Could not construct region of {self:?}: {e:?}") 110 + }) 111 + .unwrap(); 112 + 113 + region.enlarged( 114 + if region.width() > 1 { -1 } else { 0 }, 115 + if region.height() > 1 { -1 } else { 0 }, 116 + ) 117 + } 118 + Rectangle(start, end) => { 119 + Region::new(*start, *end).unwrap().enlarged(-1, -1) 120 + } 121 + Text(anchor, _, _) 122 + | CenteredText(anchor, ..) 123 + | Dot(anchor) 124 + | BigDot(anchor) 125 + | SmallCircle(anchor) => anchor.region(), 126 + BigCircle(center) => center.region(), 127 + Image(region, ..) => *region, 128 + RawSVG(_) => { 129 + unimplemented!() 130 + } 131 + } 132 + } 133 + 134 + pub fn fillable(&self) -> bool { 135 + !matches!(self, Line(..) | CurveInward(..) | CurveOutward(..)) 136 + } 137 + 138 + pub fn hatchable(&self) -> bool { 139 + self.fillable() && !matches!(self, Dot(..)) 140 + } 141 + 142 + pub fn point_is_on_line(self, point: Point) -> bool { 143 + match (&self, point) { 144 + (Line(s, e, _), Point::Corner(x, y)) => { 145 + if !self.region().contains(&point) { 146 + return false; 147 + } 148 + 149 + let (sx, sy) = s.xy::<f32>(); 150 + let (ex, ey) = e.xy::<f32>(); 151 + 152 + let m = (ey - sy) / (ex - sx); 153 + let p = sy - m * sx; 154 + 155 + (m * x as f32 + p) as usize == y 156 + } 157 + (Line(..), _) => panic!("Point type not supported"), 158 + _ => panic!("{self:?} is not a line object"), 159 + } 160 + } 161 + 162 + // 163 + // ```rs 164 + // use shapemaker::{Line, Point::Corner}; 165 + // let line = |x1: usize, y1: usize, x2: usize, y2: usize| Line(Corner(x1, y1), Corner(x2, y2), 1.0); 166 + // assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1))); 167 + // assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4))); 168 + // ``` 169 + pub fn intersects_with(&self, line: Shape) -> bool { 170 + match (self, &line) { 171 + (&Line(s1, e1, _), &Line(s2, e2, _)) => { 172 + let (dx1, dy1) = e1 - s1; 173 + let (dx2, dy2) = e2 - s2; 174 + let (dx3, dy3) = s2 - s1; 175 + 176 + let det = dx1 * dy2 - dy1 * dx2; 177 + 178 + let det1 = dx1 * dy3 - dx3 * dy1; 179 + let det2 = dx2 * dy3 - dx3 * dy2; 180 + 181 + if det == 0 { 182 + if det1 != 0 || det2 != 0 { 183 + return false; 184 + } 185 + 186 + let (x1, y1) = s1.xy::<isize>(); 187 + let (x2, y2) = e1.xy::<isize>(); 188 + let (x3, y3) = s2.xy::<isize>(); 189 + 190 + if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) { 191 + return true; 192 + } 193 + 194 + if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) { 195 + return true; 196 + } 197 + 198 + return false; 199 + } 200 + 201 + let frac_less_than_one = |num: isize, den: isize| { 202 + if num.signum() != den.signum() { 203 + return false; 204 + } 205 + 206 + num.abs() <= den.abs() 207 + }; 208 + 209 + frac_less_than_one(det1, det) && frac_less_than_one(det2, det) 210 + } 211 + _ => { 212 + unimplemented!( 213 + "Intersection not implemented for {self:?} and {:?}", 214 + line.clone() 215 + ) 216 + } 217 + } 218 + } 219 + }
+2 -2
src/lib.rs
··· 30 30 Angle, Axis, CenterPoint, Containable, CornerPoint, Norm, Point, Region, 31 31 }; 32 32 pub use graphics::{ 33 - Canvas, Color, Color::*, ColorMapping, ColoredObject, Fill, FillOperations, 34 - Filter, FilterType, Layer, LineSegment, Object, Object::*, ObjectSizes, 33 + Canvas, Color, Color::*, ColorMapping, Fill, FillOperations, Filter, 34 + FilterType, Layer, LineSegment, Object, ObjectSizes, Shape, Shape::*, 35 35 Transformation, 36 36 }; 37 37 pub use rendering::{
+9 -9
src/random/canvas.rs
··· 1 - use crate::{Canvas, ColoredObject, Fill, Layer, Object, Region}; 1 + use crate::{Canvas, Object, Fill, Layer, Shape, Region}; 2 2 use rand::{Rng, distr::uniform::SampleRange}; 3 3 use std::collections::HashMap; 4 4 ··· 7 7 self.random_layer_within(rng, name, &self.world_region.clone()) 8 8 } 9 9 10 - pub fn random_object(&mut self, rng: &mut impl Rng) -> Object { 10 + pub fn random_object(&mut self, rng: &mut impl Rng) -> Shape { 11 11 self.random_object_within(rng, &self.world_region.clone()) 12 12 } 13 13 ··· 15 15 &mut self, 16 16 rng: &mut impl Rng, 17 17 region: &Region, 18 - ) -> Object { 19 - Object::random( 18 + ) -> Shape { 19 + Shape::random( 20 20 rng, 21 21 region, 22 22 self.object_sizes.default_line_width, ··· 31 31 count: usize, 32 32 layer_name: &str, 33 33 ) -> Layer { 34 - let mut objects: HashMap<String, ColoredObject> = HashMap::new(); 34 + let mut objects: HashMap<String, Object> = HashMap::new(); 35 35 for i in 0..count { 36 - let object = Object::random_curve_within( 36 + let object = Shape::random_curve_within( 37 37 rng, 38 38 region, 39 39 self.object_sizes.default_line_width, 40 40 ); 41 41 objects.insert( 42 42 format!("{}#{}", layer_name, i), 43 - ColoredObject::from(( 43 + Object::from(( 44 44 object, 45 45 if rng.random_bool(0.5) { 46 46 Some(Fill::random_solid(rng, self.background)) ··· 76 76 name: &str, 77 77 region: &Region, 78 78 ) -> Layer { 79 - let mut objects: HashMap<String, ColoredObject> = HashMap::new(); 79 + let mut objects: HashMap<String, Object> = HashMap::new(); 80 80 let number_of_objects = 81 81 rng.random_range(self.objects_count_range.clone()); 82 82 for i in 0..number_of_objects { 83 - let object = Object::random( 83 + let object = Shape::random( 84 84 rng, 85 85 region, 86 86 self.object_sizes.default_line_width,
+11 -11
src/random/objects.rs
··· 1 1 use rand::{Rng, distr::uniform::SampleRange}; 2 2 3 - use crate::{ColoredObject, LineSegment, Object, Point, Region}; 3 + use crate::{Object, LineSegment, Point, Region, Shape}; 4 4 5 - impl Object { 5 + impl Shape { 6 6 pub fn random_starting_at<R: rand::Rng>( 7 7 rng: &mut R, 8 8 start: Point, ··· 38 38 rng: &mut R, 39 39 region: &Region, 40 40 vertices_range: impl SampleRange<usize>, 41 - ) -> Object { 41 + ) -> Shape { 42 42 let number_of_anchors = rng.random_range(vertices_range); 43 43 let start = Point::random(rng, region); 44 44 let mut lines: Vec<LineSegment> = vec![]; ··· 46 46 let next_anchor = Point::random(rng, region); 47 47 lines.push(Self::random_line_segment(rng, next_anchor)); 48 48 } 49 - Object::Polygon(start, lines) 49 + Shape::Polygon(start, lines) 50 50 } 51 51 52 52 pub fn random_line_segment<R: rand::Rng>( ··· 66 66 region: &Region, 67 67 line_width: f32, 68 68 polygon_vertices_range: impl SampleRange<usize>, 69 - ) -> Object { 69 + ) -> Shape { 70 70 let start = Point::random(rng, region); 71 - Object::random_starting_at( 71 + Shape::random_starting_at( 72 72 rng, 73 73 start, 74 74 region, ··· 81 81 rng: &mut impl Rng, 82 82 region: &Region, 83 83 line_width: f32, 84 - ) -> Object { 84 + ) -> Shape { 85 85 let start = region.random_point(rng); 86 86 match rng.random_range(1..=3) { 87 - 1 => Object::CurveInward( 87 + 1 => Shape::CurveInward( 88 88 start, 89 89 region.random_end(rng, start), 90 90 line_width, 91 91 ), 92 - 2 => Object::CurveOutward( 92 + 2 => Shape::CurveOutward( 93 93 start, 94 94 region.random_end(rng, start), 95 95 line_width, 96 96 ), 97 - 3 => Object::Line( 97 + 3 => Shape::Line( 98 98 Point::random(rng, region), 99 99 Point::random(rng, region), 100 100 line_width, ··· 104 104 } 105 105 } 106 106 107 - impl ColoredObject { 107 + impl Object { 108 108 pub fn flickering(self, rng: &mut impl Rng, amplitude: f32) -> Self { 109 109 self.opacified(rng.random_range((1.0 - amplitude).max(0.0)..1.0)) 110 110 }
+2 -2
src/rendering/canvas.rs
··· 1 1 use super::renderable::SVGRenderable; 2 2 use crate::{ 3 - ColoredObject, 3 + Object, 4 4 graphics::canvas::Canvas, 5 5 rendering::{ 6 6 rasterization::{ ··· 62 62 } 63 63 64 64 for layer in self.layers.iter() { 65 - for ColoredObject { clip_to, .. } in layer.objects.values() { 65 + for Object { clip_to, .. } in layer.objects.values() { 66 66 if let Some(region) = clip_to { 67 67 defs.add( 68 68 svg::tag("clipPath")
+1
src/rendering/mod.rs
··· 3 3 pub mod filter; 4 4 pub mod fonts; 5 5 pub mod layer; 6 + pub mod shapes; 6 7 pub mod objects; 7 8 pub mod rasterization; 8 9 pub mod renderable;
+5 -283
src/rendering/objects.rs
··· 1 1 use itertools::Itertools; 2 2 use measure_time::debug_time; 3 3 4 - use crate::{ 5 - ColoredObject, Object, 6 - graphics::objects::{LineSegment, ObjectSizes}, 7 - }; 4 + use crate::Object; 8 5 9 6 use super::{ 10 7 CSSRenderable, SVGAttributesRenderable, renderable::SVGRenderable, svg, 11 8 }; 12 9 13 - impl SVGRenderable for ColoredObject { 10 + impl SVGRenderable for Object { 14 11 fn render_to_svg( 15 12 &self, 16 13 colormap: crate::ColorMapping, ··· 19 16 id: &str, 20 17 ) -> anyhow::Result<svg::Node> { 21 18 debug_time!("render_to_svg/colored_object"); 22 - let plain_obj = self.object.render_to_svg( 19 + let plain_obj = self.shape.render_to_svg( 23 20 colormap.clone(), 24 21 cell_size, 25 22 object_sizes, ··· 28 25 29 26 let mut css = self 30 27 .fill 31 - .render_to_css(&colormap.clone(), !self.object.fillable()); 28 + .render_to_css(&colormap.clone(), !self.shape.fillable()); 32 29 33 30 let object_svg = if !self.transformations.is_empty() 34 31 || !self.filters.is_empty() ··· 37 34 // css += "transform-box: fill-box; transform-origin: 50% 50%;"; 38 35 39 36 let (center_x, center_y) = 40 - self.object.region().center_coords(cell_size); 37 + self.shape.region().center_coords(cell_size); 41 38 42 39 css += &format!("transform-origin: {center_x}px {center_y}px;"); 43 40 ··· 76 73 } 77 74 } 78 75 } 79 - 80 - impl SVGRenderable for Object { 81 - fn render_to_svg( 82 - &self, 83 - _colormap: crate::ColorMapping, 84 - cell_size: usize, 85 - object_sizes: crate::graphics::objects::ObjectSizes, 86 - id: &str, 87 - ) -> anyhow::Result<svg::Node> { 88 - debug_time!("render_to_svg/object"); 89 - let rendered = match self { 90 - Object::Text(..) | Object::CenteredText(..) => { 91 - self.render_text(cell_size) 92 - } 93 - Object::Rectangle(..) => self.render_rectangle(cell_size), 94 - Object::Polygon(..) => self.render_polygon(cell_size), 95 - Object::Line(..) => self.render_line(cell_size), 96 - Object::CurveInward(..) | Object::CurveOutward(..) => { 97 - self.render_curve(cell_size) 98 - } 99 - Object::BigDot(..) 100 - | Object::Dot(..) 101 - | Object::BigCircle(..) 102 - | Object::SmallCircle(..) => { 103 - self.render_circle(cell_size, object_sizes) 104 - } 105 - Object::Image(..) => self.render_image(cell_size), 106 - Object::RawSVG(..) => self.render_raw_svg(), 107 - }; 108 - 109 - Ok(match rendered { 110 - svg::Node::Element(el) => el.dataset("object", id).into(), 111 - svg::Node::SVG(svg) => { 112 - if svg.trim().starts_with("<") { 113 - let (before, after) = 114 - svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); 115 - svg::Node::SVG(format!( 116 - r#"{before} data-object="{id}" {after}"# 117 - )) 118 - } else { 119 - eprintln!("Malformed raw SVG, {svg} doesn't start with <"); 120 - svg::Node::SVG(svg) 121 - } 122 - } 123 - _ => { 124 - panic!("Expected Element or SVG, got {:?}", rendered); 125 - } 126 - }) 127 - } 128 - } 129 - 130 - impl Object { 131 - fn render_image(&self, cell_size: usize) -> svg::Node { 132 - if let Object::Image(region, path) = self { 133 - return svg::tag("image") 134 - .coords(region.start.coords(cell_size)) 135 - .attr("width", region.width() * cell_size) 136 - .attr("height", region.height() * cell_size) 137 - .attr("href", path.clone()) 138 - .into(); 139 - } 140 - 141 - panic!("Expected Image, got {:?}", self); 142 - } 143 - 144 - fn render_raw_svg(&self) -> svg::Node { 145 - if let Object::RawSVG(svg) = self { 146 - return svg::Node::SVG(svg.clone()); 147 - } 148 - 149 - panic!("Expected RawSVG, got {:?}", self); 150 - } 151 - 152 - fn render_text(&self, cell_size: usize) -> svg::Node { 153 - match self { 154 - Object::Text(position, content, font_size) 155 - | Object::CenteredText(position, content, font_size) => { 156 - let centered = matches!(self, Object::CenteredText(..)); 157 - 158 - svg::tag("text") 159 - .coords( 160 - if centered { 161 - position.as_centered() 162 - } else { 163 - position.as_corner() 164 - } 165 - .coords(cell_size), 166 - ) 167 - .attr("font-size", format!("{}pt", font_size)) 168 - .attr("font-family", "Inconsolata") 169 - .attr( 170 - "dominant-baseline", 171 - if centered { "middle" } else { "hanging" }, 172 - ) 173 - .attr( 174 - "text-anchor", 175 - if centered { "middle" } else { "start" }, 176 - ) 177 - .wrapping(vec![svg::Node::Text(content.to_string())]) 178 - .into() 179 - } 180 - _ => panic!("Expected Text, got {:?}", self), 181 - } 182 - } 183 - 184 - // fn render_fitted_text(&self, cell_size: usize) -> svg::Node { 185 - // if let Object::FittedText(region, content) = self { 186 - // let (x, y) = region.start.coords(cell_size); 187 - // let width = region.width() * cell_size as f32; 188 - // let height = region.height() * cell_size as f32; 189 - 190 - // return Box::new( 191 - // svg::node::element::Text::new(content.clone()) 192 - // .set("x", x) 193 - // .set("y", y) 194 - // .set("") 195 - // .set("font-size", format!("{}pt", 10.0)) 196 - // .set("font-family", "sans-serif"), 197 - // ); 198 - // } 199 - 200 - // panic!("Expected FittedText, got {:?}", self); 201 - // } 202 - 203 - fn render_rectangle(&self, cell_size: usize) -> svg::Node { 204 - if let Object::Rectangle(start, end) = self { 205 - return svg::tag("rect").region((start, end), cell_size).into(); 206 - } 207 - 208 - panic!("Expected Rectangle, got {:?}", self); 209 - } 210 - 211 - fn render_polygon(&self, cell_size: usize) -> svg::Node { 212 - if let Object::Polygon(start, lines) = self { 213 - let mut path = svg::Path::new(); 214 - path.move_to(*start, cell_size); 215 - for line in lines { 216 - match line { 217 - LineSegment::Straight(end) 218 - | LineSegment::InwardCurve(end) 219 - | LineSegment::OutwardCurve(end) => { 220 - path.line_to(*end, cell_size); 221 - } 222 - }; 223 - } 224 - path.close(); 225 - return path.node(); 226 - } 227 - 228 - panic!("Expected Polygon, got {:?}", self); 229 - } 230 - 231 - fn render_line(&self, cell_size: usize) -> svg::Node { 232 - if let Object::Line(start, end, width) = self { 233 - return svg::tag("line") 234 - .position_pair(*start, *end, cell_size) 235 - .attr("stroke-width", *width) 236 - .into(); 237 - } 238 - 239 - panic!("Expected Line, got {:?}", self); 240 - } 241 - 242 - fn render_curve(&self, cell_size: usize) -> svg::Node { 243 - if let Object::CurveOutward(start, end, stroke_width) 244 - | Object::CurveInward(start, end, stroke_width) = self 245 - { 246 - let inward = matches!(self, Object::CurveInward(..)); 247 - 248 - let (start_x, start_y) = start.coords(cell_size); 249 - let (end_x, end_y) = end.coords(cell_size); 250 - 251 - let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 252 - let start_from_midpoint = 253 - (start_x - midpoint.0, start_y - midpoint.1); 254 - let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 255 - 256 - let control = { 257 - let relative = (end_x - start_x, end_y - start_y); 258 - if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 259 - && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 260 - { 261 - if inward { 262 - ( 263 - midpoint.0 + relative.0.abs() / 2.0, 264 - midpoint.1 - relative.1.abs() / 2.0, 265 - ) 266 - } else { 267 - ( 268 - midpoint.0 - relative.0.abs() / 2.0, 269 - midpoint.1 + relative.1.abs() / 2.0, 270 - ) 271 - } 272 - // diagonal line is going like this: / 273 - } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 274 - && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 275 - { 276 - if inward { 277 - ( 278 - midpoint.0 - relative.0.abs() / 2.0, 279 - midpoint.1 - relative.1.abs() / 2.0, 280 - ) 281 - } else { 282 - ( 283 - midpoint.0 + relative.0.abs() / 2.0, 284 - midpoint.1 + relative.1.abs() / 2.0, 285 - ) 286 - } 287 - // line is horizontal 288 - } else if start_y == end_y { 289 - ( 290 - midpoint.0, 291 - midpoint.1 292 - + (if inward { -1.0 } else { 1.0 }) 293 - * relative.0.abs() 294 - / 2.0, 295 - ) 296 - // line is vertical 297 - } else if start_x == end_x { 298 - ( 299 - midpoint.0 300 - + (if inward { -1.0 } else { 1.0 }) 301 - * relative.1.abs() 302 - / 2.0, 303 - midpoint.1, 304 - ) 305 - } else { 306 - unreachable!() 307 - } 308 - }; 309 - 310 - let mut path = svg::Path::new(); 311 - path.move_to(*start, cell_size); 312 - path.quadratic_curve_to(control, *end, cell_size); 313 - return path 314 - .element() 315 - .attr("stroke-width", format!("{stroke_width}")) 316 - .into(); 317 - } 318 - 319 - panic!("Expected Curve, got {:?}", self); 320 - } 321 - 322 - fn render_circle( 323 - &self, 324 - cell_size: usize, 325 - object_sizes: ObjectSizes, 326 - ) -> svg::Node { 327 - let center = match self { 328 - Object::BigDot(at) | Object::Dot(at) => at.coords(cell_size), 329 - Object::BigCircle(at) | Object::SmallCircle(at) => { 330 - at.as_centered().coords(cell_size) 331 - } 332 - 333 - _ => panic!( 334 - "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 335 - self 336 - ), 337 - }; 338 - 339 - let radius = match self { 340 - Object::BigDot(_) => object_sizes.small_circle_radius, 341 - Object::Dot(_) => object_sizes.dot_radius, 342 - Object::BigCircle(_) => cell_size as f32 / 2.0, 343 - Object::SmallCircle(_) => object_sizes.small_circle_radius, 344 - _ => unreachable!(), 345 - }; 346 - 347 - return svg::tag("circle") 348 - .attr("cx", center.0) 349 - .attr("cy", center.1) 350 - .attr("r", radius) 351 - .into(); 352 - } 353 - }
+282
src/rendering/shapes.rs
··· 1 + use measure_time::debug_time; 2 + 3 + use crate::{LineSegment, Shape, graphics::objects::ObjectSizes}; 4 + 5 + use super::{ 6 + renderable::SVGRenderable, svg, 7 + }; 8 + 9 + impl SVGRenderable for Shape { 10 + fn render_to_svg( 11 + &self, 12 + _colormap: crate::ColorMapping, 13 + cell_size: usize, 14 + object_sizes: crate::graphics::objects::ObjectSizes, 15 + id: &str, 16 + ) -> anyhow::Result<svg::Node> { 17 + debug_time!("render_to_svg/object"); 18 + let rendered = match self { 19 + Shape::Text(..) | Shape::CenteredText(..) => { 20 + self.render_text(cell_size) 21 + } 22 + Shape::Rectangle(..) => self.render_rectangle(cell_size), 23 + Shape::Polygon(..) => self.render_polygon(cell_size), 24 + Shape::Line(..) => self.render_line(cell_size), 25 + Shape::CurveInward(..) | Shape::CurveOutward(..) => { 26 + self.render_curve(cell_size) 27 + } 28 + Shape::BigDot(..) 29 + | Shape::Dot(..) 30 + | Shape::BigCircle(..) 31 + | Shape::SmallCircle(..) => { 32 + self.render_circle(cell_size, object_sizes) 33 + } 34 + Shape::Image(..) => self.render_image(cell_size), 35 + Shape::RawSVG(..) => self.render_raw_svg(), 36 + }; 37 + 38 + Ok(match rendered { 39 + svg::Node::Element(el) => el.dataset("object", id).into(), 40 + svg::Node::SVG(svg) => { 41 + if svg.trim().starts_with("<") { 42 + let (before, after) = 43 + svg.split_once(' ').unwrap_or(svg.split_once(">").expect("Malformed SVG tag, {svg} starts with < but doesn't contain a space or >")); 44 + svg::Node::SVG(format!( 45 + r#"{before} data-object="{id}" {after}"# 46 + )) 47 + } else { 48 + eprintln!("Malformed raw SVG, {svg} doesn't start with <"); 49 + svg::Node::SVG(svg) 50 + } 51 + } 52 + _ => { 53 + panic!("Expected Element or SVG, got {:?}", rendered); 54 + } 55 + }) 56 + } 57 + } 58 + 59 + impl Shape { 60 + fn render_image(&self, cell_size: usize) -> svg::Node { 61 + if let Shape::Image(region, path) = self { 62 + return svg::tag("image") 63 + .coords(region.start.coords(cell_size)) 64 + .attr("width", region.width() * cell_size) 65 + .attr("height", region.height() * cell_size) 66 + .attr("href", path.clone()) 67 + .into(); 68 + } 69 + 70 + panic!("Expected Image, got {:?}", self); 71 + } 72 + 73 + fn render_raw_svg(&self) -> svg::Node { 74 + if let Shape::RawSVG(svg) = self { 75 + return svg::Node::SVG(svg.clone()); 76 + } 77 + 78 + panic!("Expected RawSVG, got {:?}", self); 79 + } 80 + 81 + fn render_text(&self, cell_size: usize) -> svg::Node { 82 + match self { 83 + Shape::Text(position, content, font_size) 84 + | Shape::CenteredText(position, content, font_size) => { 85 + let centered = matches!(self, Shape::CenteredText(..)); 86 + 87 + svg::tag("text") 88 + .coords( 89 + if centered { 90 + position.as_centered() 91 + } else { 92 + position.as_corner() 93 + } 94 + .coords(cell_size), 95 + ) 96 + .attr("font-size", format!("{}pt", font_size)) 97 + .attr("font-family", "Inconsolata") 98 + .attr( 99 + "dominant-baseline", 100 + if centered { "middle" } else { "hanging" }, 101 + ) 102 + .attr( 103 + "text-anchor", 104 + if centered { "middle" } else { "start" }, 105 + ) 106 + .wrapping(vec![svg::Node::Text(content.to_string())]) 107 + .into() 108 + } 109 + _ => panic!("Expected Text, got {:?}", self), 110 + } 111 + } 112 + 113 + // fn render_fitted_text(&self, cell_size: usize) -> svg::Node { 114 + // if let Object::FittedText(region, content) = self { 115 + // let (x, y) = region.start.coords(cell_size); 116 + // let width = region.width() * cell_size as f32; 117 + // let height = region.height() * cell_size as f32; 118 + 119 + // return Box::new( 120 + // svg::node::element::Text::new(content.clone()) 121 + // .set("x", x) 122 + // .set("y", y) 123 + // .set("") 124 + // .set("font-size", format!("{}pt", 10.0)) 125 + // .set("font-family", "sans-serif"), 126 + // ); 127 + // } 128 + 129 + // panic!("Expected FittedText, got {:?}", self); 130 + // } 131 + 132 + fn render_rectangle(&self, cell_size: usize) -> svg::Node { 133 + if let Shape::Rectangle(start, end) = self { 134 + return svg::tag("rect").region((start, end), cell_size).into(); 135 + } 136 + 137 + panic!("Expected Rectangle, got {:?}", self); 138 + } 139 + 140 + fn render_polygon(&self, cell_size: usize) -> svg::Node { 141 + if let Shape::Polygon(start, lines) = self { 142 + let mut path = svg::Path::new(); 143 + path.move_to(*start, cell_size); 144 + for line in lines { 145 + match line { 146 + LineSegment::Straight(end) 147 + | LineSegment::InwardCurve(end) 148 + | LineSegment::OutwardCurve(end) => { 149 + path.line_to(*end, cell_size); 150 + } 151 + }; 152 + } 153 + path.close(); 154 + return path.node(); 155 + } 156 + 157 + panic!("Expected Polygon, got {:?}", self); 158 + } 159 + 160 + fn render_line(&self, cell_size: usize) -> svg::Node { 161 + if let Shape::Line(start, end, width) = self { 162 + return svg::tag("line") 163 + .position_pair(*start, *end, cell_size) 164 + .attr("stroke-width", *width) 165 + .into(); 166 + } 167 + 168 + panic!("Expected Line, got {:?}", self); 169 + } 170 + 171 + fn render_curve(&self, cell_size: usize) -> svg::Node { 172 + if let Shape::CurveOutward(start, end, stroke_width) 173 + | Shape::CurveInward(start, end, stroke_width) = self 174 + { 175 + let inward = matches!(self, Shape::CurveInward(..)); 176 + 177 + let (start_x, start_y) = start.coords(cell_size); 178 + let (end_x, end_y) = end.coords(cell_size); 179 + 180 + let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 181 + let start_from_midpoint = 182 + (start_x - midpoint.0, start_y - midpoint.1); 183 + let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 184 + 185 + let control = { 186 + let relative = (end_x - start_x, end_y - start_y); 187 + if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 188 + && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 189 + { 190 + if inward { 191 + ( 192 + midpoint.0 + relative.0.abs() / 2.0, 193 + midpoint.1 - relative.1.abs() / 2.0, 194 + ) 195 + } else { 196 + ( 197 + midpoint.0 - relative.0.abs() / 2.0, 198 + midpoint.1 + relative.1.abs() / 2.0, 199 + ) 200 + } 201 + // diagonal line is going like this: / 202 + } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 203 + && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 204 + { 205 + if inward { 206 + ( 207 + midpoint.0 - relative.0.abs() / 2.0, 208 + midpoint.1 - relative.1.abs() / 2.0, 209 + ) 210 + } else { 211 + ( 212 + midpoint.0 + relative.0.abs() / 2.0, 213 + midpoint.1 + relative.1.abs() / 2.0, 214 + ) 215 + } 216 + // line is horizontal 217 + } else if start_y == end_y { 218 + ( 219 + midpoint.0, 220 + midpoint.1 221 + + (if inward { -1.0 } else { 1.0 }) 222 + * relative.0.abs() 223 + / 2.0, 224 + ) 225 + // line is vertical 226 + } else if start_x == end_x { 227 + ( 228 + midpoint.0 229 + + (if inward { -1.0 } else { 1.0 }) 230 + * relative.1.abs() 231 + / 2.0, 232 + midpoint.1, 233 + ) 234 + } else { 235 + unreachable!() 236 + } 237 + }; 238 + 239 + let mut path = svg::Path::new(); 240 + path.move_to(*start, cell_size); 241 + path.quadratic_curve_to(control, *end, cell_size); 242 + return path 243 + .element() 244 + .attr("stroke-width", format!("{stroke_width}")) 245 + .into(); 246 + } 247 + 248 + panic!("Expected Curve, got {:?}", self); 249 + } 250 + 251 + fn render_circle( 252 + &self, 253 + cell_size: usize, 254 + object_sizes: ObjectSizes, 255 + ) -> svg::Node { 256 + let center = match self { 257 + Shape::BigDot(at) | Shape::Dot(at) => at.coords(cell_size), 258 + Shape::BigCircle(at) | Shape::SmallCircle(at) => { 259 + at.as_centered().coords(cell_size) 260 + } 261 + 262 + _ => panic!( 263 + "Expected BigDot, Dot, BigCircle or SmallCircle, got {:?}", 264 + self 265 + ), 266 + }; 267 + 268 + let radius = match self { 269 + Shape::BigDot(_) => object_sizes.small_circle_radius, 270 + Shape::Dot(_) => object_sizes.dot_radius, 271 + Shape::BigCircle(_) => cell_size as f32 / 2.0, 272 + Shape::SmallCircle(_) => object_sizes.small_circle_radius, 273 + _ => unreachable!(), 274 + }; 275 + 276 + return svg::tag("circle") 277 + .attr("cx", center.0) 278 + .attr("cy", center.1) 279 + .attr("r", radius) 280 + .into(); 281 + } 282 + }
+2 -2
src/video/hooks.rs
··· 1 1 use super::animation::LayerAnimationUpdateFunction; 2 2 use super::context::Context; 3 3 use crate::synchronization::audio::MusicalDurationUnit; 4 - use crate::{Canvas, ColoredObject}; 4 + use crate::{Canvas, Object}; 5 5 use anyhow::Result; 6 6 use chrono::NaiveDateTime; 7 7 use std::{fmt::Formatter, panic}; ··· 238 238 ) -> Self 239 239 where 240 240 ObjectCreator: 241 - Fn(&Canvas, &mut Context<C>) -> Result<ColoredObject> + Send + Sync, 241 + Fn(&Canvas, &mut Context<C>) -> Result<Object> + Send + Sync, 242 242 { 243 243 self.with_hook(Hook { 244 244 when: Box::new(move |_, ctx, _, _| {