alpha
Login
or
Join now
gwen.works
/
shapemaker
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
This repository has no description
Star
0
Fork
0
Atom
Configure Feed
Issues
Pull Requests
Commits
Tags
Feed URL
Select the types of activity you want to include in your feed.
Overview
Issues
Pulls
Pipelines
🚸 General API improvements
author
Gwenn Le Bihan
date
8 months ago
(Oct 22, 2025, 7:28 PM +0200)
commit
42af421a
42af421a27ff2f3b7bc560527b12bd4e3ef80d26
parent
d15be194
d15be1940417a96988b380223ad2982910ffecdb
+108
-41
7 changed files
Expand all
Collapse all
Unified
Split
examples
dna-analysis-machine
src
main.rs
schedule-hell
src
main.rs
src
geometry
region.rs
graphics
canvas.rs
layer.rs
random
canvas.rs
video
context.rs
+12
-3
examples/dna-analysis-machine/src/main.rs
Reviewed
···
1
1
use itertools::Itertools;
2
2
-
use rand::{SeedableRng, Rng};
2
2
+
use rand::{Rng, SeedableRng};
3
3
use shapemaker::*;
4
4
5
5
fn artwork() -> Canvas {
···
29
29
Region::from_bottomleft(draw_in.bottomleft().translated(2, -1), (3, 3))
30
30
.unwrap();
31
31
32
32
-
canvas.n_random_curves_within(&mut rng, &strands_in, 30, "strands");
32
32
+
let strands =
33
33
+
canvas.n_random_curves_within(&mut rng, &strands_in, 30, "strands");
34
34
+
35
35
+
canvas.add_layer(strands);
33
36
34
34
-
for (i, (_key, obj)) in canvas.layer("strands").objects.iter_mut().sorted_by_key(|(k, _)| *k).enumerate() {
37
37
+
for (i, (_key, obj)) in canvas
38
38
+
.layer("strands")
39
39
+
.objects
40
40
+
.iter_mut()
41
41
+
.sorted_by_key(|(k, _)| *k)
42
42
+
.enumerate()
43
43
+
{
35
44
obj.recolor(if i % 2 == 0 { Cyan } else { Pink });
36
45
obj.filter(Filter::glow(4.0));
37
46
}
+46
-16
examples/schedule-hell/src/main.rs
Reviewed
···
104
104
Ok(())
105
105
})
106
106
.on_note("bass", &|canvas, ctx| {
107
107
-
let new_layer = canvas.random_layer_within(
107
107
+
let pitch = ctx
108
108
+
.notes_of_stem("bass")
109
109
+
.find(|note| note.is_on())
110
110
+
.map(|note| note.pitch);
111
111
+
112
112
+
let area = (2, 2);
113
113
+
ctx.extra.bass_pattern_at = match pitch {
114
114
+
Some(32 | 33 | 34) => {
115
115
+
canvas.world_region.starting_from_topleft(area)
116
116
+
}
117
117
+
Some(39) => canvas.world_region.starting_from_topright(area),
118
118
+
Some(35) => canvas.world_region.starting_from_bottomleft(area),
119
119
+
Some(42 | 41) => {
120
120
+
canvas.world_region.starting_from_bottomright(area)
121
121
+
}
122
122
+
_ => canvas.world_region.starting_from_bottomleft(area),
123
123
+
}
124
124
+
.unwrap();
125
125
+
126
126
+
let mut bass = canvas.random_layer_within(
108
127
&mut ctx.extra.rng,
109
128
"bass",
110
129
&ctx.extra.bass_pattern_at,
111
130
);
112
112
-
new_layer.paint_all_objects(Fill::Solid(Color::White));
131
131
+
132
132
+
bass.paint_all_objects(Fill::Solid(Color::White));
133
133
+
canvas.add_or_replace_layer(bass);
134
134
+
113
135
Ok(())
114
136
})
115
137
.on_note("powerful clap hit, clap, perclap", &|canvas, ctx| {
116
116
-
let new_layer = canvas.random_layer_within(
138
138
+
let mut claps = canvas.random_layer_within(
117
139
&mut ctx.extra.rng,
118
140
"claps",
119
141
&ctx.extra.bass_pattern_at.translated(2, 0),
120
142
);
121
121
-
new_layer.paint_all_objects(Fill::Solid(Color::Red));
143
143
+
claps.paint_all_objects(Fill::Solid(Color::Red));
144
144
+
canvas.add_or_replace_layer(claps);
122
145
Ok(())
123
146
})
124
147
.on_note(
125
148
"rimshot, glitchy percs, hitting percs, glitchy percs",
126
149
&|canvas, ctx| {
127
127
-
let new_layer = canvas.random_layer_within(
150
150
+
let mut foley = canvas.random_layer_within(
128
151
&mut ctx.extra.rng,
129
152
"percs",
130
153
&ctx.extra.bass_pattern_at.translated(2, 0),
131
154
);
132
132
-
new_layer.paint_all_objects(Fill::Translucent(Color::Red, 0.5));
155
155
+
foley.paint_all_objects(Fill::Translucent(Color::Red, 0.5));
156
156
+
canvas.add_or_replace_layer(foley);
133
157
Ok(())
134
158
},
135
159
)
136
160
.on_note("qanda", &|canvas, ctx| {
137
161
let canvas_line_width = canvas.object_sizes.default_line_width;
138
138
-
let new_layer = canvas.random_curves_within(
162
162
+
let mut qanda = canvas.random_curves_within(
139
163
&mut ctx.extra.rng,
140
164
"qanda",
141
165
&ctx.extra.bass_pattern_at.translated(-1, -1).enlarged(1, 1),
142
166
3..=5,
143
167
);
144
144
-
new_layer.paint_all_objects(Fill::Solid(Color::Orange));
145
145
-
new_layer.object_sizes.default_line_width =
168
168
+
qanda.paint_all_objects(Fill::Solid(Color::Orange));
169
169
+
qanda.object_sizes.default_line_width =
146
170
canvas_line_width * 4.0 * ctx.stem("qanda").velocity_relative();
171
171
+
172
172
+
canvas.add_or_replace_layer(qanda);
147
173
Ok(())
148
174
})
149
175
.on_note("brokenup", &|canvas, ctx| {
150
176
let canvas_line_width = canvas.object_sizes.default_line_width;
151
151
-
let new_layer = canvas.random_curves_within(
177
177
+
let mut brokenup = canvas.random_curves_within(
152
178
&mut ctx.extra.rng,
153
179
"brokenup",
154
180
&ctx.extra.bass_pattern_at.translated(0, -2),
155
181
3..=5,
156
182
);
157
157
-
new_layer.paint_all_objects(Fill::Solid(Color::Yellow));
158
158
-
new_layer.object_sizes.default_line_width = canvas_line_width
183
183
+
brokenup.paint_all_objects(Fill::Solid(Color::Yellow));
184
184
+
brokenup.object_sizes.default_line_width = canvas_line_width
159
185
* 4.0
160
186
* ctx.stem("brokenup").velocity_relative();
187
187
+
188
188
+
canvas.add_or_replace_layer(brokenup);
161
189
Ok(())
162
190
})
163
191
.on_note("goup", &|canvas, ctx| {
164
192
let canvas_line_width = canvas.object_sizes.default_line_width;
165
165
-
let new_layer = canvas.random_curves_within(
193
193
+
let mut goup = canvas.random_curves_within(
166
194
&mut ctx.extra.rng,
167
195
"goup",
168
196
&ctx.extra.bass_pattern_at.translated(0, 2),
169
197
3..=5,
170
198
);
171
171
-
new_layer.paint_all_objects(Fill::Solid(Color::Green));
172
172
-
new_layer.object_sizes.default_line_width =
199
199
+
goup.paint_all_objects(Fill::Solid(Color::Green));
200
200
+
goup.object_sizes.default_line_width =
173
201
canvas_line_width * 4.0 * ctx.stem("goup").velocity_relative();
202
202
+
203
203
+
canvas.add_or_replace_layer(goup);
174
204
Ok(())
175
205
})
176
206
.on_note("ch", &|canvas, ctx| {
···
210
240
"credits text",
211
241
Object::Text(
212
242
world.start.translated(2, 2),
213
213
-
"by ewen-lbh".into(),
243
243
+
"Postamble / Schedule Hell".into(),
214
244
12.0,
215
245
)
216
246
.colored(Color::White),
+27
-7
src/geometry/region.rs
Reviewed
···
209
209
)
210
210
}
211
211
212
212
+
pub fn starting_from_topleft(&self, size: (usize, usize)) -> Result<Self> {
213
213
+
Self::from_topleft(self.start, size)
214
214
+
}
215
215
+
212
216
pub fn from_bottomleft(origin: Point, size: (usize, usize)) -> Result<Self> {
213
217
Self::from_topleft(origin.translated(0, -(size.1 as i32 - 1)), size)
214
218
}
215
219
220
220
+
pub fn starting_from_bottomleft(&self, size: (usize, usize)) -> Result<Self> {
221
221
+
Self::from_bottomleft(self.bottomleft(), size)
222
222
+
}
223
223
+
216
224
pub fn from_bottomright(origin: Point, size: (usize, usize)) -> Result<Self> {
217
225
Self::from_points(
218
218
-
origin.translated_by(Point::from(size).translated(-1, -1)),
226
226
+
origin.translated(-(size.0 as i32 - 1), -(size.1 as i32 - 1)),
219
227
origin,
220
228
)
229
229
+
}
230
230
+
231
231
+
pub fn starting_from_bottomright(
232
232
+
&self,
233
233
+
size: (usize, usize),
234
234
+
) -> Result<Self> {
235
235
+
Self::from_bottomright(self.bottomright(), size)
221
236
}
222
237
223
238
pub fn from_topright(origin: Point, size: (usize, usize)) -> Result<Self> {
224
239
Self::from_topleft(origin.translated(-(size.0 as i32 - 1), 0), size)
225
240
}
226
241
242
242
+
pub fn starting_from_topright(&self, size: (usize, usize)) -> Result<Self> {
243
243
+
Self::from_topright(self.topright(), size)
244
244
+
}
245
245
+
227
246
pub fn from_center_and_size(
228
247
center: Point,
229
248
size: (usize, usize),
···
270
289
}
271
290
272
291
/// adds dx and dy to the end of the region (dx and dy are _not_ multiplicative but **additive** factors)
273
273
-
pub fn enlarged(&self, dx: i32, dy: i32) -> Self {
292
292
+
pub fn enlarged(&self, add_x: i32, add_y: i32) -> Self {
274
293
let resulting = Self {
275
294
start: self.start,
276
295
end: (
277
277
-
(self.end.0 as i32 + dx) as usize,
278
278
-
(self.end.1 as i32 + dy) as usize,
296
296
+
(self.end.0 as i32 + add_x) as usize,
297
297
+
(self.end.1 as i32 + add_y) as usize,
279
298
)
280
299
.into(),
281
300
};
···
283
302
if resulting.ensure_valid().is_err() {
284
303
let bt = Backtrace::new();
285
304
println!(
286
286
-
"WARN: Did not enlarge region {self} with ({dx}, {dy}), it would result in a non-valid region\n{bt:?}"
305
305
+
"WARN: Did not enlarge region {self} with ({add_x}, {add_y}), it would result in a non-valid region\n{bt:?}"
287
306
);
288
307
return *self;
289
308
}
···
292
311
}
293
312
294
313
/// resized is like enlarged, but transforms from the center, by first translating the region by (-dx, -dy)
295
295
-
pub fn resized(&self, dx: i32, dy: i32) -> Self {
296
296
-
self.translated(-dx / 2, -dy / 2).enlarged(dx, dy)
314
314
+
pub fn resized(&self, add_x: i32, add_y: i32) -> Self {
315
315
+
self.translated(-add_x / 2, -add_y / 2)
316
316
+
.enlarged(add_x, add_y)
297
317
}
298
318
299
319
pub fn x_range(&self) -> std::ops::RangeInclusive<usize> {
+1
-1
src/graphics/canvas.rs
Reviewed
···
180
180
assert!(new_order.iter().all(|name| self.layer_exists(name)));
181
181
182
182
self.layers.sort_by_key(|o| {
183
183
-
new_order.iter().position(|&n| n == o.name).unwrap_or(
183
183
+
new_order.iter().position(|n| *n == o.name).unwrap_or(
184
184
current_order.iter().position(|n| *n == o.name).unwrap(),
185
185
)
186
186
});
+2
-2
src/graphics/layer.rs
Reviewed
···
156
156
layer.add_anon(self);
157
157
}
158
158
159
159
-
pub fn add_named_to(self, name: impl Display, layer: &mut Layer) {
160
160
-
layer.add(name, self);
159
159
+
pub fn set_in(self, layer: &mut Layer, name: impl Display) {
160
160
+
layer.set(name, self);
161
161
}
162
162
}
+11
-11
src/random/canvas.rs
Reviewed
···
3
3
use std::collections::HashMap;
4
4
5
5
impl Canvas {
6
6
-
pub fn random_layer(&mut self, rng: &mut impl Rng, name: &str) -> &mut Layer {
6
6
+
pub fn random_layer(&mut self, rng: &mut impl Rng, name: &str) -> Layer {
7
7
self.random_layer_within(rng, name, &self.world_region.clone())
8
8
}
9
9
···
30
30
region: &Region,
31
31
count: usize,
32
32
layer_name: &str,
33
33
-
) -> &mut Layer {
33
33
+
) -> Layer {
34
34
let mut objects: HashMap<String, ColoredObject> = HashMap::new();
35
35
for i in 0..count {
36
36
let object = Object::random_curve_within(
···
50
50
)),
51
51
);
52
52
}
53
53
-
let layer = Layer {
53
53
+
54
54
+
Layer {
54
55
object_sizes: self.object_sizes,
55
56
name: layer_name.to_owned(),
56
57
objects,
57
58
_render_cache: None,
58
59
hidden: false,
59
59
-
};
60
60
-
self.add_layer(layer)
60
60
+
}
61
61
}
62
62
63
63
pub fn random_curves_within(
···
66
66
layer_name: &str,
67
67
region: &Region,
68
68
object_counts: impl SampleRange<usize>,
69
69
-
) -> &mut Layer {
69
69
+
) -> Layer {
70
70
let number_of_objects = rng.random_range(object_counts);
71
71
self.n_random_curves_within(rng, region, number_of_objects, layer_name)
72
72
}
···
76
76
rng: &mut impl Rng,
77
77
name: &str,
78
78
region: &Region,
79
79
-
) -> &mut Layer {
79
79
+
) -> Layer {
80
80
let mut objects: HashMap<String, ColoredObject> = HashMap::new();
81
81
let number_of_objects =
82
82
rng.random_range(self.objects_count_range.clone());
···
97
97
}),
98
98
);
99
99
}
100
100
-
let layer = Layer {
100
100
+
101
101
+
Layer {
101
102
object_sizes: self.object_sizes,
102
103
name: name.to_string(),
103
104
objects,
104
105
_render_cache: None,
105
106
hidden: false,
106
106
-
};
107
107
-
self.add_layer(layer)
107
107
+
}
108
108
}
109
109
110
110
pub fn random_linelikes(
111
111
&mut self,
112
112
rng: &mut impl Rng,
113
113
layer_name: &str,
114
114
-
) -> &mut Layer {
114
114
+
) -> Layer {
115
115
self.random_curves_within(
116
116
rng,
117
117
layer_name,
+9
-1
src/video/context.rs
Reviewed
···
1
1
use super::animation::{AnimationUpdateFunction, LayerAnimationUpdateFunction};
2
2
use super::engine::{LaterHook, LaterRenderFunction};
3
3
use super::Animation;
4
4
-
use crate::synchronization::audio::StemAtInstant;
4
4
+
use crate::synchronization::audio::{Note, StemAtInstant};
5
5
use crate::synchronization::sync::SyncData;
6
6
use itertools::Itertools;
7
7
use nanoid::nanoid;
···
50
50
duration: stems[name].duration_ms,
51
51
notes: stems[name].notes.get(&self.ms).cloned().unwrap_or(vec![]),
52
52
}
53
53
+
}
54
54
+
55
55
+
pub fn notes_of_stem(&self, name: &str) -> impl Iterator<Item = Note> + '_ {
56
56
+
let stem = &self.syncdata.stems[name];
57
57
+
stem.notes
58
58
+
.get(&self.ms)
59
59
+
.into_iter()
60
60
+
.flat_map(|notes| notes.iter().cloned())
53
61
}
54
62
55
63
pub fn dump_syncdata(&self, to: PathBuf) -> anyhow::Result<()> {