From bb6347f810fcd72ac5737e8becb2c32a7b9036f8 Mon Sep 17 00:00:00 2001 From: Ross Andrews Date: Fri, 28 Nov 2025 15:33:12 -0600 Subject: [PATCH] Random colors --- Cargo.lock | 14 ++++++++++++++ Cargo.toml | 4 +++- src/grid.rs | 32 +++++++++++++++++++++++++++----- src/gui.rs | 2 +- src/saveload.rs | 12 +++++++++--- src/tenori.rs | 12 ++++++++++-- 6 files changed, 64 insertions(+), 12 deletions(-) diff --git a/Cargo.lock b/Cargo.lock index 4c58bdb..ebc1317 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -683,6 +683,12 @@ dependencies = [ "unicode-width", ] +[[package]] +name = "color" +version = "0.3.2" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a18ef4657441fb193b65f34dc39b3781f0dfec23d3bd94d0eeb4e88cde421edb" + [[package]] name = "combine" version = "4.6.7" @@ -3185,10 +3191,12 @@ dependencies = [ name = "tenori-ish" version = "0.1.0" dependencies = [ + "color", "eframe", "rfd", "rodio", "serde", + "tinyrand", "tokio", "toml", ] @@ -3281,6 +3289,12 @@ dependencies = [ "strict-num", ] +[[package]] +name = "tinyrand" +version = "0.5.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "87ffaad2263779579369d45f65cad0647c860893d27e4543cdcc1e428d07da2c" + [[package]] name = "tinystr" version = "0.8.1" diff --git a/Cargo.toml b/Cargo.toml index 545861b..4e7a366 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -9,4 +9,6 @@ tokio = { version = "1.47.1", features = ["rt", "rt-multi-thread", "macros"] } rodio = { version = "0.21.1", features = ["noise"] } toml = "0.9.7" serde = { version = "1.0.227", features = ["derive"] } -rfd = "0.15.4" \ No newline at end of file +rfd = "0.15.4" +color = "0.3.2" +tinyrand = "0.5.0" \ No newline at end of file diff --git a/src/grid.rs b/src/grid.rs index b766b0d..fe814cc 100644 --- a/src/grid.rs +++ b/src/grid.rs @@ -1,6 +1,9 @@ -use std::ops::RangeInclusive; +use color::ColorSpace; +use std::ops::{DerefMut, RangeInclusive}; +use std::sync::{Arc, Mutex}; use eframe::egui; use eframe::egui::{Color32, Context, Id, PointerButton, Pos2, Rangef, Sense, Ui, Vec2}; +use tinyrand::{Rand, StdRand}; use crate::gui::Showable; use crate::scale::Scale; use crate::tenori::LOOP_LENGTH; @@ -15,11 +18,15 @@ pub struct Grid { pub name: String, pub open: bool, pub timbre: Timbre, - pub timbre_open: bool + pub timbre_open: bool, + pub color: Color32, + pub rand: Arc> } impl Grid { - pub fn new(id: Id) -> Self { + pub fn new(id: Id, rand: Arc>) -> Self { + let color = Self::random_color(rand.lock().unwrap()); + Self { volume: 1.0, open: true, @@ -28,10 +35,21 @@ impl Grid { name: "New Track".to_string(), timbre: Timbre::default(), timbre_open: false, + rand, + color, id } } + fn random_color>(mut rand: T) -> Color32 { + let angle = (rand.next_lim_u32(72) * 5) as f32; + let rgb = color::Hsl::convert::([angle, 100.0, 50.0]); + Color32::from_rgb( + (rgb[0] * 255.0) as u8, + (rgb[1] * 255.0) as u8, + (rgb[2] * 255.0) as u8) + } + fn draw_grid(&mut self, ui: &mut Ui, cursor: f32) { let dim = 20.0 * LOOP_LENGTH as f32; let (rect, response) = ui.allocate_exact_size(Vec2::new(dim, dim), Sense::click_and_drag()); @@ -42,7 +60,7 @@ impl Grid { (x * 20 + 10) as f32 + rect.left(), (y * 20 + 10) as f32 + rect.top()); if *lit { - ui.painter().circle_filled(center, 10.0, Color32::from_rgb(80, 140, 160)); + ui.painter().circle_filled(center, 10.0, self.color); } else { ui.painter().circle_stroke(center, 10.0, (1.0, Color32::from_gray(0x88))); } @@ -51,7 +69,7 @@ impl Grid { ui.painter().vline( cursor * dim + rect.left(), Rangef::new(rect.top(), rect.top() + dim), - (1.0, Color32::from_rgb(80, 140, 160)) + (1.0, self.color) ); if response.contains_pointer() { @@ -110,6 +128,10 @@ impl Showable for Grid { if ui.button("Timbre...").clicked() { self.timbre_open = !self.timbre_open; + }; + + if ui.button("Color").clicked() { + self.color = Self::random_color(self.rand.lock().unwrap()); } }); diff --git a/src/gui.rs b/src/gui.rs index 9da684b..29e91ff 100644 --- a/src/gui.rs +++ b/src/gui.rs @@ -35,7 +35,7 @@ impl Tenori { if ui.button("Add track").clicked() { let id = self.window_id(); - self.grids.push(Grid::new(id)); + self.grids.push(Grid::new(id, self.rand.clone())); } ui.with_layout(egui::Layout::right_to_left(egui::Align::Center), |ui| { diff --git a/src/saveload.rs b/src/saveload.rs index 5b7f0d9..b69d9e7 100644 --- a/src/saveload.rs +++ b/src/saveload.rs @@ -1,5 +1,7 @@ -use eframe::egui::Id; +use std::sync::{Arc, Mutex}; +use eframe::egui::{Color32, Id}; use serde::{Deserialize, Serialize}; +use tinyrand::StdRand; use crate::grid::Grid; use crate::scale::Scale; use crate::tenori::Tenori; @@ -22,7 +24,7 @@ impl From<&Tenori> for PersistedTenori { impl PersistedTenori { pub fn apply_to(self, tenori: &mut Tenori) { - tenori.grids = self.grids.into_iter().map(|g| g.into_grid(tenori.window_id())).collect(); + tenori.grids = self.grids.into_iter().map(|g| g.into_grid(tenori.window_id(), tenori.rand.clone())).collect(); tenori.tempo = self.tempo; tenori.playing = false; // Start paused tenori.timer = 0.0; // Start at the beginning of the loop @@ -36,6 +38,7 @@ struct PersistedGrid { notes: String, name: String, timbre: Timbre, + color: (u8, u8, u8) } impl From<&Grid> for PersistedGrid { @@ -46,13 +49,14 @@ impl From<&Grid> for PersistedGrid { scale: value.scale, name: value.name.clone(), timbre: value.timbre, + color: (value.color.r(), value.color.g(), value.color.b()), notes } } } impl PersistedGrid { - pub fn into_grid(self, id: Id) -> Grid { + pub fn into_grid(self, id: Id, rand: Arc>) -> Grid { let notes: Vec<_> = self.notes.chars().map(|c| c == '1').collect(); Grid { volume: self.volume, @@ -61,6 +65,8 @@ impl PersistedGrid { timbre: self.timbre, open: true, timbre_open: false, + color: Color32::from_rgb(self.color.0, self.color.1, self.color.2), + rand, notes, id } diff --git a/src/tenori.rs b/src/tenori.rs index 77ec3b8..f3a5398 100644 --- a/src/tenori.rs +++ b/src/tenori.rs @@ -1,5 +1,7 @@ -use std::time::Instant; +use std::sync::{Arc, Mutex}; +use std::time::{Instant, SystemTime, UNIX_EPOCH}; use rodio::OutputStream; +use tinyrand::{Seeded, StdRand}; use crate::grid::Grid; use crate::dialog::Dialog; use crate::noise::Note; @@ -33,7 +35,10 @@ pub struct Tenori { /// If present, we can save to this file without asking the /// user to select a file first. - pub default_filename: Option + pub default_filename: Option, + + /// A random number generator + pub rand: Arc>, } impl Default for Tenori { @@ -41,6 +46,8 @@ impl Default for Tenori { let output_stream = rodio::OutputStreamBuilder::open_default_stream() .expect("Open audio output stream"); + let rand = StdRand::seed(SystemTime::now().duration_since(UNIX_EPOCH).unwrap().as_secs()); + Self { tempo: 90, timer: 0.0, @@ -50,6 +57,7 @@ impl Default for Tenori { window_counter: 0, dialogs: vec![], default_filename: None, + rand: Arc::new(Mutex::new(rand)), output_stream } }