this repo has no description
3
fork

Configure Feed

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

🚧 Expose more stuff to the WASM/JS interface

authored by

Gwenn Le Bihan and committed by
Ewen Le Bihan
fab18d11 1c322631

+363 -43
+1
Cargo.lock
··· 607 607 "indicatif", 608 608 "itertools", 609 609 "midly", 610 + "once_cell", 610 611 "rand", 611 612 "rust-analyzer", 612 613 "serde",
+2 -1
Cargo.toml
··· 17 17 itertools = "0.12.1" 18 18 midly = "0.5.3" 19 19 rand = "0.8.5" 20 - serde = "1.0.147" 20 + serde = { version = "1.0.147", features = ["derive"] } 21 21 serde_cbor = "0.11.2" 22 22 serde_json = "1.0.91" 23 23 svg = "0.17.0" ··· 33 33 'Node', 34 34 'Window', 35 35 ] } 36 + once_cell = "1.19.0" 36 37 37 38 38 39 [dev-dependencies]
+4
src/anchors.rs
··· 1 + use wasm_bindgen::prelude::wasm_bindgen; 2 + 3 + #[wasm_bindgen] 1 4 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 2 5 pub struct Anchor(pub i32, pub i32); 3 6 ··· 15 18 } 16 19 17 20 #[derive(Debug, Clone, Copy, PartialEq, Eq, Default)] 21 + #[wasm_bindgen] 18 22 pub struct CenterAnchor(pub i32, pub i32); 19 23 20 24 impl CenterAnchor {
+15 -27
src/canvas.rs
··· 5 5 use rand::Rng; 6 6 7 7 use crate::{ 8 - layer::Layer, objects::Object, Anchor, CenterAnchor, Color, ColorMapping, Fill, LineSegment, 9 - ObjectSizes, Region, 8 + layer::Layer, objects::Object, random_color, Anchor, CenterAnchor, Color, ColorMapping, 9 + ColoredObject, Fill, LineSegment, ObjectSizes, Region, 10 10 }; 11 11 12 12 #[derive(Debug, Clone)] ··· 110 110 match self.layer_safe(layer) { 111 111 None => Err(format!("Layer {} does not exist", layer)), 112 112 Some(layer) => { 113 - layer.objects.insert(name.to_string(), (object, fill)); 113 + layer 114 + .objects 115 + .insert(name.to_string(), (object, fill).into()); 114 116 Ok(()) 115 117 } 116 118 } ··· 162 164 } 163 165 164 166 pub fn random_layer_within(&self, name: &str, region: &Region) -> Layer { 165 - let mut objects: HashMap<String, (Object, Option<Fill>)> = HashMap::new(); 167 + let mut objects: HashMap<String, ColoredObject> = HashMap::new(); 166 168 let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 167 169 for i in 0..number_of_objects { 168 170 let object = self.random_object_within(region); 169 171 objects.insert( 170 172 format!("{}#{}", name, i), 171 - ( 173 + ColoredObject( 172 174 object, 173 175 if rand::thread_rng().gen_bool(0.5) { 174 176 Some(self.random_fill()) ··· 186 188 } 187 189 } 188 190 189 - pub fn random_linelikes_within(&self, layer_name: &'static str, region: &Region) -> Layer { 190 - let mut objects: HashMap<String, (Object, Option<Fill>)> = HashMap::new(); 191 + pub fn random_linelikes(&self, layer_name: &str) -> Layer { 192 + self.random_linelikes_within(layer_name, &self.world_region) 193 + } 194 + 195 + pub fn random_linelikes_within(&self, layer_name: &str, region: &Region) -> Layer { 196 + let mut objects: HashMap<String, ColoredObject> = HashMap::new(); 191 197 let number_of_objects = rand::thread_rng().gen_range(self.objects_count_range.clone()); 192 198 for i in 0..number_of_objects { 193 199 let object = self.random_linelike_within(region); 194 200 objects.insert( 195 201 format!("{}#{}", layer_name, i), 196 - ( 202 + ColoredObject( 197 203 object, 198 204 if rand::thread_rng().gen_bool(0.5) { 199 205 Some(self.random_fill()) ··· 341 347 } 342 348 343 349 pub fn random_fill(&self) -> Fill { 344 - Fill::Solid(self.random_color()) 350 + Fill::Solid(random_color()) 345 351 // match rand::thread_rng().gen_range(1..=3) { 346 352 // 1 => Fill::Solid(random_color()), 347 353 // 2 => Fill::Hatched, 348 354 // 3 => Fill::Dotted, 349 355 // _ => unreachable!(), 350 356 // } 351 - } 352 - 353 - pub fn random_color(&self) -> Color { 354 - match rand::thread_rng().gen_range(1..=12) { 355 - 1 => Color::Black, 356 - 2 => Color::White, 357 - 3 => Color::Red, 358 - 4 => Color::Green, 359 - 5 => Color::Blue, 360 - 6 => Color::Yellow, 361 - 7 => Color::Orange, 362 - 8 => Color::Purple, 363 - 9 => Color::Brown, 364 - 10 => Color::Pink, 365 - 11 => Color::Gray, 366 - 12 => Color::Cyan, 367 - _ => unreachable!(), 368 - } 369 357 } 370 358 371 359 pub fn clear(&mut self) {
+38 -1
src/color.rs
··· 6 6 }; 7 7 8 8 use serde::Deserialize; 9 + use rand::Rng; 9 10 use wasm_bindgen::prelude::*; 10 11 11 12 #[wasm_bindgen] ··· 23 24 Cyan, 24 25 Pink, 25 26 Gray, 27 + } 28 + 29 + #[wasm_bindgen] 30 + pub fn random_color() -> Color { 31 + match rand::thread_rng().gen_range(1..=12) { 32 + 1 => Color::Black, 33 + 2 => Color::White, 34 + 3 => Color::Red, 35 + 4 => Color::Green, 36 + 5 => Color::Blue, 37 + 6 => Color::Yellow, 38 + 7 => Color::Orange, 39 + 8 => Color::Purple, 40 + 9 => Color::Brown, 41 + 10 => Color::Pink, 42 + 11 => Color::Gray, 43 + 12 => Color::Cyan, 44 + _ => unreachable!(), 45 + } 26 46 } 27 47 28 48 impl Default for Color { ··· 88 108 } 89 109 } 90 110 111 + #[wasm_bindgen(getter_with_clone)] 91 112 #[derive(Debug, Deserialize, Clone)] 92 113 pub struct ColorMapping { 93 114 pub black: String, ··· 104 125 pub gray: String, 105 126 } 106 127 128 + #[wasm_bindgen] 107 129 impl ColorMapping { 108 130 pub fn default() -> Self { 109 131 ColorMapping { ··· 122 144 } 123 145 } 124 146 147 + pub fn from_json(content: &str) -> ColorMapping { 148 + let json: HashMap<String, String> = serde_json::from_str(content).unwrap(); 149 + ColorMapping::from_hashmap(json) 150 + } 151 + 152 + pub fn from_css(content: &str) -> ColorMapping { 153 + let mut mapping = ColorMapping::default(); 154 + for line in content.lines() { 155 + mapping.from_css_line(&line); 156 + } 157 + mapping 158 + } 159 + } 160 + 161 + impl ColorMapping { 125 162 pub fn from_cli_args(args: &Vec<String>) -> ColorMapping { 126 163 let mut colormap: HashMap<String, String> = HashMap::new(); 127 164 for mapping in args { ··· 209 246 } 210 247 211 248 pub fn from_css_file(path: PathBuf) -> ColorMapping { 249 + let mut mapping = ColorMapping::default(); 212 250 let file = File::open(path).unwrap(); 213 251 let lines = std::io::BufReader::new(file).lines(); 214 - let mut mapping = ColorMapping::default(); 215 252 for line in lines { 216 253 if let Ok(line) = line { 217 254 mapping.from_css_line(&line);
+2 -2
src/fill.rs
··· 69 69 fn render_stroke_css(&self, colormap: &ColorMapping) -> String { 70 70 match self { 71 71 Fill::Solid(color) => { 72 - format!("stroke: {};", color.to_string(colormap)) 72 + format!("stroke: {}; fill: transparent;", color.to_string(colormap)) 73 73 } 74 74 Fill::Translucent(color, opacity) => { 75 75 format!( 76 - "stroke: {}; opacity: {};", 76 + "stroke: {}; opacity: {}; fill: transparent;", 77 77 color.to_string(colormap), 78 78 opacity 79 79 )
+9 -7
src/layer.rs
··· 1 - use crate::{ColorMapping, Fill, Object, ObjectSizes}; 1 + use crate::{ColorMapping, ColoredObject, Fill, Object, ObjectSizes}; 2 2 use std::collections::HashMap; 3 + use wasm_bindgen::prelude::*; 3 4 4 5 #[derive(Debug, Clone, Default)] 6 + // #[wasm_bindgen(getter_with_clone)] 5 7 pub struct Layer { 6 8 pub object_sizes: ObjectSizes, 7 - pub objects: HashMap<String, (Object, Option<Fill>)>, 9 + pub objects: HashMap<String, ColoredObject>, 8 10 pub name: String, 9 11 pub _render_cache: Option<svg::node::element::Group>, 10 12 } ··· 19 21 } 20 22 } 21 23 22 - pub fn object(&mut self, name: &str) -> &mut (Object, Option<Fill>) { 24 + pub fn object(&mut self, name: &str) -> &mut ColoredObject { 23 25 self.objects.get_mut(name).unwrap() 24 26 } 25 27 ··· 34 36 } 35 37 36 38 pub fn paint_all_objects(&mut self, fill: Fill) { 37 - for (_id, (_, maybe_fill)) in &mut self.objects { 39 + for (_id, ColoredObject(_, ref mut maybe_fill)) in &mut self.objects { 38 40 *maybe_fill = Some(fill.clone()); 39 41 } 40 42 self.flush(); ··· 43 45 pub fn move_all_objects(&mut self, dx: i32, dy: i32) { 44 46 self.objects 45 47 .iter_mut() 46 - .for_each(|(_, (obj, _))| obj.translate(dx, dy)); 48 + .for_each(|(_, ColoredObject(obj, _))| obj.translate(dx, dy)); 47 49 self.flush(); 48 50 } 49 51 50 52 pub fn add_object(&mut self, name: &str, object: Object, fill: Option<Fill>) { 51 - self.objects.insert(name.to_string(), (object, fill)); 53 + self.objects.insert(name.to_string(), (object, fill).into()); 52 54 self.flush(); 53 55 } 54 56 ··· 77 79 .set("class", "layer") 78 80 .set("data-layer", self.name.clone()); 79 81 80 - for (id, (object, maybe_fill)) in &self.objects { 82 + for (id, ColoredObject(object, maybe_fill)) in &self.objects { 81 83 layer_group = layer_group.add(object.render( 82 84 cell_size, 83 85 object_sizes,
src/main.rs

This is a binary file and will not be displayed.

+11
src/objects.rs
··· 1 1 use crate::{Anchor, CenterAnchor, ColorMapping, Coordinates, Fill, Region}; 2 + use wasm_bindgen::prelude::*; 2 3 3 4 #[derive(Debug, Clone, PartialEq, Eq)] 4 5 pub enum LineSegment { ··· 21 22 RawSVG(Box<dyn svg::Node>), 22 23 } 23 24 25 + #[derive(Debug, Clone)] 26 + pub struct ColoredObject(pub Object, pub Option<Fill>); 27 + 28 + impl From<(Object, Option<Fill>)> for ColoredObject { 29 + fn from(value: (Object, Option<Fill>)) -> Self { 30 + ColoredObject(value.0, value.1) 31 + } 32 + } 33 + 34 + #[wasm_bindgen] 24 35 #[derive(Debug, Clone, Copy)] 25 36 pub struct ObjectSizes { 26 37 pub empty_shape_stroke_width: f32,
+241 -3
src/web.rs
··· 1 - use wasm_bindgen::prelude::wasm_bindgen; 2 - use wasm_bindgen::JsValue; 1 + use std::ptr::NonNull; 2 + use std::sync::Mutex; 3 3 4 - use crate::{Canvas, Color, ColorMapping, Fill}; 4 + use once_cell::sync::Lazy; 5 + use wasm_bindgen::{closure::Closure, prelude::wasm_bindgen}; 6 + use wasm_bindgen::{JsValue, UnwrapThrowExt}; 7 + 8 + use crate::{layer, Anchor, Canvas, CenterAnchor, Color, ColorMapping, Fill, Layer, Object}; 9 + 10 + static WEB_CANVAS: Lazy<Mutex<Canvas>> = Lazy::new(|| Mutex::new(Canvas::default_settings())); 11 + 12 + fn canvas() -> std::sync::MutexGuard<'static, Canvas> { 13 + WEB_CANVAS.lock().unwrap() 14 + } 5 15 6 16 #[wasm_bindgen(start)] 7 17 pub fn main() -> Result<(), JsValue> { ··· 50 60 body.append_child(&output)?; 51 61 Ok(()) 52 62 } 63 + 64 + #[wasm_bindgen] 65 + pub enum MidiEvent { 66 + Note, 67 + ControlChange, 68 + } 69 + 70 + #[wasm_bindgen] 71 + pub struct MidiEventData([u8; 3]); 72 + 73 + #[wasm_bindgen] 74 + pub struct MidiPitch(u8); 75 + 76 + #[wasm_bindgen] 77 + impl MidiPitch { 78 + pub fn octave(&self) -> u8 { 79 + self.0 / 12 80 + } 81 + } 82 + 83 + pub struct Percentage(pub f32); 84 + 85 + impl From<u8> for Percentage { 86 + fn from(value: u8) -> Self { 87 + Self(value as f32 / 127.0) 88 + } 89 + } 90 + 91 + pub enum MidiMessage { 92 + NoteOn(MidiPitch, Percentage), 93 + NoteOff(MidiPitch), 94 + PedalOn, 95 + PedalOff, 96 + ControlChange(u8, Percentage), 97 + } 98 + 99 + impl From<(MidiEvent, MidiEventData)> for MidiMessage { 100 + fn from(value: (MidiEvent, MidiEventData)) -> Self { 101 + match value { 102 + (MidiEvent::Note, MidiEventData([pitch, velocity, _])) => { 103 + if velocity == 0 { 104 + MidiMessage::NoteOff(MidiPitch(pitch)) 105 + } else { 106 + MidiMessage::NoteOn(MidiPitch(pitch), velocity.into()) 107 + } 108 + } 109 + (MidiEvent::ControlChange, MidiEventData([64, value, _])) => { 110 + if value == 0 { 111 + MidiMessage::PedalOff 112 + } else { 113 + MidiMessage::PedalOn 114 + } 115 + } 116 + (MidiEvent::ControlChange, MidiEventData([controller, value, _])) => { 117 + MidiMessage::ControlChange(controller, value.into()) 118 + } 119 + } 120 + } 121 + } 122 + 123 + #[wasm_bindgen] 124 + pub fn render_canvas(layers_pattern: Option<String>, render_background: Option<bool>) -> String { 125 + canvas().render( 126 + &match layers_pattern { 127 + Some(ref pattern) => vec![pattern], 128 + None => vec!["*"], 129 + }, 130 + render_background.unwrap_or(false), 131 + ) 132 + } 133 + 134 + #[wasm_bindgen] 135 + pub fn set_palette(palette: ColorMapping) -> () { 136 + canvas().colormap = palette; 137 + } 138 + 139 + #[wasm_bindgen] 140 + pub fn new_layer(name: &str) -> LayerWeb { 141 + canvas().add_or_replace_layer(Layer::new(name)); 142 + LayerWeb { 143 + name: name.to_string(), 144 + } 145 + } 146 + 147 + #[wasm_bindgen] 148 + pub fn get_layer(name: &str) -> Result<LayerWeb, JsValue> { 149 + match canvas().layer_safe(name) { 150 + Some(layer) => Ok(LayerWeb { 151 + name: layer.name.clone(), 152 + }), 153 + None => Err(JsValue::from_str("Layer not found")), 154 + } 155 + } 156 + 157 + #[wasm_bindgen] 158 + pub fn random_linelikes(name: &str) -> LayerWeb { 159 + canvas().add_or_replace_layer(canvas().random_linelikes(name)); 160 + LayerWeb { 161 + name: name.to_string(), 162 + } 163 + } 164 + 165 + #[wasm_bindgen(getter_with_clone)] 166 + pub struct LayerWeb { 167 + pub name: String, 168 + } 169 + 170 + #[wasm_bindgen] 171 + impl LayerWeb { 172 + pub fn render(&self) -> String { 173 + canvas().render(&vec![&self.name], false) 174 + } 175 + 176 + pub fn render_into(&self, selector: String) -> () { 177 + let window = web_sys::window().expect_throw("no global `window` exists"); 178 + let document = window 179 + .document() 180 + .expect_throw("should have a document on window"); 181 + let element = document 182 + .query_selector(&selector) 183 + .expect_throw(&format!("selector '{}' not found", selector)) 184 + .expect_throw("could not get the element, but is was found (shouldn't happen)"); 185 + 186 + let output = document.create_element("div").unwrap(); 187 + output.set_class_name("frame"); 188 + output.set_inner_html(&self.render()); 189 + element.append_child(&output).unwrap(); 190 + } 191 + 192 + pub fn paint_all(&self, color: Color, opacity: Option<f32>) -> () { 193 + canvas() 194 + .layer(&self.name) 195 + .paint_all_objects(Fill::Translucent(color, opacity.unwrap_or(1.0))); 196 + } 197 + 198 + pub fn random(name: &str) -> Self { 199 + let layer = canvas().random_layer(name); 200 + canvas().add_or_replace_layer(layer); 201 + LayerWeb { 202 + name: name.to_string(), 203 + } 204 + } 205 + 206 + pub fn new_line( 207 + &self, 208 + name: &str, 209 + start: Anchor, 210 + end: Anchor, 211 + thickness: f32, 212 + color: Color, 213 + ) -> () { 214 + canvas().layer(name).add_object( 215 + name, 216 + Object::Line(start, end, thickness), 217 + Some(Fill::Solid(color)), 218 + ) 219 + } 220 + pub fn new_curve_outward( 221 + &self, 222 + name: &str, 223 + start: Anchor, 224 + end: Anchor, 225 + thickness: f32, 226 + color: Color, 227 + ) -> () { 228 + canvas().layer(name).add_object( 229 + name, 230 + Object::CurveOutward(start, end, thickness), 231 + Some(Fill::Solid(color)), 232 + ) 233 + } 234 + pub fn new_curve_inward( 235 + &self, 236 + name: &str, 237 + start: Anchor, 238 + end: Anchor, 239 + thickness: f32, 240 + color: Color, 241 + ) -> () { 242 + canvas().layer(name).add_object( 243 + name, 244 + Object::CurveInward(start, end, thickness), 245 + Some(Fill::Solid(color)), 246 + ) 247 + } 248 + pub fn new_small_circle(&self, name: &str, center: Anchor, color: Color) -> () { 249 + canvas() 250 + .layer(name) 251 + .add_object(name, Object::SmallCircle(center), Some(Fill::Solid(color))) 252 + } 253 + pub fn new_dot(&self, name: &str, center: Anchor, color: Color) -> () { 254 + canvas() 255 + .layer(name) 256 + .add_object(name, Object::Dot(center), Some(Fill::Solid(color))) 257 + } 258 + pub fn new_big_circle(&self, name: &str, center: CenterAnchor, color: Color) -> () { 259 + canvas() 260 + .layer(name) 261 + .add_object(name, Object::BigCircle(center), Some(Fill::Solid(color))) 262 + } 263 + pub fn new_text( 264 + &self, 265 + name: &str, 266 + anchor: Anchor, 267 + text: String, 268 + font_size: f32, 269 + color: Color, 270 + ) -> () { 271 + canvas().layer(name).add_object( 272 + name, 273 + Object::Text(anchor, text, font_size), 274 + Some(Fill::Solid(color)), 275 + ) 276 + } 277 + pub fn new_rectangle( 278 + &self, 279 + name: &str, 280 + topleft: Anchor, 281 + bottomright: Anchor, 282 + color: Color, 283 + ) -> () { 284 + canvas().layer(name).add_object( 285 + name, 286 + Object::Rectangle(topleft, bottomright), 287 + Some(Fill::Solid(color)), 288 + ) 289 + } 290 + }
+40 -2
web/index.html
··· 6 6 <title>Shapemaker Web</title> 7 7 </head> 8 8 <body> 9 + <div class="prev"></div> 9 10 <script type="module"> 10 - import init, { render_image, Color, color_name } from "./shapemaker.js" 11 + import init, { 12 + render_image, 13 + LayerWeb, 14 + render_canvas, 15 + Color, 16 + color_name, 17 + set_palette, 18 + random_linelikes, 19 + ColorMapping, 20 + random_color, 21 + } from "./shapemaker.js" 11 22 async function run() { 12 23 await init() 13 24 window.renderImage = (vel, col) => { ··· 16 27 ?.forEach((el) => el.remove()) 17 28 render_image(vel, col) 18 29 } 30 + 31 + set_palette( 32 + ColorMapping.from_css(` 33 + :root { 34 + black: #000000; 35 + white: #ffffff; 36 + red: #cf0a2b; 37 + green: #22e753; 38 + blue: #2734e6; 39 + yellow: #f8e21e; 40 + orange: #f05811; 41 + purple: #6a24ec; 42 + brown: #a05634; 43 + pink: #e92e76; 44 + gray: #81a0a8; 45 + cyan: #4fecec; 46 + } 47 + `) 48 + ) 19 49 } 20 50 run() 21 51 ··· 32 62 } 33 63 34 64 window.pedal_held = false 65 + window.previousColor = null 35 66 36 67 window.addEventListener("keypress", (e) => { 37 68 if (e.key === " ") { 38 - window.renderImage(1, Color.White) 69 + const layer = random_linelikes("feur") 70 + let color = random_color() 71 + while ([Color.Black, Color.Gray, window.previousColor].includes(color)) { 72 + color = random_color() 73 + } 74 + window.previousColor = color 75 + layer.paint_all(color, 0.75) 76 + layer.render_into("body") 39 77 } 40 78 }) 41 79