PC Music Generator - a Virtual Modular Synthesizer
1use egui::{
2 epaint::{
3 CircleShape,
4 PathShape,
5 RectShape,
6 TextShape,
7 },
8 Color32,
9 FontFamily,
10 FontId,
11 Rounding,
12 Shape,
13 Stroke,
14 Ui,
15};
16use emath::{
17 vec2,
18 Pos2,
19 Rect,
20 Vec2,
21};
22use serde::{
23 Deserialize,
24 Serialize,
25};
26
27use self::templates::{
28 VisualComponentTemplate,
29 VisualShapeTemplate,
30};
31
32pub mod templates;
33
34#[derive(Serialize, Deserialize, Clone, Copy, Debug, PartialEq)]
35pub enum Mode {
36 Static,
37 Rotate,
38 Shift(Vec2),
39}
40
41impl std::fmt::Display for Mode {
42 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
43 std::fmt::Debug::fmt(self, f)
44 }
45}
46
47impl Mode {
48 pub fn all() -> [Self; 3] {
49 use Mode::*;
50 [Static, Rotate, Shift(Vec2::default())]
51 }
52}
53
54#[derive(Serialize, Deserialize)]
55pub struct VisualComponent {
56 pub shape: VisualShape,
57 pub color: VisualColor,
58 pub show: Activity,
59 pub mode: Mode,
60 pub thickness: f32,
61}
62
63impl VisualComponent {
64 pub fn show(&self, ui: &mut Ui, widget_center: Pos2, theme: VisualTheme) {
65 self.show_with_value(ui, widget_center, theme, 0.0)
66 }
67
68 pub fn show_with_value(
69 &self,
70 ui: &mut Ui,
71 widget_center: Pos2,
72 theme: VisualTheme,
73 value: f32,
74 ) {
75 let color = match self.color {
76 VisualColor::Highlight => theme.highlight_color,
77 VisualColor::Midtone => theme.midtone_color,
78 VisualColor::Lowlight => theme.lowlight_color,
79 VisualColor::Accent => theme.accent_color,
80 VisualColor::Text => theme.text_color,
81 };
82
83 let translation = {
84 match self.mode {
85 Mode::Static => {
86 Box::new(|pos: Pos2| widget_center + pos.to_vec2()) as Box<dyn Fn(_) -> _>
87 }
88 Mode::Rotate => Box::new(|pos: Pos2| {
89 let Pos2 { x, y } = pos;
90 let nx = x * value.cos() - y * value.sin();
91 let ny = y * value.cos() + x * value.sin();
92 widget_center + vec2(nx, ny)
93 }),
94 Mode::Shift(shift) => {
95 Box::new(move |pos: Pos2| widget_center + pos.to_vec2() + (shift * value))
96 }
97 }
98 };
99
100 let shape: Shape = match self.shape.clone() {
101 VisualShape::Line(mut line) => {
102 line.iter_mut().for_each(|p| *p = translation(*p));
103 if line.first() == line.last() {
104 line.pop();
105 PathShape::closed_line(
106 line,
107 Stroke {
108 width: self.thickness,
109 color,
110 },
111 )
112 .into()
113 } else {
114 PathShape::line(
115 line,
116 Stroke {
117 width: self.thickness,
118 color,
119 },
120 )
121 .into()
122 }
123 }
124 VisualShape::Circle(p, r) => CircleShape::stroke(
125 translation(p),
126 r,
127 Stroke {
128 width: self.thickness,
129 color,
130 },
131 )
132 .into(),
133 VisualShape::Text(p, t, f) => {
134 let galley = ui.fonts(|r| {
135 r.layout_no_wrap(
136 t,
137 FontId {
138 size: self.thickness,
139 family: f,
140 },
141 color,
142 )
143 });
144
145 TextShape::new(
146 translation(p) - galley.size() / 2.0,
147 galley,
148 Color32::default(),
149 )
150 .into()
151 }
152 VisualShape::Rect(min, max, fc) => {
153 let fill = match fc {
154 Some(VisualColor::Highlight) => theme.highlight_color,
155 Some(VisualColor::Midtone) => theme.midtone_color,
156 Some(VisualColor::Lowlight) => theme.lowlight_color,
157 Some(VisualColor::Accent) => theme.accent_color,
158 Some(VisualColor::Text) => theme.text_color,
159 None => Color32::TRANSPARENT,
160 };
161 RectShape::new(
162 Rect::from_min_max(translation(min), translation(max)),
163 Rounding::ZERO,
164 fill,
165 Stroke {
166 width: self.thickness,
167 color,
168 },
169 )
170 .into()
171 }
172 };
173 ui.painter().add(shape);
174 }
175}
176
177impl TryFrom<VisualComponentTemplate> for VisualComponent {
178 type Error = ();
179
180 fn try_from(value: VisualComponentTemplate) -> Result<Self, Self::Error> {
181 let VisualComponentTemplate {
182 shape,
183 color,
184 show,
185 thickness,
186 mode,
187 } = value;
188 let shape = match shape {
189 VisualShapeTemplate::Line(l) => {
190 if l.is_empty() {
191 return Err(());
192 }
193 VisualShape::Line(l)
194 }
195 VisualShapeTemplate::Circle(p, r) => VisualShape::Circle(p.ok_or(())?, r.ok_or(())?),
196 VisualShapeTemplate::Text(p, t, f) => VisualShape::Text(p.ok_or(())?, t, f),
197 VisualShapeTemplate::Rect(min, max, fill) => {
198 VisualShape::Rect(min.ok_or(())?, max.ok_or(())?, fill)
199 }
200 };
201 Ok(VisualComponent {
202 shape,
203 color,
204 show,
205 thickness,
206 mode,
207 })
208 }
209}
210
211#[derive(Serialize, Deserialize, Debug, PartialEq, Clone, Copy, Default)]
212pub enum VisualColor {
213 Highlight,
214 #[default]
215 Midtone,
216 Lowlight,
217 Accent,
218 Text,
219}
220
221impl std::fmt::Display for VisualColor {
222 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
223 std::fmt::Debug::fmt(self, f)
224 }
225}
226
227impl VisualColor {
228 pub fn all() -> [Self; 5] {
229 use VisualColor::*;
230 [Highlight, Midtone, Lowlight, Accent, Text]
231 }
232}
233
234#[derive(Serialize, Deserialize, PartialEq, Clone)]
235pub enum VisualShape {
236 Line(Vec<Pos2>),
237 Rect(Pos2, Pos2, Option<VisualColor>),
238 Circle(Pos2, f32),
239 Text(Pos2, String, FontFamily),
240}
241
242#[derive(Serialize, Deserialize, Clone, Copy, Debug)]
243pub struct VisualTheme {
244 pub highlight_color: Color32,
245 pub midtone_color: Color32,
246 pub lowlight_color: Color32,
247 pub accent_color: Color32,
248 pub text_color: Color32,
249 pub background_color: Color32,
250 pub background_accent_color: Color32,
251}
252
253impl Default for VisualTheme {
254 fn default() -> Self {
255 Self {
256 highlight_color: Color32::WHITE,
257 midtone_color: Color32::GRAY,
258 lowlight_color: Color32::DARK_GRAY,
259 accent_color: Color32::GOLD,
260 text_color: Color32::GRAY,
261 background_color: Color32::from_rgb(40, 80, 40),
262 background_accent_color: Color32::from_rgb(60, 100, 40),
263 }
264 }
265}
266
267// module or entire rack has a set theme
268#[derive(Serialize, Deserialize)]
269struct ThemeId(usize);
270
271#[derive(Serialize, Deserialize, Default, Debug, PartialEq, Clone, Copy)]
272pub enum Activity {
273 #[default]
274 Always,
275 OnHover,
276 OnInteract,
277}
278
279impl Activity {
280 pub fn all() -> [Self; 3] {
281 use Activity::*;
282 [Always, OnHover, OnInteract]
283 }
284}
285
286impl std::fmt::Display for Activity {
287 fn fmt(&self, f: &mut std::fmt::Formatter<'_>) -> std::fmt::Result {
288 std::fmt::Debug::fmt(self, f)
289 }
290}