This repository has no description
0

Configure Feed

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

1use crate::{Angle, Fill, Filter, Point, Region, Transformation}; 2use anyhow::anyhow; 3use itertools::Itertools; 4use std::fmt::Display; 5#[cfg(feature = "web")] 6use wasm_bindgen::prelude::*; 7 8use super::{Color, fill::FillOperations}; 9 10#[derive(Debug, Clone, PartialEq, Eq)] 11pub enum LineSegment { 12 Straight(Point), 13 InwardCurve(Point), 14 OutwardCurve(Point), 15} 16 17#[derive(Debug, Clone)] 18pub 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 Dot(Point), 25 BigCircle(Point), 26 Text(Point, String, f32), 27 CenteredText(Point, String, f32), 28 // FittedText(Region, String), 29 Rectangle(Point, Point), 30 Image(Region, String), 31 RawSVG(String), 32 // Tiling(Region, Box<Object>), 33} 34 35impl Object { 36 pub fn filled(self, fill: Fill) -> ColoredObject { 37 ColoredObject::from((self, Some(fill))) 38 } 39 40 pub fn colored(self, color: Color) -> ColoredObject { 41 ColoredObject::from((self, None)).colored(color) 42 } 43 44 pub fn filtered(self, filter: Filter) -> ColoredObject { 45 ColoredObject::from((self, None)).filtered(filter) 46 } 47 48 pub fn transform(self, transformation: Transformation) -> ColoredObject { 49 ColoredObject::from((self, None)).transformed(transformation) 50 } 51} 52 53#[derive(Debug, Clone)] 54pub struct ColoredObject { 55 pub object: Object, 56 pub fill: Option<Fill>, 57 pub filters: Vec<Filter>, 58 pub transformations: Vec<Transformation>, 59 pub tags: Vec<String>, 60 pub clip_to: Option<Region> 61} 62 63impl ColoredObject { 64 pub fn filtered(mut self, filter: Filter) -> Self { 65 self.filters.push(filter); 66 self 67 } 68 69 pub fn transformed(mut self, transformation: Transformation) -> Self { 70 self.transformations.push(transformation); 71 self 72 } 73 74 pub fn filled(mut self, fill: Fill) -> Self { 75 self.fill = Some(fill); 76 self 77 } 78 79 pub fn colored(mut self, color: Color) -> Self { 80 self.fill = Some(Fill::Solid(color)); 81 self 82 } 83 84 pub fn opacified(mut self, opacity: f32) -> Self { 85 if let Some(fill) = &mut self.fill { 86 *fill = fill.opacify(opacity); 87 } 88 self 89 } 90 91 pub fn clipped_to(mut self, region: impl Into<Region>) -> Self { 92 self.clip_to = Some(region.into()); 93 self 94 } 95 96 pub fn clear_filters(&mut self) { 97 self.filters.clear(); 98 } 99 100 pub fn refill(&mut self, fill: Fill) { 101 self.fill = Some(fill); 102 } 103 104 pub fn recolor(&mut self, color: Color) { 105 self.fill = Some(Fill::Solid(color)) 106 } 107 108 pub fn filter(&mut self, filter: Filter) { 109 self.filters.push(filter) 110 } 111 112 pub fn rotate(&mut self, angle: Angle) { 113 self.transformations 114 .push(Transformation::Rotate(angle.degrees())) 115 } 116 117 pub fn set_rotation(&mut self, angle: Angle) { 118 self.transformations 119 .retain(|t| !matches!(t, Transformation::Rotate(_))); 120 self.transformations 121 .push(Transformation::Rotate(angle.degrees())) 122 } 123 124 pub fn region(&self) -> Region { 125 self.object.region() 126 } 127 128 pub fn tag(&mut self, tag: impl Display) { 129 self.tags.push(format!("{tag}")); 130 } 131 132 pub fn remove_tag(&mut self, tag: impl Display) { 133 let tag_str = format!("{tag}"); 134 self.tags.retain(|t| t != &tag_str); 135 } 136 137 pub fn tagged(mut self, tag: impl Display) -> Self { 138 self.tags.push(format!("{tag}")); 139 self 140 } 141 142 pub fn has_tag(&self, tag: impl Display) -> bool { 143 let tag_str = format!("{tag}"); 144 self.tags.iter().any(|t| t == &tag_str) 145 } 146 147 148} 149 150impl std::fmt::Display for ColoredObject { 151 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result { 152 let ColoredObject { 153 object, 154 fill, 155 filters, 156 transformations, 157 tags, 158 clip_to 159 } = self; 160 161 if fill.is_some() { 162 write!(f, "{:?} {:?}", fill.unwrap(), object)?; 163 } else { 164 write!(f, "transparent {:?}", object)?; 165 } 166 167 if !filters.is_empty() { 168 write!(f, " with filters {:?}", filters)?; 169 } 170 171 if !transformations.is_empty() { 172 write!(f, " with transformations {:?}", transformations)?; 173 } 174 175 if !tags.is_empty() { 176 write!(f, "{}", tags.iter().map(|t| format!("#{t}")).join(" "))?; 177 } 178 179 if let Some(clip_to) = clip_to { 180 write!(f, " (clipped to {:?})", clip_to)?; 181 } 182 183 Ok(()) 184 } 185} 186 187impl From<Object> for ColoredObject { 188 fn from(value: Object) -> Self { 189 ColoredObject { 190 object: value, 191 fill: None, 192 filters: vec![], 193 transformations: vec![], 194 tags: vec![], 195 clip_to: None 196 } 197 } 198} 199 200impl From<(Object, Option<Fill>)> for ColoredObject { 201 fn from((object, fill): (Object, Option<Fill>)) -> Self { 202 ColoredObject { 203 object, 204 fill, 205 filters: vec![], 206 transformations: vec![], 207 tags: vec![], 208 clip_to: None 209 } 210 } 211} 212 213#[cfg_attr(feature = "web", wasm_bindgen)] 214#[derive(Debug, Clone, Copy)] 215pub struct ObjectSizes { 216 pub empty_shape_stroke_width: f32, 217 pub small_circle_radius: f32, 218 pub dot_radius: f32, 219 pub default_line_width: f32, 220} 221 222impl Default for ObjectSizes { 223 fn default() -> Self { 224 Self { 225 empty_shape_stroke_width: 0.5, 226 small_circle_radius: 5.0, 227 dot_radius: 2.0, 228 default_line_width: 2.0, 229 } 230 } 231} 232 233impl Object { 234 pub fn translate(&mut self, dx: i32, dy: i32) { 235 match self { 236 Object::Polygon(start, lines) => { 237 start.translate(dx, dy); 238 for line in lines { 239 match line { 240 LineSegment::InwardCurve(anchor) 241 | LineSegment::OutwardCurve(anchor) 242 | LineSegment::Straight(anchor) => { 243 anchor.translate(dx, dy) 244 } 245 } 246 } 247 } 248 Object::Line(start, end, _) 249 | Object::CurveInward(start, end, _) 250 | Object::CurveOutward(start, end, _) 251 | Object::Rectangle(start, end) => { 252 start.translate(dx, dy); 253 end.translate(dx, dy); 254 } 255 Object::Text(anchor, _, _) 256 | Object::CenteredText(anchor, ..) 257 | Object::Dot(anchor) 258 | Object::SmallCircle(anchor) => anchor.translate(dx, dy), 259 Object::BigCircle(center) => center.translate(dx, dy), 260 Object::Image(region, ..) => region.translate(dx, dy), 261 Object::RawSVG(_) => { 262 unimplemented!() 263 } 264 } 265 } 266 267 pub fn translate_with(&mut self, delta: (i32, i32)) { 268 self.translate(delta.0, delta.1) 269 } 270 271 pub fn teleport(&mut self, x: i32, y: i32) { 272 let Point(current_x, current_y) = self.region().start; 273 let delta_x = x - current_x as i32; 274 let delta_y = y - current_y as i32; 275 self.translate(delta_x, delta_y); 276 } 277 278 pub fn teleport_with(&mut self, position: (i32, i32)) { 279 self.teleport(position.0, position.1) 280 } 281 282 pub fn region(&self) -> Region { 283 match self { 284 Object::Polygon(start, lines) => { 285 let mut region: Region = (start, start).into(); 286 for line in lines { 287 match line { 288 LineSegment::InwardCurve(anchor) 289 | LineSegment::OutwardCurve(anchor) 290 | LineSegment::Straight(anchor) => { 291 // println!( 292 // "extending region {} with {}", 293 // region, 294 // Region::from((start, anchor)) 295 // ); 296 region = *region.max(&(start, anchor).into()) 297 } 298 } 299 } 300 // println!("region for {:?} -> {}", self, region); 301 region 302 } 303 Object::Line(Point(x1, y1), Point(x2, y2), _) 304 | Object::CurveInward(Point(x1, y1), Point(x2, y2), _) 305 | Object::CurveOutward(Point(x1, y1), Point(x2, y2), _) => { 306 let region = Region::new( 307 (x1.min(x2), y1.min(y2)), 308 (x1.max(x2), y1.max(y2)), 309 ) 310 .map_err(|e| { 311 anyhow!("Could not construct region of {self:?}: {e:?}") 312 }) 313 .unwrap(); 314 315 region.enlarged( 316 if region.width() > 1 { -1 } else { 0 }, 317 if region.height() > 1 { -1 } else { 0 }, 318 ) 319 } 320 Object::Rectangle(start, end) => { 321 Region::new(*start, *end).unwrap().enlarged(-1, -1) 322 } 323 Object::Text(anchor, _, _) 324 | Object::CenteredText(anchor, ..) 325 | Object::Dot(anchor) 326 | Object::SmallCircle(anchor) => anchor.region(), 327 Object::BigCircle(center) => center.region(), 328 Object::Image(region, ..) => *region, 329 Object::RawSVG(_) => { 330 unimplemented!() 331 } 332 } 333 } 334 335 pub fn fillable(&self) -> bool { 336 !matches!( 337 self, 338 Object::Line(..) | Object::CurveInward(..) | Object::CurveOutward(..) 339 ) 340 } 341 342 pub fn hatchable(&self) -> bool { 343 self.fillable() && !matches!(self, Object::Dot(..)) 344 } 345}