This repository has no description
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}