this repo has no description
3
fork

Configure Feed

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

✨ Improve API, add Shape::Component (WIP)

+189 -53
+1 -1
examples/specimen/src/main.rs
··· 32 32 pub fn colors_shed() -> Canvas { 33 33 let mut canvas = Canvas::with_layers(vec!["circles"]); 34 34 canvas.set_grid_size(3, 3); 35 - canvas.canvas_outer_padding = 0; 35 + canvas.outer_padding = 0; 36 36 canvas.set_background(Color::White); 37 37 38 38 let all_colors = vec![
+11 -2
src/geometry/point.rs
··· 8 8 9 9 use super::Angle; 10 10 11 - #[derive(Debug, Clone, Copy, PartialEq, Serialize, Deserialize)] 11 + #[derive(Clone, Copy, PartialEq, Serialize, Deserialize)] 12 12 #[cfg_attr(feature = "web", serde(tag = "type", content = "data"))] 13 13 pub enum Point { 14 14 Corner(usize, usize), ··· 181 181 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 182 182 match self { 183 183 Point::Corner(x, y) => write!(f, "({x}, {y})"), 184 - Point::Center(x, y) => write!(f, "centered ({x}, {y})"), 184 + Point::Center(x, y) => write!(f, "[{x}, {y}]"), 185 + } 186 + } 187 + } 188 + 189 + impl std::fmt::Debug for Point { 190 + fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 191 + match self { 192 + Point::Corner(x, y) => write!(f, "({x}, {y})"), 193 + Point::Center(x, y) => write!(f, "[{x}, {y}]"), 185 194 } 186 195 } 187 196 }
+5
src/geometry/region.rs
··· 334 334 } 335 335 } 336 336 337 + pub fn translated_by(&self, point: Point) -> Self { 338 + let (x, y) = point.xy::<i32>(); 339 + self.translated(x, y) 340 + } 341 + 337 342 /// adds dx and dy to the end of the region (dx and dy are _not_ multiplicative but **additive** factors) 338 343 pub fn enlarged(&self, add_x: i32, add_y: i32) -> Self { 339 344 let resulting = Self {
+8 -5
src/graphics/canvas.rs
··· 19 19 pub cell_size: usize, 20 20 pub objects_count_range: Range<usize>, 21 21 pub polygon_vertices_range: Range<usize>, 22 - pub canvas_outer_padding: usize, 22 + pub outer_padding: usize, 23 23 pub object_sizes: ObjectSizes, 24 24 pub font_options: FontOptions, 25 25 pub colormap: ColorMapping, ··· 49 49 cell_size: 50, 50 50 objects_count_range: 3..7, 51 51 polygon_vertices_range: 2..7, 52 - canvas_outer_padding: 10, 52 + outer_padding: 10, 53 53 object_sizes: ObjectSizes::default(), 54 54 font_options: FontOptions::default(), 55 55 colormap: ColorMapping::default(), ··· 185 185 }); 186 186 } 187 187 188 + pub fn remove_layer(&mut self, name: &str) { 189 + self.layers.retain(|layer| layer.name != name); 190 + } 191 + 188 192 pub fn root(&mut self) -> &mut Layer { 189 193 self.layer("root") 190 194 .expect("Layer 'root' should always exist in a canvas") ··· 265 269 } 266 270 267 271 pub fn width(&self) -> usize { 268 - self.cell_size * self.world_region.width() + 2 * self.canvas_outer_padding 272 + self.cell_size * self.world_region.width() + 2 * self.outer_padding 269 273 } 270 274 271 275 pub fn height(&self) -> usize { 272 - self.cell_size * self.world_region.height() 273 - + 2 * self.canvas_outer_padding 276 + self.cell_size * self.world_region.height() + 2 * self.outer_padding 274 277 } 275 278 276 279 pub fn aspect_ratio(&self) -> f32 {
+21 -3
src/graphics/layer.rs
··· 42 42 self.objects.get_mut(name) 43 43 } 44 44 45 + pub fn objects_sorted_owned( 46 + &self, 47 + ) -> impl Iterator<Item = (String, Object)> + '_ { 48 + self.objects 49 + .iter() 50 + .sorted_by_cached_key(|&(id, _)| id.clone()) 51 + .map(|(id, obj)| (id.clone(), obj.clone())) 52 + } 53 + 45 54 // Useful to be able to guarantee a stable order when rendering. 46 55 pub fn objects_sorted(&self) -> impl Iterator<Item = (&String, &Object)> { 47 56 self.objects ··· 68 77 } 69 78 70 79 pub fn object_at(&mut self, point: Point) -> Option<&mut Object> { 71 - self.objects 72 - .values_mut() 73 - .find(|obj| obj.shape.region().start == point) 80 + self.find_object_mut(|o| o.position() == point.as_corner()) 74 81 } 75 82 76 83 pub fn has_object_that(&self, pred: impl Fn(&Object) -> bool) -> bool { 77 84 self.objects.values().any(|obj| pred(obj)) 85 + } 86 + 87 + pub fn find_object_mut( 88 + &mut self, 89 + pred: impl Fn(&Object) -> bool, 90 + ) -> Option<&mut Object> { 91 + self.objects.values_mut().find(|obj| pred(obj)) 92 + } 93 + 94 + pub fn find_object(&self, pred: impl Fn(&Object) -> bool) -> Option<&Object> { 95 + self.objects.values().find(|obj| pred(obj)) 78 96 } 79 97 80 98 // Remove all objects.
+10 -3
src/graphics/objects.rs
··· 1 1 use super::shapes::Shape; 2 - use crate::{Angle, Fill, Filter, Region, Transformation}; 2 + use crate::{Angle, Fill, Filter, Point, Region, Transformation}; 3 3 use itertools::Itertools; 4 4 use std::fmt::Display; 5 5 #[cfg(feature = "web")] ··· 100 100 self.shape.region() 101 101 } 102 102 103 - pub fn tag(&mut self, tag: impl Display) { 104 - self.tags.push(format!("{tag}")); 103 + pub fn position(&self) -> Point { 104 + self.shape.position() 105 + } 106 + 107 + pub fn tag(&mut self, tag: impl Display) -> String { 108 + let tag = tag.to_string(); 109 + 110 + self.tags.push(tag.clone()); 111 + tag 105 112 } 106 113 107 114 pub fn remove_tag(&mut self, tag: impl Display) {
+70 -31
src/graphics/shapes.rs
··· 1 1 use self::Shape::*; 2 - use crate::{Containable, Point, Region}; 2 + use crate::{Containable, Object, Point, Region}; 3 3 use anyhow::anyhow; 4 4 5 5 #[derive(Debug, Clone, PartialEq, Eq)] ··· 26 26 Image(Region, String), 27 27 RawSVG(String), 28 28 // Tiling(Region, Box<Object>), 29 + Component { 30 + at: Point, 31 + size: (usize, usize), 32 + objects: Box<Vec<Object>>, 33 + }, 29 34 } 30 35 31 36 impl Shape { ··· 56 61 | BigDot(anchor) => anchor.translate(dx, dy), 57 62 BigCircle(center) | SmallCircle(center) => center.translate(dx, dy), 58 63 Image(region, ..) => region.translate(dx, dy), 64 + Component { at, .. } => at.translate(dx, dy), 65 + RawSVG(_) => { 66 + unimplemented!() 67 + } 68 + } 69 + } 70 + 71 + pub fn position(&self) -> Point { 72 + match self { 73 + Polygon(at, ..) 74 + | Line(at, ..) 75 + | CurveInward(at, ..) 76 + | CurveOutward(at, ..) 77 + | Rectangle(at, ..) 78 + | Text(at, ..) 79 + | CenteredText(at, ..) 80 + | Dot(at) 81 + | BigDot(at) 82 + | BigCircle(at) 83 + | SmallCircle(at) 84 + | Component { at, .. } 85 + | Image(Region { start: at, .. }, ..) => *at, 59 86 RawSVG(_) => { 60 87 unimplemented!() 61 88 } ··· 125 152 | SmallCircle(anchor) => anchor.region(), 126 153 BigCircle(center) => center.region(), 127 154 Image(region, ..) => *region, 155 + Component { at, size, .. } => Region::from_topleft(*at, *size) 156 + .expect("Invalid region for component"), 128 157 RawSVG(_) => { 129 158 unimplemented!() 130 159 } ··· 159 188 } 160 189 } 161 190 191 + pub fn meets_endpoint_of_line(&self, point: Point) -> bool { 192 + match self { 193 + Line(s, e, _) => *s == point || *e == point, 194 + _ => panic!("{self:?} is not a line object"), 195 + } 196 + } 197 + 162 198 /// Check if this line intersects with another line. 163 199 /// Panics if either shape is not a line. 164 - /// 200 + /// 165 201 /// ``` 166 - /// use shapemaker::{Line, Point::Corner}; 167 - /// let line = |x1: u32, y1: u32, x2: u32, y2: u32| Line(Center(x1, y1), Center(x2, y2), 1.0); 202 + /// use shapemaker::{Line, Point::Center}; 203 + /// let line = |x1: usize, y1: usize, x2: usize, y2: usize| 204 + /// Line(Center(x1, y1), Center(x2, y2), 1.0); 168 205 /// assert!(line(1, 1, 4, 4).intersects_with(line(1, 4, 4, 1))); 169 206 /// assert!(line(7, 6, 9, 7).intersects_with(line(7, 7, 9, 4))); 170 207 /// assert!(line(4, 4, 6, 3).intersects_with(line(5, 2, 6, 5))); ··· 172 209 pub fn intersects_with(&self, line: Shape) -> bool { 173 210 match (self, &line) { 174 211 (&Line(s1, e1, _), &Line(s2, e2, _)) => { 175 - let (dx1, dy1) = e1 - s1; 176 - let (dx2, dy2) = e2 - s2; 177 - let (dx3, dy3) = s2 - s1; 212 + let parameters = |s: Point, e: Point| { 213 + let (sx, sy) = s.xy::<isize>(); 214 + let (ex, ey) = e.xy::<isize>(); 215 + let a = ey - sy; 216 + let b = sx - ex; 217 + let c = (ex * sy) - (sx * ey); 218 + (a, b, c) 219 + }; 178 220 179 - let det = dx1 * dy2 - dy1 * dx2; 221 + let distance_to_line = |p: Point, (s, e): (Point, Point)| { 222 + let (x, y) = p.xy::<isize>(); 223 + let (a, b, c) = parameters(s, e); 180 224 181 - let det1 = dx1 * dy3 - dx3 * dy1; 182 - let det2 = dx2 * dy3 - dx3 * dy2; 225 + (a * x) + (b * y) + c 226 + }; 183 227 184 - if det == 0 { 185 - if det1 != 0 || det2 != 0 { 186 - return false; 187 - } 188 - 189 - let (x1, y1) = s1.xy::<isize>(); 190 - let (x2, y2) = e1.xy::<isize>(); 191 - let (x3, y3) = s2.xy::<isize>(); 228 + let same_side_of_line = 229 + |p1: Point, p2: Point, line: (Point, Point)| { 230 + let d1 = distance_to_line(p1, line); 231 + let d2 = distance_to_line(p2, line); 192 232 193 - if dx1 != 0 && (x1 < x3 && x3 < x2 || x1 > x3 && x3 > x2) { 194 - return true; 195 - } 233 + if (d1 == 0) || (d2 == 0) { 234 + return false; 235 + } 196 236 197 - if dx1 == 0 && (y1 < y3 && y3 < y2 || y1 > y3 && y3 > y2) { 198 - return true; 199 - } 237 + d1.signum() == d2.signum() 238 + }; 200 239 240 + if same_side_of_line(s2, e2, (s1, e1)) { 201 241 return false; 202 242 } 203 243 204 - let frac_less_than_one = |num: isize, den: isize| { 205 - if num.signum() != den.signum() { 206 - return false; 207 - } 244 + if same_side_of_line(s1, e1, (s2, e2)) { 245 + return false; 246 + } 208 247 209 - num.abs() <= den.abs() 210 - }; 248 + let (a1, b1, _) = parameters(s1, e1); 249 + let (a2, b2, _) = parameters(s2, e2); 211 250 212 - frac_less_than_one(det1, det) && frac_less_than_one(det2, det) 251 + (a1 * b2) != (a2 * b1) 213 252 } 214 253 _ => { 215 254 unimplemented!(
+9 -1
src/random/objects.rs
··· 1 - use rand::{Rng, distr::uniform::SampleRange}; 1 + use rand::{Rng, distr::uniform::SampleRange, seq::IndexedRandom}; 2 2 3 3 use crate::{LineSegment, Object, Point, Region, Shape}; 4 4 ··· 107 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 + } 111 + 112 + pub fn pick_random_tag<R: rand::Rng>( 113 + &mut self, 114 + rng: &mut R, 115 + tags: &[&str], 116 + ) -> String { 117 + self.tag(tags.choose(rng).expect("Coudln't choose tag")) 110 118 } 111 119 }
+3 -3
src/rendering/canvas.rs
··· 27 27 28 28 svg.add( 29 29 svg::tag("rect") 30 - .attr("x", -(self.canvas_outer_padding as i32)) 31 - .attr("y", -(self.canvas_outer_padding as i32)) 30 + .attr("x", -(self.outer_padding as i32)) 31 + .attr("y", -(self.outer_padding as i32)) 32 32 .attr("width", self.width()) 33 33 .attr("height", self.height()) 34 34 .attr("fill", background_color.render(&self.colormap)), ··· 85 85 "viewBox", 86 86 format!( 87 87 "{0} {0} {1} {2}", 88 - -(self.canvas_outer_padding as i32), 88 + -(self.outer_padding as i32), 89 89 self.width(), 90 90 self.height() 91 91 ),
+48 -1
src/rendering/shapes.rs
··· 7 7 impl SVGRenderable for Shape { 8 8 fn render_to_svg( 9 9 &self, 10 - _colormap: crate::ColorMapping, 10 + colormap: crate::ColorMapping, 11 11 cell_size: usize, 12 12 object_sizes: crate::graphics::objects::ObjectSizes, 13 13 id: &str, ··· 30 30 self.render_circle(cell_size, object_sizes) 31 31 } 32 32 Shape::Image(..) => self.render_image(cell_size), 33 + Shape::Component { .. } => { 34 + self.render_component(colormap, cell_size, object_sizes, id) 35 + } 33 36 Shape::RawSVG(..) => self.render_raw_svg(), 34 37 }; 35 38 ··· 66 69 } 67 70 68 71 panic!("Expected Image, got {:?}", self); 72 + } 73 + 74 + fn render_component( 75 + &self, 76 + colormap: crate::ColorMapping, 77 + cell_size: usize, 78 + object_sizes: crate::graphics::objects::ObjectSizes, 79 + id: &str, 80 + ) -> svg::Node { 81 + if let Shape::Component { 82 + at, 83 + size: (real_w, real_h), 84 + objects, 85 + } = self 86 + { 87 + let (object_w, object_h) = objects 88 + .iter() 89 + .map(|o| o.position().xy()) 90 + .fold((0, 0), |(max_w, max_h), (w, h)| { 91 + (max_w.max(w) + 1, max_h.max(h) + 1) 92 + }); 93 + 94 + let percent = 95 + |num: usize, den: usize| format!("{}%", (num * 100) / (den)); 96 + 97 + return svg::tag("svg") 98 + .coords(at.coords(cell_size)) 99 + .dataset("nested-dims", format!("{object_w}x{object_h}")) 100 + .style( 101 + "transform", 102 + format!( 103 + "scale({}, {})", 104 + percent(*real_w, object_w), 105 + percent(*real_h, object_h) 106 + ), 107 + ) 108 + .wrapping(objects.iter().map(move |o| { 109 + o.render_to_svg(colormap.clone(), cell_size, object_sizes, id) 110 + .expect("Could not render component object to SVG") 111 + })) 112 + .into(); 113 + } 114 + 115 + panic!("Expected Component, got {:?}", self); 69 116 } 70 117 71 118 fn render_raw_svg(&self) -> svg::Node {
+2 -2
src/rendering/svg.rs
··· 131 131 .size(region, cell_size) 132 132 } 133 133 134 - pub fn style(self, key: &str, value: &str) -> Self { 134 + pub fn style(self, key: &str, value: impl Display) -> Self { 135 135 let mut styles = self.styles.clone(); 136 136 styles.insert(key.to_string(), value.to_string()); 137 137 Element { styles, ..self } 138 138 } 139 139 140 - pub fn dataset(self, key: &str, value: &str) -> Self { 140 + pub fn dataset(self, key: &str, value: impl Display) -> Self { 141 141 self.attr(&format!("data-{key}"), value) 142 142 } 143 143
+1 -1
src/video/encoders/vgv.rs
··· 45 45 r#"width={w} height={h} viewBox="-{pad} -{pad} {w} {h}""#, 46 46 w = initial_canvas.width(), 47 47 h = initial_canvas.height(), 48 - pad = initial_canvas.canvas_outer_padding 48 + pad = initial_canvas.outer_padding 49 49 ), 50 50 }), 51 51 })