PC Music Generator - a Virtual Modular Synthesizer
at main 290 lines 7.9 kB view raw
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}