diff --git a/crates/tek_core/src/color.rs b/crates/tek_core/src/color.rs index 11680b03..5455babb 100644 --- a/crates/tek_core/src/color.rs +++ b/crates/tek_core/src/color.rs @@ -2,33 +2,37 @@ use crate::*; use rand::{thread_rng, distributions::uniform::UniformSampler}; pub use palette::{*, convert::*, okhsl::*}; -#[derive(Debug, Copy, Clone)] +/// A color in OKHSL and RGB representations. +#[derive(Debug, Default, Copy, Clone)] pub struct ItemColor { pub okhsl: Okhsl, pub rgb: Color, } -#[derive(Debug, Copy, Clone)] +/// A color in OKHSL and RGB with lighter and darker variants. +#[derive(Debug, Default, Copy, Clone)] pub struct ItemColorTriplet { pub base: ItemColor, pub light: ItemColor, pub dark: ItemColor, } +/// Adds TUI RGB representation to an OKHSL value. impl From> for ItemColor { - fn from (okhsl: Okhsl) -> Self { - Self { okhsl, rgb: okhsl_to_rgb(okhsl) } - } + fn from (okhsl: Okhsl) -> Self { Self { okhsl, rgb: okhsl_to_rgb(okhsl) } } +} +/// Adds OKHSL representation to a TUI RGB value. +impl From for ItemColor { + fn from (rgb: Color) -> Self { Self { rgb, okhsl: rgb_to_okhsl(rgb) } } } impl ItemColor { - pub fn random () -> Self { - random_okhsl().into() - } + pub fn random () -> Self { random_okhsl().into() } pub fn random_dark () -> Self { - random_okhsl_dark().into() + let mut rng = thread_rng(); + let lo = Okhsl::new(-180.0, 0.01, 0.05); + let hi = Okhsl::new( 180.0, 0.5, 0.2); + UniformOkhsl::new(lo, hi).sample(&mut rng).into() } pub fn random_near (other: Self, distance: f32) -> Self { - if distance > 1.0 { - panic!("random_color_near requires distance between 0.0 and 1.0"); - } + if distance > 1.0 { panic!("ItemColor::random_near takes distance between 0.0 and 1.0"); } other.okhsl.mix(random_okhsl(), distance).into() } } @@ -46,18 +50,9 @@ impl From for ItemColorTriplet { pub fn random_okhsl () -> Okhsl { let mut rng = thread_rng(); - UniformOkhsl::new( - Okhsl::new(-180.0, 0.01, 0.2), - Okhsl::new( 180.0, 0.9, 0.5), - ).sample(&mut rng) -} - -pub fn random_okhsl_dark () -> Okhsl { - let mut rng = thread_rng(); - UniformOkhsl::new( - Okhsl::new(-180.0, 0.01, 0.05), - Okhsl::new( 180.0, 0.5, 0.2), - ).sample(&mut rng) + let lo = Okhsl::new(-180.0, 0.01, 0.2); + let hi = Okhsl::new( 180.0, 0.9, 0.5); + UniformOkhsl::new(lo, hi).sample(&mut rng) } pub fn okhsl_to_rgb (color: Okhsl) -> Color { @@ -65,26 +60,14 @@ pub fn okhsl_to_rgb (color: Okhsl) -> Color { Color::Rgb((red * 255.0) as u8, (green * 255.0) as u8, (blue * 255.0) as u8,) } -pub fn random_color () -> Color { - okhsl_to_rgb(random_okhsl()) -} - -pub fn random_color_dark () -> Color { - okhsl_to_rgb(random_okhsl_dark()) -} - -pub fn random_color_near (color: Color, distance: f32) -> Color { - let (r, g, b) = if let Color::Rgb(r, g, b) = color { - (r, g, b) +pub fn rgb_to_okhsl (color: Color) -> Okhsl { + if let Color::Rgb(r, g, b) = color { + Okhsl::from_color(Srgb::new( + r as f32 / 255.0, + g as f32 / 255.0, + b as f32 / 255.0, + )) } else { - panic!("random_color_near works only with Color::Rgb") - }; - if distance > 1.0 { - panic!("random_color_near requires distance between 0.0 and 1.0"); + unreachable!("only Color::Rgb is supported") } - okhsl_to_rgb(Okhsl::from_color(Srgb::new( - r as f32 / 255.0, - g as f32 / 255.0, - b as f32 / 255.0, - )).mix(random_okhsl(), distance)) } diff --git a/crates/tek_sequencer/src/arranger.rs b/crates/tek_sequencer/src/arranger.rs index 2636b697..51f947cf 100644 --- a/crates/tek_sequencer/src/arranger.rs +++ b/crates/tek_sequencer/src/arranger.rs @@ -67,7 +67,7 @@ pub struct Arrangement { /// Whether the arranger is currently focused pub focused: bool, /// Background color of arrangement - pub color: Color, + pub color: ItemColor, /// Width and height of arrangement area at last render pub size: Measure, } @@ -78,7 +78,7 @@ pub struct ArrangementTrack { /// Preferred width of track column pub width: usize, /// Identifying color of track - pub color: Color, + pub color: ItemColor, /// MIDI player/recorder pub player: PhrasePlayer, } @@ -89,7 +89,7 @@ pub struct Scene { /// Clips in scene, one per track pub clips: Vec>>>, /// Identifying color of scene - pub color: Color, + pub color: ItemColor, } #[derive(PartialEq, Clone, Copy)] /// Represents the current user selection in the arranger @@ -232,7 +232,7 @@ impl Arrangement { scenes: vec![], tracks: vec![], focused: false, - color: Color::Rgb(28, 35, 25), + color: Color::Rgb(28, 35, 25).into(), size: Measure::new(), } } @@ -374,11 +374,11 @@ impl Arrangement { } pub fn randomize_color (&mut self) { match self.selected { - ArrangementFocus::Mix => { self.color = random_color_dark() }, - ArrangementFocus::Track(t) => { self.tracks[t].color = random_color() }, - ArrangementFocus::Scene(s) => { self.scenes[s].color = random_color() }, + ArrangementFocus::Mix => { self.color = ItemColor::random_dark() }, + ArrangementFocus::Track(t) => { self.tracks[t].color = ItemColor::random() }, + ArrangementFocus::Scene(s) => { self.scenes[s].color = ItemColor::random() }, ArrangementFocus::Clip(t, s) => if let Some(phrase) = &self.scenes[s].clips[t] { - phrase.write().unwrap().color = random_color(); + phrase.write().unwrap().color = ItemColor::random(); } } } @@ -396,7 +396,7 @@ impl Arrangement { pub fn track_next (&mut self) { self.selected.track_next(self.tracks.len() - 1) } pub fn track_prev (&mut self) { self.selected.track_prev() } pub fn track_add ( - &mut self, name: Option<&str>, color: Option + &mut self, name: Option<&str>, color: Option ) -> Usually<&mut ArrangementTrack> { self.tracks.push(name.map_or_else( || ArrangementTrack::new(&self.clock, &self.track_default_name(), color), @@ -442,7 +442,7 @@ impl Arrangement { pub fn scene_prev (&mut self) { self.selected.scene_prev() } - pub fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut Scene> { + pub fn scene_add (&mut self, name: Option<&str>, color: Option) -> Usually<&mut Scene> { let clips = vec![None;self.tracks.len()]; let name = name.map(|x|x.to_string()).unwrap_or_else(||self.scene_default_name()); self.scenes.push(Scene::new(name, clips, color)); @@ -523,11 +523,11 @@ impl Arrangement { } } impl ArrangementTrack { - pub fn new (clock: &Arc, name: &str, color: Option) -> Self { + pub fn new (clock: &Arc, name: &str, color: Option) -> Self { Self { name: Arc::new(RwLock::new(name.into())), width: name.len() + 2, - color: color.unwrap_or_else(random_color), + color: color.unwrap_or_else(ItemColor::random), player: PhrasePlayer::new(clock), } } @@ -640,12 +640,12 @@ impl Scene { pub fn new ( name: impl AsRef, clips: impl AsRef<[Option>>]>, - color: Option, + color: Option, ) -> Self { Self { name: Arc::new(RwLock::new(name.as_ref().into())), clips: clips.as_ref().iter().map(|x|x.clone()).collect(), - color: color.unwrap_or_else(random_color), + color: color.unwrap_or_else(ItemColor::random), } } /// Returns the pulse length of the longest phrase in the scene diff --git a/crates/tek_sequencer/src/arranger_cli.rs b/crates/tek_sequencer/src/arranger_cli.rs index 43adcf0a..d775fa57 100644 --- a/crates/tek_sequencer/src/arranger_cli.rs +++ b/crates/tek_sequencer/src/arranger_cli.rs @@ -33,7 +33,7 @@ impl ArrangerCli { for i in 0..self.tracks { let _track = arrangement.track_add( None, - Some(okhsl_to_rgb(track_color_1.mix(track_color_2, i as f32 / self.tracks as f32))) + Some(track_color_1.mix(track_color_2, i as f32 / self.tracks as f32).into()) )?; } let scene_color_1 = random_okhsl(); @@ -41,7 +41,7 @@ impl ArrangerCli { for i in 0..self.scenes { let _scene = arrangement.scene_add( None, - Some(okhsl_to_rgb(scene_color_1.mix(scene_color_2, i as f32 / self.scenes as f32))) + Some(scene_color_1.mix(scene_color_2, i as f32 / self.scenes as f32).into()) )?; } let arranger = Arc::new(RwLock::new(Arranger::new( diff --git a/crates/tek_sequencer/src/arranger_tui.rs b/crates/tek_sequencer/src/arranger_tui.rs index 1551a705..11ccef69 100644 --- a/crates/tek_sequencer/src/arranger_tui.rs +++ b/crates/tek_sequencer/src/arranger_tui.rs @@ -182,7 +182,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { .unwrap_or("(none)".into())); col!(name, input) .min_xy(w as u16, track_title_h) - .bg(track.color) + .bg(track.color.rgb) .push_x(scene_title_w) }); // track controls @@ -219,7 +219,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { .unwrap_or("(none)".into())); col!(until_next, elapsed, output) .min_xy(w as u16, tracks_footer) - .bg(track.color) + .bg(track.color.rgb) .push_x(scene_title_w) }); // scene titles @@ -236,7 +236,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let name = format!("{}", name); let max_w = name.len().min((w as usize).saturating_sub(2)); add(&name.as_str()[0..max_w].push_x(1).fixed_x(w))?; - color = (phrase as &Arc>).read().unwrap().color; + color = (phrase as &Arc>).read().unwrap().color.rgb; }, _ => {} }; @@ -250,7 +250,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { let playing = scene.is_playing(tracks); Stack::right(move |add| { // scene title: - add(&scene_name(scene, playing, height).bg(scene.color))?; + add(&scene_name(scene, playing, height).bg(scene.color.rgb))?; // clip per track: Ok(for (track, w) in cols.iter().map(|col|col.0).enumerate() { add(&scene_clip(scene, track, w as u16, height))?; @@ -308,7 +308,7 @@ impl<'a> Content for VerticalArranger<'a, Tui> { else { area.clip_w(scene_title_w).clip_h(track_title_h) }, &CORNERS)? }) })) - }).bg(bg);//.grow_y(1);//.border(border); + }).bg(bg.rgb);//.grow_y(1);//.border(border); let color = if self.0.focused {Color::Rgb(150, 160, 90)} else {Color::Rgb(120, 130, 100)}; let size = format!("{}x{}", self.0.size.w(), self.0.size.h()); let lower_right = TuiStyle::fg(size, color).pull_x(1).align_se().fill_xy(); diff --git a/crates/tek_sequencer/src/sequencer.rs b/crates/tek_sequencer/src/sequencer.rs index 37972f92..08387609 100644 --- a/crates/tek_sequencer/src/sequencer.rs +++ b/crates/tek_sequencer/src/sequencer.rs @@ -79,7 +79,7 @@ pub enum PhrasePoolMode { /// All notes are displayed with minimum length pub percussive: bool, /// Identifying color of phrase - pub color: Color, + pub color: ItemColor, } /// Contains state for viewing and editing a phrase pub struct PhraseEditor { @@ -205,7 +205,7 @@ impl PhrasePool { } return None } - fn new_phrase (name: Option<&str>, color: Option) -> Arc> { + fn new_phrase (name: Option<&str>, color: Option) -> Arc> { Arc::new(RwLock::new(Phrase::new( String::from(name.unwrap_or("(new)")), true, 4 * PPQ, None, color ))) @@ -216,23 +216,23 @@ impl PhrasePool { self.phrase = self.phrase.min(self.phrases.len().saturating_sub(1)); } } - pub fn append_new (&mut self, name: Option<&str>, color: Option) { + pub fn append_new (&mut self, name: Option<&str>, color: Option) { self.phrases.push(Self::new_phrase(name, color)); self.phrase = self.phrases.len() - 1; } - pub fn insert_new (&mut self, name: Option<&str>, color: Option) { + pub fn insert_new (&mut self, name: Option<&str>, color: Option) { self.phrases.insert(self.phrase + 1, Self::new_phrase(name, color)); self.phrase += 1; } pub fn insert_dup (&mut self) { let mut phrase = self.phrases[self.phrase].read().unwrap().duplicate(); - phrase.color = random_color_near(phrase.color, 0.25); + phrase.color = ItemColor::random_near(phrase.color, 0.25); self.phrases.insert(self.phrase + 1, Arc::new(RwLock::new(phrase))); self.phrase += 1; } pub fn randomize_color (&mut self) { let mut phrase = self.phrases[self.phrase].write().unwrap(); - phrase.color = random_color(); + phrase.color = ItemColor::random(); } pub fn begin_rename (&mut self) { self.mode = Some(PhrasePoolMode::Rename( @@ -347,7 +347,7 @@ impl Phrase { loop_on: bool, length: usize, notes: Option, - color: Option, + color: Option, ) -> Self { Self { uuid: uuid::Uuid::new_v4(), @@ -359,7 +359,7 @@ impl Phrase { loop_start: 0, loop_length: length, percussive: true, - color: color.unwrap_or_else(random_color) + color: color.unwrap_or_else(ItemColor::random) } } pub fn duplicate (&self) -> Self { @@ -384,7 +384,7 @@ impl Phrase { } } impl Default for Phrase { - fn default () -> Self { Self::new("(empty)", false, 0, None, Some(Color::Rgb(0, 0, 0))) } + fn default () -> Self { Self::new("(empty)", false, 0, None, Some(Color::Rgb(0, 0, 0).into())) } } impl PartialEq for Phrase { fn eq (&self, other: &Self) -> bool { self.uuid == other.uuid } } impl Eq for Phrase {} diff --git a/crates/tek_sequencer/src/sequencer_tui.rs b/crates/tek_sequencer/src/sequencer_tui.rs index 494e207f..f606f73d 100644 --- a/crates/tek_sequencer/src/sequencer_tui.rs +++ b/crates/tek_sequencer/src/sequencer_tui.rs @@ -33,7 +33,7 @@ impl Content for PhrasePool { }; let row2 = TuiStyle::bold(row2, true); let bg = if i == self.phrase { color } else { color }; - add(&col!(row1, row2).fill_x().bg(bg))?; + add(&col!(row1, row2).fill_x().bg(bg.rgb))?; Ok(if *focused && i == self.phrase { add(&CORNERS)?; }) }) ); @@ -57,7 +57,7 @@ impl Content for PhraseEditor { start: time_start, point: time_point, clamp: time_clamp, scale: time_scale } = *self.time_axis.read().unwrap(); let color = Color::Rgb(0,255,0); - let color = phrase.as_ref().map(|p|p.read().unwrap().color).unwrap_or(color); + let color = phrase.as_ref().map(|p|p.read().unwrap().color.rgb).unwrap_or(color); let keys = CustomWidget::new(|to:[u16;2]|Ok(Some(to.clip_w(5))), move|to: &mut TuiOutput|{ Ok(if to.area().h() >= 2 { to.buffer_update(to.area().set_w(5), &|cell, x, y|{