this repo has no description
3
fork

Configure Feed

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

♻️ Split code into more files

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
a8821c0d 9fc5444d

+709 -727
+64
src/anchors.rs
··· 1 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 2 + pub struct Anchor(pub i32, pub i32); 3 + 4 + impl Anchor { 5 + pub fn translate(&mut self, dx: i32, dy: i32) { 6 + self.0 += dx; 7 + self.1 += dy; 8 + } 9 + } 10 + 11 + impl From<(i32, i32)> for Anchor { 12 + fn from(value: (i32, i32)) -> Self { 13 + Anchor(value.0, value.1) 14 + } 15 + } 16 + 17 + #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 18 + pub struct CenterAnchor(pub i32, pub i32); 19 + 20 + impl CenterAnchor { 21 + pub fn translate(&mut self, dx: i32, dy: i32) { 22 + self.0 += dx; 23 + self.1 += dy; 24 + } 25 + } 26 + 27 + pub trait Coordinates { 28 + fn coords(&self, cell_size: usize) -> (f32, f32); 29 + fn center() -> Self; 30 + } 31 + 32 + impl Coordinates for Anchor { 33 + fn coords(&self, cell_size: usize) -> (f32, f32) { 34 + match self { 35 + Anchor(-1, -1) => (cell_size as f32 / 2.0, cell_size as f32 / 2.0), 36 + Anchor(i, j) => { 37 + let x = (i * cell_size as i32) as f32; 38 + let y = (j * cell_size as i32) as f32; 39 + (x, y) 40 + } 41 + } 42 + } 43 + 44 + fn center() -> Self { 45 + Anchor(-1, -1) 46 + } 47 + } 48 + 49 + impl Coordinates for CenterAnchor { 50 + fn coords(&self, cell_size: usize) -> (f32, f32) { 51 + match self { 52 + CenterAnchor(-1, -1) => ((cell_size / 2) as f32, (cell_size / 2) as f32), 53 + CenterAnchor(i, j) => { 54 + let x = *i as f32 * cell_size as f32 + cell_size as f32 / 2.0; 55 + let y = *j as f32 * cell_size as f32 + cell_size as f32 / 2.0; 56 + (x, y) 57 + } 58 + } 59 + } 60 + 61 + fn center() -> Self { 62 + CenterAnchor(-1, -1) 63 + } 64 + }
+5 -399
src/canvas.rs
··· 1 1 use core::panic; 2 - use std::{ 3 - cmp, 4 - collections::HashMap, 5 - io::Write, 6 - ops::{Range, RangeInclusive}, 7 - }; 2 + use std::{cmp, collections::HashMap, io::Write, ops::Range}; 8 3 9 4 use chrono::DateTime; 10 5 use rand::Rng; 11 6 12 - use crate::{layer::Layer, Color, ColorMapping}; 13 - 14 - #[derive(Debug, Clone, Default, Copy)] 15 - pub struct Region { 16 - pub start: (usize, usize), 17 - pub end: (usize, usize), 18 - } 19 - 20 - impl From<((usize, usize), (usize, usize))> for Region { 21 - fn from(value: ((usize, usize), (usize, usize))) -> Self { 22 - Region { 23 - start: value.0, 24 - end: value.1, 25 - } 26 - } 27 - } 28 - 29 - impl From<(&Anchor, &Anchor)> for Region { 30 - fn from(value: (&Anchor, &Anchor)) -> Self { 31 - Region { 32 - start: (value.0 .0 as usize, value.0 .1 as usize), 33 - end: (value.1 .0 as usize, value.1 .1 as usize), 34 - } 35 - } 36 - } 37 - 38 - impl From<(&CenterAnchor, &CenterAnchor)> for Region { 39 - fn from(value: (&CenterAnchor, &CenterAnchor)) -> Self { 40 - Region { 41 - start: (value.0 .0 as usize, value.0 .1 as usize), 42 - end: (value.1 .0 as usize, value.1 .1 as usize), 43 - } 44 - } 45 - } 46 - 47 - impl std::ops::Sub for Region { 48 - type Output = (i32, i32); 49 - 50 - fn sub(self, rhs: Self) -> Self::Output { 51 - ( 52 - (self.start.0 as i32 - rhs.start.0 as i32), 53 - (self.start.1 as i32 - rhs.start.1 as i32), 54 - ) 55 - } 56 - } 57 - 58 - #[test] 59 - fn test_sub_and_transate_coherence() { 60 - let a = Region::from_origin((3, 3)); 61 - let mut b = a.clone(); 62 - b.translate(2, 3); 63 - 64 - assert_eq!(b - a, (2, 3)); 65 - } 66 - 67 - impl Region { 68 - pub fn new(start_x: usize, start_y: usize, end_x: usize, end_y: usize) -> Self { 69 - let region = Self { 70 - start: (start_x, start_y), 71 - end: (end_x, end_y), 72 - }; 73 - region.ensure_valid(); 74 - region 75 - } 76 - 77 - pub fn max<'a>(&'a self, other: &'a Region) -> &'a Region { 78 - if self.within(other) { 79 - other 80 - } else { 81 - self 82 - } 83 - } 84 - 85 - pub fn random_coordinates_within(&self) -> (i32, i32) { 86 - ( 87 - rand::thread_rng().gen_range(self.start.0..self.end.0) as i32, 88 - rand::thread_rng().gen_range(self.start.1..self.end.1) as i32, 89 - ) 90 - } 91 - 92 - pub fn from_origin(end: (usize, usize)) -> Self { 93 - Self::new(0, 0, end.0, end.1) 94 - } 95 - 96 - pub fn from_origin_and_size(origin: (usize, usize), size: (usize, usize)) -> Self { 97 - Self::new( 98 - origin.0, 99 - origin.1, 100 - origin.0 + size.0 + 1, 101 - origin.1 + size.1 + 1, 102 - ) 103 - } 104 - 105 - pub fn from_center_and_size(center: (usize, usize), size: (usize, usize)) -> Self { 106 - let half_size = (size.0 / 2, size.1 / 2); 107 - Self::new( 108 - center.0 - half_size.0, 109 - center.1 - half_size.1, 110 - center.0 + half_size.0, 111 - center.1 + half_size.1, 112 - ) 113 - } 114 - 115 - // panics if the region is invalid 116 - pub fn ensure_valid(self) -> Self { 117 - if self.start.0 >= self.end.0 || self.start.1 >= self.end.1 { 118 - panic!( 119 - "Invalid region: start ({:?}) >= end ({:?})", 120 - self.start, self.end 121 - ) 122 - } 123 - self 124 - } 125 - 126 - pub fn translate(&mut self, dx: i32, dy: i32) { 127 - *self = self.translated(dx, dy); 128 - } 129 - 130 - pub fn translated(&self, dx: i32, dy: i32) -> Self { 131 - Self { 132 - start: ( 133 - (self.start.0 as i32 + dx) as usize, 134 - (self.start.1 as i32 + dy) as usize, 135 - ), 136 - end: ( 137 - (self.end.0 as i32 + dx) as usize, 138 - (self.end.1 as i32 + dy) as usize, 139 - ), 140 - } 141 - .ensure_valid() 142 - } 143 - 144 - /// adds dx and dy to the end of the region (dx and dy are _not_ multiplicative but **additive** factors) 145 - pub fn enlarged(&self, dx: i32, dy: i32) -> Self { 146 - Self { 147 - start: self.start, 148 - end: ( 149 - (self.end.0 as i32 + dx) as usize, 150 - (self.end.1 as i32 + dy) as usize, 151 - ), 152 - } 153 - .ensure_valid() 154 - } 155 - 156 - /// resized is like enlarged, but transforms from the center, by first translating the region by (-dx, -dy) 157 - pub fn resized(&self, dx: i32, dy: i32) -> Self { 158 - self.translated(-dx, -dy).enlarged(dx, dy) 159 - } 160 - 161 - pub fn x_range(&self) -> Range<usize> { 162 - self.start.0..self.end.0 163 - } 164 - pub fn y_range(&self) -> Range<usize> { 165 - self.start.1..self.end.1 166 - } 167 - 168 - pub fn x_range_without_last(&self) -> Range<usize> { 169 - self.start.0..self.end.0 - 1 170 - } 171 - 172 - pub fn y_range_without_last(&self) -> Range<usize> { 173 - self.start.1..self.end.1 - 1 174 - } 175 - 176 - pub fn within(&self, other: &Region) -> bool { 177 - self.start.0 >= other.start.0 178 - && self.start.1 >= other.start.1 179 - && self.end.0 <= other.end.0 180 - && self.end.1 <= other.end.1 181 - } 182 - 183 - pub fn clamped(&self, within: &Region) -> Region { 184 - Region { 185 - start: ( 186 - self.start.0.max(within.start.0), 187 - self.start.1.max(within.start.1), 188 - ), 189 - end: (self.end.0.min(within.end.0), self.end.1.min(within.end.1)), 190 - } 191 - } 192 - 193 - pub fn width(&self) -> usize { 194 - self.end.0 - self.start.0 195 - } 196 - 197 - pub fn height(&self) -> usize { 198 - self.end.1 - self.start.1 199 - } 200 - 201 - // goes from -width to width (inclusive on both ends) 202 - pub fn mirrored_width_range(&self) -> RangeInclusive<i32> { 203 - let w = self.width() as i32; 204 - -w..=w 205 - } 206 - 207 - pub fn mirrored_height_range(&self) -> RangeInclusive<i32> { 208 - let h = self.height() as i32; 209 - -h..=h 210 - } 211 - 212 - fn contains(&self, anchor: &Anchor) -> bool { 213 - self.x_range().contains(&(anchor.0 as usize)) 214 - && self.y_range().contains(&(anchor.1 as usize)) 215 - } 216 - } 7 + use crate::{ 8 + layer::Layer, objects::Object, Anchor, CenterAnchor, Color, ColorMapping, Fill, LineSegment, 9 + ObjectSizes, Region, 10 + }; 217 11 218 12 #[derive(Debug, Clone)] 219 13 pub struct Canvas { ··· 666 460 .format("%H:%M:%S%.3f") 667 461 ) 668 462 } 669 - 670 - #[derive(Debug, Clone, Copy)] 671 - pub struct ObjectSizes { 672 - pub empty_shape_stroke_width: f32, 673 - pub small_circle_radius: f32, 674 - pub dot_radius: f32, 675 - pub default_line_width: f32, 676 - } 677 - 678 - impl Default for ObjectSizes { 679 - fn default() -> Self { 680 - Self { 681 - empty_shape_stroke_width: 0.5, 682 - small_circle_radius: 5.0, 683 - dot_radius: 2.0, 684 - default_line_width: 2.0, 685 - } 686 - } 687 - } 688 - 689 - #[derive(Debug, Clone)] 690 - pub enum Object { 691 - Polygon(Anchor, Vec<LineSegment>), 692 - Line(Anchor, Anchor, f32), 693 - CurveOutward(Anchor, Anchor, f32), 694 - CurveInward(Anchor, Anchor, f32), 695 - SmallCircle(Anchor), 696 - Dot(Anchor), 697 - BigCircle(CenterAnchor), 698 - Text(Anchor, String, f32), 699 - Rectangle(Anchor, Anchor), 700 - RawSVG(Box<dyn svg::Node>), 701 - } 702 - 703 - impl Object { 704 - pub fn translate(&mut self, dx: i32, dy: i32) { 705 - match self { 706 - Object::Polygon(start, lines) => { 707 - start.translate(dx, dy); 708 - for line in lines { 709 - match line { 710 - LineSegment::InwardCurve(anchor) 711 - | LineSegment::OutwardCurve(anchor) 712 - | LineSegment::Straight(anchor) => anchor.translate(dx, dy), 713 - } 714 - } 715 - } 716 - Object::Line(start, end, _) 717 - | Object::CurveInward(start, end, _) 718 - | Object::CurveOutward(start, end, _) 719 - | Object::Rectangle(start, end) => { 720 - start.translate(dx, dy); 721 - end.translate(dx, dy); 722 - } 723 - Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 724 - anchor.translate(dx, dy) 725 - } 726 - Object::BigCircle(center) => center.translate(dx, dy), 727 - Object::RawSVG(_) => { 728 - unimplemented!() 729 - } 730 - } 731 - } 732 - 733 - pub fn translate_with(&mut self, delta: (i32, i32)) { 734 - self.translate(delta.0, delta.1) 735 - } 736 - 737 - pub fn teleport(&mut self, x: i32, y: i32) { 738 - let (current_x, current_y) = self.region().start; 739 - let delta_x = x - current_x as i32; 740 - let delta_y = y - current_y as i32; 741 - self.translate(delta_x, delta_y); 742 - } 743 - 744 - pub fn teleport_with(&mut self, position: (i32, i32)) { 745 - self.teleport(position.0, position.1) 746 - } 747 - 748 - pub fn region(&self) -> Region { 749 - match self { 750 - Object::Polygon(start, lines) => { 751 - let mut region: Region = (start, start).into(); 752 - for line in lines { 753 - match line { 754 - LineSegment::InwardCurve(anchor) 755 - | LineSegment::OutwardCurve(anchor) 756 - | LineSegment::Straight(anchor) => { 757 - region = *region.max(&(start, anchor).into()) 758 - } 759 - } 760 - } 761 - region 762 - } 763 - Object::Line(start, end, _) 764 - | Object::CurveInward(start, end, _) 765 - | Object::CurveOutward(start, end, _) 766 - | Object::Rectangle(start, end) => (start, end).into(), 767 - Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 768 - (anchor, anchor).into() 769 - } 770 - Object::BigCircle(center) => (center, center).into(), // FIXME will be wrong lmao, 771 - Object::RawSVG(_) => { 772 - unimplemented!() 773 - } 774 - } 775 - } 776 - } 777 - 778 - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 779 - pub struct Anchor(pub i32, pub i32); 780 - 781 - impl Anchor { 782 - pub fn translate(&mut self, dx: i32, dy: i32) { 783 - self.0 += dx; 784 - self.1 += dy; 785 - } 786 - } 787 - 788 - impl From<(i32, i32)> for Anchor { 789 - fn from(value: (i32, i32)) -> Self { 790 - Anchor(value.0, value.1) 791 - } 792 - } 793 - 794 - #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 795 - pub struct CenterAnchor(pub i32, pub i32); 796 - 797 - impl CenterAnchor { 798 - pub fn translate(&mut self, dx: i32, dy: i32) { 799 - self.0 += dx; 800 - self.1 += dy; 801 - } 802 - } 803 - 804 - pub trait Coordinates { 805 - fn coords(&self, cell_size: usize) -> (f32, f32); 806 - fn center() -> Self; 807 - } 808 - 809 - impl Coordinates for Anchor { 810 - fn coords(&self, cell_size: usize) -> (f32, f32) { 811 - match self { 812 - Anchor(-1, -1) => (cell_size as f32 / 2.0, cell_size as f32 / 2.0), 813 - Anchor(i, j) => { 814 - let x = (i * cell_size as i32) as f32; 815 - let y = (j * cell_size as i32) as f32; 816 - (x, y) 817 - } 818 - } 819 - } 820 - 821 - fn center() -> Self { 822 - Anchor(-1, -1) 823 - } 824 - } 825 - 826 - impl Coordinates for CenterAnchor { 827 - fn coords(&self, cell_size: usize) -> (f32, f32) { 828 - match self { 829 - CenterAnchor(-1, -1) => ((cell_size / 2) as f32, (cell_size / 2) as f32), 830 - CenterAnchor(i, j) => { 831 - let x = *i as f32 * cell_size as f32 + cell_size as f32 / 2.0; 832 - let y = *j as f32 * cell_size as f32 + cell_size as f32 / 2.0; 833 - (x, y) 834 - } 835 - } 836 - } 837 - 838 - fn center() -> Self { 839 - CenterAnchor(-1, -1) 840 - } 841 - } 842 - 843 - #[derive(Debug, Clone, PartialEq, Eq)] 844 - pub enum LineSegment { 845 - Straight(Anchor), 846 - InwardCurve(Anchor), 847 - OutwardCurve(Anchor), 848 - } 849 - 850 - #[derive(Debug, Clone, Copy)] 851 - pub enum Fill { 852 - Solid(Color), 853 - Translucent(Color, f32), 854 - Hatched, 855 - Dotted, 856 - }
+53
src/fill.rs
··· 1 + use crate::{Color, ColorMapping, RenderCSS}; 2 + 3 + #[derive(Debug, Clone, Copy)] 4 + pub enum Fill { 5 + Solid(Color), 6 + Translucent(Color, f32), 7 + Hatched, 8 + Dotted, 9 + } 10 + 11 + impl RenderCSS for Fill { 12 + fn render_fill_css(&self, colormap: &ColorMapping) -> String { 13 + match self { 14 + Fill::Solid(color) => { 15 + format!("fill: {};", color.to_string(colormap)) 16 + } 17 + Fill::Translucent(color, opacity) => { 18 + format!("fill: {}; opacity: {};", color.to_string(colormap), opacity) 19 + } 20 + Fill::Dotted => unimplemented!(), 21 + Fill::Hatched => unimplemented!(), 22 + } 23 + } 24 + 25 + fn render_stroke_css(&self, colormap: &ColorMapping) -> String { 26 + match self { 27 + Fill::Solid(color) => { 28 + format!("stroke: {};", color.to_string(colormap)) 29 + } 30 + Fill::Translucent(color, opacity) => { 31 + format!( 32 + "stroke: {}; opacity: {};", 33 + color.to_string(colormap), 34 + opacity 35 + ) 36 + } 37 + Fill::Dotted => unimplemented!(), 38 + Fill::Hatched => unimplemented!(), 39 + } 40 + } 41 + } 42 + 43 + impl RenderCSS for Option<Fill> { 44 + fn render_fill_css(&self, colormap: &ColorMapping) -> String { 45 + self.map(|fill| fill.render_fill_css(colormap)) 46 + .unwrap_or_default() 47 + } 48 + 49 + fn render_stroke_css(&self, colormap: &ColorMapping) -> String { 50 + self.map(|fill| fill.render_stroke_css(colormap)) 51 + .unwrap_or_default() 52 + } 53 + }
+11 -318
src/layer.rs
··· 1 + use crate::{ColorMapping, Fill, Object, ObjectSizes}; 1 2 use std::collections::HashMap; 2 - 3 - use chrono::format; 4 - 5 - use crate::{ 6 - canvas::{Coordinates, Fill, LineSegment, Object, ObjectSizes}, 7 - Color, ColorMapping, 8 - }; 9 3 10 4 #[derive(Debug, Clone, Default)] 11 5 pub struct Layer { ··· 78 72 if let Some(cached_svg) = &self._render_cache { 79 73 return cached_svg.clone(); 80 74 } 81 - let default_color = Color::Black.to_string(&colormap); 82 - // eprintln!("render: background_color({:?})", background_color); 75 + 83 76 let mut layer_group = svg::node::element::Group::new() 84 77 .set("class", "layer") 85 78 .set("data-layer", self.name.clone()); 79 + 86 80 for (id, (object, maybe_fill)) in &self.objects { 87 - let mut group = svg::node::element::Group::new(); 88 - group = group.set("data-object", id.clone()); 89 - match object { 90 - Object::RawSVG(svg) => { 91 - // eprintln!("render: raw_svg [{}]", id); 92 - group = group.add(svg.clone()); 93 - } 94 - Object::Text(position, content, font_size) => { 95 - group = group.add( 96 - svg::node::element::Text::new(content.clone()) 97 - .set("x", position.coords(cell_size).0) 98 - .set("y", position.coords(cell_size).1) 99 - .set("font-size", format!("{}pt", font_size)) 100 - .set("font-family", "sans-serif") 101 - .set("text-anchor", "middle") 102 - .set("dominant-baseline", "middle") 103 - .set( 104 - "style", 105 - match maybe_fill { 106 - Some(Fill::Solid(color)) => { 107 - format!("fill: {};", color.to_string(&colormap)) 108 - } 109 - Some(Fill::Translucent(color, opacity)) => { 110 - format!( 111 - "fill: {}; opacity: {};", 112 - color.to_string(&colormap), 113 - opacity 114 - ) 115 - } 116 - _ => "".to_string(), 117 - }, 118 - ), 119 - ); 120 - } 121 - Object::Rectangle(start, end) => { 122 - group = group.add( 123 - svg::node::element::Rectangle::new() 124 - .set("x1", start.coords(cell_size).0) 125 - .set("y1", start.coords(cell_size).1) 126 - .set("x2", end.coords(cell_size).0) 127 - .set("y2", end.coords(cell_size).1) 128 - .set( 129 - "style", 130 - match maybe_fill { 131 - Some(Fill::Solid(color)) => { 132 - format!("fill: {};", color.to_string(&colormap)) 133 - } 134 - Some(Fill::Translucent(color, opacity)) => { 135 - format!( 136 - "fill: {}; opacity: {};", 137 - color.to_string(&colormap), 138 - opacity 139 - ) 140 - } 141 - _ => "".to_string(), 142 - }, 143 - ), 144 - ); 145 - } 146 - Object::Polygon(start, lines) => { 147 - // eprintln!("render: polygon({:?}, {:?}) [{}]", start, lines, id); 148 - let mut path = svg::node::element::path::Data::new(); 149 - path = path.move_to(start.coords(cell_size)); 150 - for line in lines { 151 - path = match line { 152 - LineSegment::Straight(end) 153 - | LineSegment::InwardCurve(end) 154 - | LineSegment::OutwardCurve(end) => path.line_to(end.coords(cell_size)), 155 - }; 156 - } 157 - path = path.close(); 158 - group = group 159 - .add(svg::node::element::Path::new().set("d", path)) 160 - .set( 161 - "style", 162 - match maybe_fill { 163 - // TODO 164 - Some(Fill::Solid(color)) => { 165 - format!("fill: {};", color.to_string(&colormap)) 166 - } 167 - Some(Fill::Translucent(color, opacity)) => { 168 - format!( 169 - "fill: {}; opacity: {};", 170 - color.to_string(&colormap), 171 - opacity 172 - ) 173 - } 174 - _ => format!( 175 - "fill: none; stroke: {}; stroke-width: {}px;", 176 - default_color, object_sizes.empty_shape_stroke_width 177 - ), 178 - }, 179 - ); 180 - } 181 - Object::Line(start, end, line_width) => { 182 - // eprintln!("render: line({:?}, {:?}) [{}]", start, end, id); 183 - group = group.add( 184 - svg::node::element::Line::new() 185 - .set("x1", start.coords(cell_size).0) 186 - .set("y1", start.coords(cell_size).1) 187 - .set("x2", end.coords(cell_size).0) 188 - .set("y2", end.coords(cell_size).1) 189 - .set( 190 - "style", 191 - match maybe_fill { 192 - // TODO 193 - Some(Fill::Solid(color)) => { 194 - format!( 195 - "fill: none; stroke: {}; stroke-width: {}px;", 196 - color.to_string(&colormap), 197 - line_width 198 - ) 199 - } 200 - _ => format!( 201 - "fill: none; stroke: {}; stroke-width: {}px;", 202 - default_color, line_width 203 - ), 204 - }, 205 - ), 206 - ); 207 - } 208 - Object::CurveInward(start, end, line_width) 209 - | Object::CurveOutward(start, end, line_width) => { 210 - let inward = if matches!(object, Object::CurveInward(_, _, _)) { 211 - // eprintln!("render: curve_inward({:?}, {:?}) [{}]", start, end, id); 212 - true 213 - } else { 214 - // eprintln!("render: curve_outward({:?}, {:?}) [{}]", start, end, id); 215 - false 216 - }; 81 + layer_group = layer_group.add(object.render( 82 + cell_size, 83 + object_sizes, 84 + &colormap, 85 + &id, 86 + *maybe_fill, 87 + )); 88 + } 217 89 218 - let (start_x, start_y) = start.coords(cell_size); 219 - let (end_x, end_y) = end.coords(cell_size); 220 - 221 - let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 222 - let start_from_midpoint = (start_x - midpoint.0, start_y - midpoint.1); 223 - let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 224 - // eprintln!(" midpoint: {:?}", midpoint); 225 - // eprintln!( 226 - // " from midpoint: {:?} -> {:?}", 227 - // start_from_midpoint, end_from_midpoint 228 - // ); 229 - let control = { 230 - let relative = (end_x - start_x, end_y - start_y); 231 - // eprintln!(" relative: {:?}", relative); 232 - // diagonal line is going like this: \ 233 - if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 234 - && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 235 - { 236 - // eprintln!(" diagonal \\"); 237 - if inward { 238 - ( 239 - midpoint.0 + relative.0.abs() / 2.0, 240 - midpoint.1 - relative.1.abs() / 2.0, 241 - ) 242 - } else { 243 - ( 244 - midpoint.0 - relative.0.abs() / 2.0, 245 - midpoint.1 + relative.1.abs() / 2.0, 246 - ) 247 - } 248 - // diagonal line is going like this: / 249 - } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 250 - && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 251 - { 252 - // eprintln!(" diagonal /"); 253 - if inward { 254 - ( 255 - midpoint.0 - relative.0.abs() / 2.0, 256 - midpoint.1 - relative.1.abs() / 2.0, 257 - ) 258 - } else { 259 - ( 260 - midpoint.0 + relative.0.abs() / 2.0, 261 - midpoint.1 + relative.1.abs() / 2.0, 262 - ) 263 - } 264 - // line is horizontal 265 - } else if start_y == end_y { 266 - // eprintln!(" horizontal"); 267 - ( 268 - midpoint.0, 269 - midpoint.1 270 - + (if inward { -1.0 } else { 1.0 }) * relative.0.abs() / 2.0, 271 - ) 272 - // line is vertical 273 - } else if start_x == end_x { 274 - // eprintln!(" vertical"); 275 - ( 276 - midpoint.0 277 - + (if inward { -1.0 } else { 1.0 }) * relative.1.abs() / 2.0, 278 - midpoint.1, 279 - ) 280 - } else { 281 - unreachable!() 282 - } 283 - }; 284 - // eprintln!(" control: {:?}", control); 285 - group = group.add( 286 - svg::node::element::Path::new() 287 - .set( 288 - "d", 289 - svg::node::element::path::Data::new() 290 - .move_to(start.coords(cell_size)) 291 - .quadratic_curve_to((control, end.coords(cell_size))), 292 - ) 293 - .set( 294 - "style", 295 - match maybe_fill { 296 - // TODO 297 - Some(Fill::Solid(color)) => { 298 - format!( 299 - "fill: none; stroke: {}; stroke-width: {}px;", 300 - color.to_string(&colormap), 301 - line_width 302 - ) 303 - } 304 - Some(Fill::Translucent(color, opacity)) => { 305 - format!( 306 - "fill: none; stroke: {}; stroke-width: {}px; opacity: {}", 307 - color.to_string(&colormap), 308 - line_width, 309 - opacity 310 - ) 311 - } 312 - _ => format!( 313 - "fill: none; stroke: {}; stroke-width: {}px;", 314 - default_color, object_sizes.default_line_width 315 - ), 316 - }, 317 - ), 318 - ); 319 - } 320 - Object::SmallCircle(center) => { 321 - // eprintln!("render: small_circle({:?}) [{}]", center, id); 322 - group = group.add( 323 - svg::node::element::Circle::new() 324 - .set("cx", center.coords(cell_size).0) 325 - .set("cy", center.coords(cell_size).1) 326 - .set("r", object_sizes.small_circle_radius) 327 - .set( 328 - "style", 329 - match maybe_fill { 330 - // TODO 331 - Some(Fill::Solid(color)) => { 332 - format!("fill: {};", color.to_string(&colormap)) 333 - } 334 - Some(Fill::Translucent(color, opacity)) => { 335 - format!( 336 - "fill: {}; opacity: {};", 337 - color.to_string(&colormap), 338 - opacity 339 - ) 340 - } 341 - _ => format!( 342 - "fill: none; stroke: {}; stroke-width: {}px;", 343 - default_color, object_sizes.empty_shape_stroke_width 344 - ), 345 - }, 346 - ), 347 - ); 348 - } 349 - Object::Dot(center) => { 350 - // eprintln!("render: dot({:?}) [{}]", center, id); 351 - group = group.add( 352 - svg::node::element::Circle::new() 353 - .set("cx", center.coords(cell_size).0) 354 - .set("cy", center.coords(cell_size).1) 355 - .set("r", object_sizes.dot_radius) 356 - .set( 357 - "style", 358 - match maybe_fill { 359 - // TODO 360 - Some(Fill::Solid(color)) => { 361 - format!("fill: {};", color.to_string(&colormap)) 362 - } 363 - _ => format!( 364 - "fill: none; stroke: {}; stroke-width: {}px;", 365 - default_color, object_sizes.empty_shape_stroke_width 366 - ), 367 - }, 368 - ), 369 - ); 370 - } 371 - Object::BigCircle(center) => { 372 - // eprintln!("render: big_circle({:?}) [{}]", center, id); 373 - group = group.add( 374 - svg::node::element::Circle::new() 375 - .set("cx", center.coords(cell_size).0) 376 - .set("cy", center.coords(cell_size).1) 377 - .set("r", cell_size / 2) 378 - .set( 379 - "style", 380 - match maybe_fill { 381 - // TODO 382 - Some(Fill::Solid(color)) => { 383 - format!("fill: {};", color.to_string(&colormap)) 384 - } 385 - _ => format!( 386 - "fill: none; stroke: {}; stroke-width: 0.5px;", 387 - default_color 388 - ), 389 - }, 390 - ), 391 - ); 392 - } 393 - } 394 - // eprintln!(" fill: {:?}", &maybe_fill); 395 - layer_group = layer_group.add(group); 396 - } 397 90 self._render_cache = Some(layer_group.clone()); 398 91 layer_group 399 92 }
+12 -4
src/lib.rs
··· 1 1 mod color; 2 + mod objects; 2 3 pub use color::*; 4 + pub use objects::*; 5 + mod anchors; 6 + mod fill; 7 + pub use anchors::*; 8 + pub use fill::*; 9 + mod region; 10 + pub use region::*; 3 11 mod audio; 4 12 pub use audio::*; 5 13 mod sync; 6 14 use itertools::Itertools; 7 - use midly::write; 15 + 8 16 use sync::SyncData; 9 17 pub use sync::Syncable; 10 18 mod layer; ··· 24 32 use std::path::{Path, PathBuf}; 25 33 use std::sync::{Arc, Mutex}; 26 34 use std::thread::{self, JoinHandle}; 27 - use std::{iter, time}; 35 + use std::time; 28 36 29 37 const PROGRESS_BARS_STYLE: &str = 30 38 "{spinner:.cyan} {percent:03.bold.cyan}% {msg:<30} [{bar:100.bold.blue/dim.blue}] {eta:.cyan}"; ··· 780 788 composition: Vec<&str>, 781 789 render_background: bool, 782 790 workers_count: usize, 783 - preview_only: bool, 791 + _preview_only: bool, 784 792 ) -> () { 785 793 let mut frame_writer_threads = vec![]; 786 794 let mut frames_to_write: Vec<(String, usize, usize)> = vec![]; ··· 817 825 818 826 let chunk_size = (frames_to_write.len() as f32 / workers_count as f32).ceil() as usize; 819 827 let frames_to_write = Arc::new(frames_to_write); 820 - let frames_output_directory = self.frames_output_directory.clone(); 828 + let frames_output_directory = self.frames_output_directory; 821 829 for i in 0..workers_count { 822 830 let frames_to_write = Arc::clone(&frames_to_write); 823 831 let progress_bar = progress_bar.clone();
+1 -3
src/main.rs
··· 1 - use std::f32::consts::E; 2 - 3 1 use itertools::Itertools; 4 - use shapemaker::{Anchor, Canvas, CenterAnchor, Color, Fill, Layer, Object, Region, Video}; 2 + use shapemaker::{Anchor, Canvas, Color, Fill, Layer, Object, Region, Video}; 5 3 mod cli; 6 4 pub use cli::{canvas_from_cli, cli_args}; 7 5
+354
src/objects.rs
··· 1 + use crate::{Anchor, CenterAnchor, ColorMapping, Coordinates, Fill, Region}; 2 + 3 + #[derive(Debug, Clone, PartialEq, Eq)] 4 + pub enum LineSegment { 5 + Straight(Anchor), 6 + InwardCurve(Anchor), 7 + OutwardCurve(Anchor), 8 + } 9 + 10 + #[derive(Debug, Clone)] 11 + pub enum Object { 12 + Polygon(Anchor, Vec<LineSegment>), 13 + Line(Anchor, Anchor, f32), 14 + CurveOutward(Anchor, Anchor, f32), 15 + CurveInward(Anchor, Anchor, f32), 16 + SmallCircle(Anchor), 17 + Dot(Anchor), 18 + BigCircle(CenterAnchor), 19 + Text(Anchor, String, f32), 20 + Rectangle(Anchor, Anchor), 21 + RawSVG(Box<dyn svg::Node>), 22 + } 23 + 24 + #[derive(Debug, Clone, Copy)] 25 + pub struct ObjectSizes { 26 + pub empty_shape_stroke_width: f32, 27 + pub small_circle_radius: f32, 28 + pub dot_radius: f32, 29 + pub default_line_width: f32, 30 + } 31 + 32 + impl Default for ObjectSizes { 33 + fn default() -> Self { 34 + Self { 35 + empty_shape_stroke_width: 0.5, 36 + small_circle_radius: 5.0, 37 + dot_radius: 2.0, 38 + default_line_width: 2.0, 39 + } 40 + } 41 + } 42 + 43 + pub trait RenderCSS { 44 + fn render_fill_css(&self, colormap: &ColorMapping) -> String; 45 + fn render_stroke_css(&self, colormap: &ColorMapping) -> String; 46 + fn render_css(&self, colormap: &ColorMapping, fill_as_stroke_color: bool) -> String { 47 + if fill_as_stroke_color { 48 + self.render_stroke_css(colormap) 49 + } else { 50 + self.render_fill_css(colormap) 51 + } 52 + } 53 + } 54 + 55 + impl Object { 56 + pub fn translate(&mut self, dx: i32, dy: i32) { 57 + match self { 58 + Object::Polygon(start, lines) => { 59 + start.translate(dx, dy); 60 + for line in lines { 61 + match line { 62 + LineSegment::InwardCurve(anchor) 63 + | LineSegment::OutwardCurve(anchor) 64 + | LineSegment::Straight(anchor) => anchor.translate(dx, dy), 65 + } 66 + } 67 + } 68 + Object::Line(start, end, _) 69 + | Object::CurveInward(start, end, _) 70 + | Object::CurveOutward(start, end, _) 71 + | Object::Rectangle(start, end) => { 72 + start.translate(dx, dy); 73 + end.translate(dx, dy); 74 + } 75 + Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 76 + anchor.translate(dx, dy) 77 + } 78 + Object::BigCircle(center) => center.translate(dx, dy), 79 + Object::RawSVG(_) => { 80 + unimplemented!() 81 + } 82 + } 83 + } 84 + 85 + pub fn translate_with(&mut self, delta: (i32, i32)) { 86 + self.translate(delta.0, delta.1) 87 + } 88 + 89 + pub fn teleport(&mut self, x: i32, y: i32) { 90 + let (current_x, current_y) = self.region().start; 91 + let delta_x = x - current_x as i32; 92 + let delta_y = y - current_y as i32; 93 + self.translate(delta_x, delta_y); 94 + } 95 + 96 + pub fn teleport_with(&mut self, position: (i32, i32)) { 97 + self.teleport(position.0, position.1) 98 + } 99 + 100 + pub fn region(&self) -> Region { 101 + match self { 102 + Object::Polygon(start, lines) => { 103 + let mut region: Region = (start, start).into(); 104 + for line in lines { 105 + match line { 106 + LineSegment::InwardCurve(anchor) 107 + | LineSegment::OutwardCurve(anchor) 108 + | LineSegment::Straight(anchor) => { 109 + region = *region.max(&(start, anchor).into()) 110 + } 111 + } 112 + } 113 + region 114 + } 115 + Object::Line(start, end, _) 116 + | Object::CurveInward(start, end, _) 117 + | Object::CurveOutward(start, end, _) 118 + | Object::Rectangle(start, end) => (start, end).into(), 119 + Object::Text(anchor, _, _) | Object::Dot(anchor) | Object::SmallCircle(anchor) => { 120 + (anchor, anchor).into() 121 + } 122 + Object::BigCircle(center) => (center, center).into(), // FIXME will be wrong lmao, 123 + Object::RawSVG(_) => { 124 + unimplemented!() 125 + } 126 + } 127 + } 128 + } 129 + 130 + impl Object { 131 + pub fn fillable(&self) -> bool { 132 + !matches!( 133 + self, 134 + Object::Line(..) | Object::CurveInward(..) | Object::CurveOutward(..) 135 + ) 136 + } 137 + 138 + pub fn render( 139 + &self, 140 + cell_size: usize, 141 + object_sizes: ObjectSizes, 142 + colormap: &ColorMapping, 143 + id: &str, 144 + fill: Option<Fill>, 145 + ) -> svg::node::element::Group { 146 + let mut group = svg::node::element::Group::new(); 147 + 148 + let rendered = match self { 149 + Object::Text(..) => self.render_text(cell_size), 150 + Object::Rectangle(..) => self.render_rectangle(cell_size), 151 + Object::Polygon(..) => self.render_polygon(cell_size), 152 + Object::Line(..) => self.render_line(cell_size), 153 + Object::CurveInward(..) | Object::CurveOutward(..) => self.render_curve(cell_size), 154 + Object::SmallCircle(..) => self.render_small_circle(cell_size, object_sizes), 155 + Object::Dot(..) => self.render_dot(cell_size, object_sizes), 156 + Object::BigCircle(..) => self.render_big_circle(cell_size), 157 + Object::RawSVG(..) => self.render_raw_svg(), 158 + }; 159 + 160 + group = group.add(rendered); 161 + 162 + if !matches!(self, Object::RawSVG(..)) { 163 + group = group.set("style", fill.render_css(colormap, !self.fillable())); 164 + } 165 + 166 + group = group.set("data-object", id); 167 + group 168 + } 169 + 170 + fn render_raw_svg(&self) -> Box<dyn svg::node::Node> { 171 + if let Object::RawSVG(svg) = self { 172 + return svg.clone(); 173 + } 174 + 175 + panic!("Expected RawSVG, got {:?}", self); 176 + } 177 + 178 + fn render_text(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 179 + if let Object::Text(position, content, font_size) = self { 180 + return Box::new( 181 + svg::node::element::Text::new(content.clone()) 182 + .set("x", position.coords(cell_size).0) 183 + .set("y", position.coords(cell_size).1) 184 + .set("font-size", format!("{}pt", font_size)) 185 + .set("font-family", "sans-serif") 186 + .set("text-anchor", "middle") 187 + .set("dominant-baseline", "middle"), 188 + ); 189 + } 190 + 191 + panic!("Expected Text, got {:?}", self); 192 + } 193 + 194 + fn render_rectangle(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 195 + if let Object::Rectangle(start, end) = self { 196 + return Box::new( 197 + svg::node::element::Rectangle::new() 198 + .set("x1", start.coords(cell_size).0) 199 + .set("y1", start.coords(cell_size).1) 200 + .set("x2", end.coords(cell_size).0) 201 + .set("y2", end.coords(cell_size).1), 202 + ); 203 + } 204 + 205 + panic!("Expected Rectangle, got {:?}", self); 206 + } 207 + 208 + fn render_polygon(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 209 + if let Object::Polygon(start, lines) = self { 210 + let mut path = svg::node::element::path::Data::new(); 211 + path = path.move_to(start.coords(cell_size)); 212 + for line in lines { 213 + path = match line { 214 + LineSegment::Straight(end) 215 + | LineSegment::InwardCurve(end) 216 + | LineSegment::OutwardCurve(end) => path.line_to(end.coords(cell_size)), 217 + }; 218 + } 219 + path = path.close(); 220 + return Box::new(svg::node::element::Path::new().set("d", path)); 221 + } 222 + 223 + panic!("Expected Polygon, got {:?}", self); 224 + } 225 + 226 + fn render_line(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 227 + if let Object::Line(start, end, _) = self { 228 + return Box::new( 229 + svg::node::element::Line::new() 230 + .set("x1", start.coords(cell_size).0) 231 + .set("y1", start.coords(cell_size).1) 232 + .set("x2", end.coords(cell_size).0) 233 + .set("y2", end.coords(cell_size).1), 234 + ); 235 + } 236 + 237 + panic!("Expected Line, got {:?}", self); 238 + } 239 + 240 + fn render_curve(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 241 + if let Object::CurveOutward(start, end, _) | Object::CurveInward(start, end, _) = self { 242 + let inward = matches!(self, Object::CurveInward(..)); 243 + 244 + let (start_x, start_y) = start.coords(cell_size); 245 + let (end_x, end_y) = end.coords(cell_size); 246 + 247 + let midpoint = ((start_x + end_x) / 2.0, (start_y + end_y) / 2.0); 248 + let start_from_midpoint = (start_x - midpoint.0, start_y - midpoint.1); 249 + let end_from_midpoint = (end_x - midpoint.0, end_y - midpoint.1); 250 + 251 + let control = { 252 + let relative = (end_x - start_x, end_y - start_y); 253 + if start_from_midpoint.0 * start_from_midpoint.1 > 0.0 254 + && end_from_midpoint.0 * end_from_midpoint.1 > 0.0 255 + { 256 + if inward { 257 + ( 258 + midpoint.0 + relative.0.abs() / 2.0, 259 + midpoint.1 - relative.1.abs() / 2.0, 260 + ) 261 + } else { 262 + ( 263 + midpoint.0 - relative.0.abs() / 2.0, 264 + midpoint.1 + relative.1.abs() / 2.0, 265 + ) 266 + } 267 + // diagonal line is going like this: / 268 + } else if start_from_midpoint.0 * start_from_midpoint.1 < 0.0 269 + && end_from_midpoint.0 * end_from_midpoint.1 < 0.0 270 + { 271 + if inward { 272 + ( 273 + midpoint.0 - relative.0.abs() / 2.0, 274 + midpoint.1 - relative.1.abs() / 2.0, 275 + ) 276 + } else { 277 + ( 278 + midpoint.0 + relative.0.abs() / 2.0, 279 + midpoint.1 + relative.1.abs() / 2.0, 280 + ) 281 + } 282 + // line is horizontal 283 + } else if start_y == end_y { 284 + ( 285 + midpoint.0, 286 + midpoint.1 + (if inward { -1.0 } else { 1.0 }) * relative.0.abs() / 2.0, 287 + ) 288 + // line is vertical 289 + } else if start_x == end_x { 290 + ( 291 + midpoint.0 + (if inward { -1.0 } else { 1.0 }) * relative.1.abs() / 2.0, 292 + midpoint.1, 293 + ) 294 + } else { 295 + unreachable!() 296 + } 297 + }; 298 + 299 + return Box::new( 300 + svg::node::element::Path::new().set( 301 + "d", 302 + svg::node::element::path::Data::new() 303 + .move_to(start.coords(cell_size)) 304 + .quadratic_curve_to((control, end.coords(cell_size))), 305 + ), 306 + ); 307 + } 308 + 309 + panic!("Expected Curve, got {:?}", self); 310 + } 311 + 312 + fn render_small_circle( 313 + &self, 314 + cell_size: usize, 315 + object_sizes: ObjectSizes, 316 + ) -> Box<dyn svg::node::Node> { 317 + if let Object::SmallCircle(center) = self { 318 + return Box::new( 319 + svg::node::element::Circle::new() 320 + .set("cx", center.coords(cell_size).0) 321 + .set("cy", center.coords(cell_size).1) 322 + .set("r", object_sizes.small_circle_radius), 323 + ); 324 + } 325 + 326 + panic!("Expected SmallCircle, got {:?}", self); 327 + } 328 + 329 + fn render_dot(&self, cell_size: usize, object_sizes: ObjectSizes) -> Box<dyn svg::node::Node> { 330 + if let Object::Dot(center) = self { 331 + return Box::new( 332 + svg::node::element::Circle::new() 333 + .set("cx", center.coords(cell_size).0) 334 + .set("cy", center.coords(cell_size).1) 335 + .set("r", object_sizes.dot_radius), 336 + ); 337 + } 338 + 339 + panic!("Expected Dot, got {:?}", self); 340 + } 341 + 342 + fn render_big_circle(&self, cell_size: usize) -> Box<dyn svg::node::Node> { 343 + if let Object::BigCircle(center) = self { 344 + return Box::new( 345 + svg::node::element::Circle::new() 346 + .set("cx", center.coords(cell_size).0) 347 + .set("cy", center.coords(cell_size).1) 348 + .set("r", cell_size / 2), 349 + ); 350 + } 351 + 352 + panic!("Expected BigCircle, got {:?}", self); 353 + } 354 + }
+3 -3
src/preview.rs
··· 1 - use std::{collections::HashMap, fs, hash::Hash, path::PathBuf}; 1 + use std::{collections::HashMap, fs, path::PathBuf}; 2 2 3 3 use handlebars::Handlebars; 4 4 use itertools::Itertools; 5 5 use serde_json::json; 6 6 7 - use crate::{Canvas, ColorMapping}; 7 + use crate::Canvas; 8 8 9 9 const FRAMES_BUFFER_SIZE: usize = 500; 10 10 ··· 17 17 let template = String::from_utf8_lossy(include_bytes!("../preview/index.html.hbs")); 18 18 let engine_js_source = String::from_utf8_lossy(include_bytes!("../preview/engine.js")); 19 19 20 - let mut hbs = Handlebars::new(); 20 + let hbs = Handlebars::new(); 21 21 hbs.render_template( 22 22 &template, 23 23 &json!({
+206
src/region.rs
··· 1 + use crate::{Anchor, CenterAnchor}; 2 + use rand::Rng; 3 + 4 + #[derive(Debug, Clone, Default, Copy)] 5 + pub struct Region { 6 + pub start: (usize, usize), 7 + pub end: (usize, usize), 8 + } 9 + 10 + impl From<((usize, usize), (usize, usize))> for Region { 11 + fn from(value: ((usize, usize), (usize, usize))) -> Self { 12 + Region { 13 + start: value.0, 14 + end: value.1, 15 + } 16 + } 17 + } 18 + 19 + impl From<(&Anchor, &Anchor)> for Region { 20 + fn from(value: (&Anchor, &Anchor)) -> Self { 21 + Region { 22 + start: (value.0 .0 as usize, value.0 .1 as usize), 23 + end: (value.1 .0 as usize, value.1 .1 as usize), 24 + } 25 + } 26 + } 27 + 28 + impl From<(&CenterAnchor, &CenterAnchor)> for Region { 29 + fn from(value: (&CenterAnchor, &CenterAnchor)) -> Self { 30 + Region { 31 + start: (value.0 .0 as usize, value.0 .1 as usize), 32 + end: (value.1 .0 as usize, value.1 .1 as usize), 33 + } 34 + } 35 + } 36 + 37 + impl std::ops::Sub for Region { 38 + type Output = (i32, i32); 39 + 40 + fn sub(self, rhs: Self) -> Self::Output { 41 + ( 42 + (self.start.0 as i32 - rhs.start.0 as i32), 43 + (self.start.1 as i32 - rhs.start.1 as i32), 44 + ) 45 + } 46 + } 47 + 48 + #[test] 49 + fn test_sub_and_transate_coherence() { 50 + let a = Region::from_origin((3, 3)); 51 + let mut b = a.clone(); 52 + b.translate(2, 3); 53 + 54 + assert_eq!(b - a, (2, 3)); 55 + } 56 + 57 + impl Region { 58 + pub fn new(start_x: usize, start_y: usize, end_x: usize, end_y: usize) -> Self { 59 + let region = Self { 60 + start: (start_x, start_y), 61 + end: (end_x, end_y), 62 + }; 63 + region.ensure_valid(); 64 + region 65 + } 66 + 67 + pub fn max<'a>(&'a self, other: &'a Region) -> &'a Region { 68 + if self.within(other) { 69 + other 70 + } else { 71 + self 72 + } 73 + } 74 + 75 + pub fn random_coordinates_within(&self) -> (i32, i32) { 76 + ( 77 + rand::thread_rng().gen_range(self.start.0..self.end.0) as i32, 78 + rand::thread_rng().gen_range(self.start.1..self.end.1) as i32, 79 + ) 80 + } 81 + 82 + pub fn from_origin(end: (usize, usize)) -> Self { 83 + Self::new(0, 0, end.0, end.1) 84 + } 85 + 86 + pub fn from_origin_and_size(origin: (usize, usize), size: (usize, usize)) -> Self { 87 + Self::new( 88 + origin.0, 89 + origin.1, 90 + origin.0 + size.0 + 1, 91 + origin.1 + size.1 + 1, 92 + ) 93 + } 94 + 95 + pub fn from_center_and_size(center: (usize, usize), size: (usize, usize)) -> Self { 96 + let half_size = (size.0 / 2, size.1 / 2); 97 + Self::new( 98 + center.0 - half_size.0, 99 + center.1 - half_size.1, 100 + center.0 + half_size.0, 101 + center.1 + half_size.1, 102 + ) 103 + } 104 + 105 + // panics if the region is invalid 106 + pub fn ensure_valid(self) -> Self { 107 + if self.start.0 >= self.end.0 || self.start.1 >= self.end.1 { 108 + panic!( 109 + "Invalid region: start ({:?}) >= end ({:?})", 110 + self.start, self.end 111 + ) 112 + } 113 + self 114 + } 115 + 116 + pub fn translate(&mut self, dx: i32, dy: i32) { 117 + *self = self.translated(dx, dy); 118 + } 119 + 120 + pub fn translated(&self, dx: i32, dy: i32) -> Self { 121 + Self { 122 + start: ( 123 + (self.start.0 as i32 + dx) as usize, 124 + (self.start.1 as i32 + dy) as usize, 125 + ), 126 + end: ( 127 + (self.end.0 as i32 + dx) as usize, 128 + (self.end.1 as i32 + dy) as usize, 129 + ), 130 + } 131 + .ensure_valid() 132 + } 133 + 134 + /// adds dx and dy to the end of the region (dx and dy are _not_ multiplicative but **additive** factors) 135 + pub fn enlarged(&self, dx: i32, dy: i32) -> Self { 136 + Self { 137 + start: self.start, 138 + end: ( 139 + (self.end.0 as i32 + dx) as usize, 140 + (self.end.1 as i32 + dy) as usize, 141 + ), 142 + } 143 + .ensure_valid() 144 + } 145 + 146 + /// resized is like enlarged, but transforms from the center, by first translating the region by (-dx, -dy) 147 + pub fn resized(&self, dx: i32, dy: i32) -> Self { 148 + self.translated(-dx, -dy).enlarged(dx, dy) 149 + } 150 + 151 + pub fn x_range(&self) -> std::ops::Range<usize> { 152 + self.start.0..self.end.0 153 + } 154 + pub fn y_range(&self) -> std::ops::Range<usize> { 155 + self.start.1..self.end.1 156 + } 157 + 158 + pub fn x_range_without_last(&self) -> std::ops::Range<usize> { 159 + self.start.0..self.end.0 - 1 160 + } 161 + 162 + pub fn y_range_without_last(&self) -> std::ops::Range<usize> { 163 + self.start.1..self.end.1 - 1 164 + } 165 + 166 + pub fn within(&self, other: &Region) -> bool { 167 + self.start.0 >= other.start.0 168 + && self.start.1 >= other.start.1 169 + && self.end.0 <= other.end.0 170 + && self.end.1 <= other.end.1 171 + } 172 + 173 + pub fn clamped(&self, within: &Region) -> Region { 174 + Region { 175 + start: ( 176 + self.start.0.max(within.start.0), 177 + self.start.1.max(within.start.1), 178 + ), 179 + end: (self.end.0.min(within.end.0), self.end.1.min(within.end.1)), 180 + } 181 + } 182 + 183 + pub fn width(&self) -> usize { 184 + self.end.0 - self.start.0 185 + } 186 + 187 + pub fn height(&self) -> usize { 188 + self.end.1 - self.start.1 189 + } 190 + 191 + // goes from -width to width (inclusive on both ends) 192 + pub fn mirrored_width_range(&self) -> std::ops::RangeInclusive<i32> { 193 + let w = self.width() as i32; 194 + -w..=w 195 + } 196 + 197 + pub fn mirrored_height_range(&self) -> std::ops::RangeInclusive<i32> { 198 + let h = self.height() as i32; 199 + -h..=h 200 + } 201 + 202 + pub fn contains(&self, anchor: &Anchor) -> bool { 203 + self.x_range().contains(&(anchor.0 as usize)) 204 + && self.y_range().contains(&(anchor.1 as usize)) 205 + } 206 + }