experimental SVG-based video rendering engine made for music videos. React to MIDI or arbitrary signals from your DAW through "probe" VSTs
1use super::shapes::Shape;
2use crate::{Angle, Fill, Filter, Point, Region, Transformation};
3use itertools::Itertools;
4use std::fmt::Display;
5#[cfg(feature = "web")]
6use wasm_bindgen::prelude::*;
7
8use super::{Color, fill::FillOperations};
9
10impl Shape {
11 pub fn filled(self, fill: Fill) -> Object {
12 Object::from((self, Some(fill)))
13 }
14
15 pub fn colored(self, color: Color) -> Object {
16 Object::from((self, None)).colored(color)
17 }
18
19 pub fn filtered(self, filter: Filter) -> Object {
20 Object::from((self, None)).filtered(filter)
21 }
22
23 pub fn transform(self, transformation: Transformation) -> Object {
24 Object::from((self, None)).transformed(transformation)
25 }
26}
27
28#[derive(Debug, Clone)]
29pub struct Object {
30 pub shape: Shape,
31 pub fill: Option<Fill>,
32 pub filters: Vec<Filter>,
33 pub transformations: Vec<Transformation>,
34 pub tags: Vec<String>,
35 pub clip_to: Option<Region>,
36}
37
38impl Object {
39 pub fn filtered(mut self, filter: Filter) -> Self {
40 self.filters.push(filter);
41 self
42 }
43
44 pub fn transformed(mut self, transformation: Transformation) -> Self {
45 self.transformations.push(transformation);
46 self
47 }
48
49 pub fn filled(mut self, fill: Fill) -> Self {
50 self.fill = Some(fill);
51 self
52 }
53
54 pub fn colored(mut self, color: Color) -> Self {
55 self.fill = Some(Fill::Solid(color));
56 self
57 }
58
59 pub fn opacified(mut self, opacity: f32) -> Self {
60 if let Some(fill) = &mut self.fill {
61 *fill = fill.opacify(opacity);
62 }
63 self
64 }
65
66 pub fn clipped_to(mut self, region: impl Into<Region>) -> Self {
67 self.clip_to = Some(region.into());
68 self
69 }
70
71 pub fn clear_filters(&mut self) {
72 self.filters.clear();
73 }
74
75 pub fn refill(&mut self, fill: Fill) {
76 self.fill = Some(fill);
77 }
78
79 pub fn recolor(&mut self, color: Color) {
80 self.fill = Some(Fill::Solid(color))
81 }
82
83 pub fn filter(&mut self, filter: Filter) {
84 self.filters.push(filter)
85 }
86
87 pub fn rotate(&mut self, angle: Angle) {
88 self.transformations
89 .push(Transformation::Rotate(angle.degrees()))
90 }
91
92 pub fn set_rotation(&mut self, angle: Angle) {
93 self.transformations
94 .retain(|t| !matches!(t, Transformation::Rotate(_)));
95 self.transformations
96 .push(Transformation::Rotate(angle.degrees()))
97 }
98
99 pub fn region(&self) -> Region {
100 self.shape.region()
101 }
102
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
112 }
113
114 pub fn remove_tag(&mut self, tag: impl Display) {
115 let tag_str = format!("{tag}");
116 self.tags.retain(|t| t != &tag_str);
117 }
118
119 pub fn tagged(mut self, tag: impl Display) -> Self {
120 self.tags.push(format!("{tag}"));
121 self
122 }
123
124 pub fn has_tag(&self, tag: impl Display) -> bool {
125 let tag_str = format!("{tag}");
126 self.tags.iter().any(|t| t == &tag_str)
127 }
128}
129
130impl std::fmt::Display for Object {
131 fn fmt(&self, f: &mut std::fmt::Formatter) -> std::fmt::Result {
132 let Object {
133 shape: object,
134 fill,
135 filters,
136 transformations,
137 tags,
138 clip_to,
139 } = self;
140
141 if fill.is_some() {
142 write!(f, "{:?} {:?}", fill.unwrap(), object)?;
143 } else {
144 write!(f, "transparent {:?}", object)?;
145 }
146
147 if !filters.is_empty() {
148 write!(f, " with filters {:?}", filters)?;
149 }
150
151 if !transformations.is_empty() {
152 write!(f, " with transformations {:?}", transformations)?;
153 }
154
155 if !tags.is_empty() {
156 write!(f, "{}", tags.iter().map(|t| format!("#{t}")).join(" "))?;
157 }
158
159 if let Some(clip_to) = clip_to {
160 write!(f, " (clipped to {:?})", clip_to)?;
161 }
162
163 Ok(())
164 }
165}
166
167impl From<Shape> for Object {
168 fn from(value: Shape) -> Self {
169 Object {
170 shape: value,
171 fill: None,
172 filters: vec![],
173 transformations: vec![],
174 tags: vec![],
175 clip_to: None,
176 }
177 }
178}
179
180impl From<(Shape, Option<Fill>)> for Object {
181 fn from((object, fill): (Shape, Option<Fill>)) -> Self {
182 Object {
183 shape: object,
184 fill,
185 filters: vec![],
186 transformations: vec![],
187 tags: vec![],
188 clip_to: None,
189 }
190 }
191}
192
193#[cfg_attr(feature = "web", wasm_bindgen)]
194#[derive(Debug, Clone, Copy)]
195pub struct ObjectSizes {
196 pub empty_shape_stroke_width: f32,
197 pub small_circle_radius: f32,
198 pub dot_radius: f32,
199 pub default_line_width: f32,
200}
201
202impl Default for ObjectSizes {
203 fn default() -> Self {
204 Self {
205 empty_shape_stroke_width: 0.5,
206 small_circle_radius: 5.0,
207 dot_radius: 2.0,
208 default_line_width: 2.0,
209 }
210 }
211}