diff --git a/Cargo.lock b/Cargo.lock index 33126b9..b247b43 100644 --- a/Cargo.lock +++ b/Cargo.lock @@ -38,6 +38,12 @@ version = "1.0.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "baf1de4339761588bc0619e3cbc0120ee582ebb74b53b4efbf79117bd2da40fd" +[[package]] +name = "convert_case" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "6245d59a3e82a7fc217c5828a6692dbc6dfb63a0c8c90495621f7b9d79704a0e" + [[package]] name = "crossterm" version = "0.24.0" @@ -63,6 +69,19 @@ dependencies = [ "winapi", ] +[[package]] +name = "derive_more" +version = "0.99.17" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "4fb810d30a7c1953f91334de7244731fc3f3c10d7fe163338a35b9f640960321" +dependencies = [ + "convert_case", + "proc-macro2", + "quote", + "rustc_version", + "syn", +] + [[package]] name = "difflib" version = "0.4.0" @@ -293,12 +312,27 @@ version = "0.6.27" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "a3f87b73ce11b1619a3c6332f45341e0047173771e8b8b73f87bfeefb7b56244" +[[package]] +name = "rustc_version" +version = "0.4.0" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "bfa0f585226d2e68097d4f95d113b15b83a82e819ab25717ec0590d9584ef366" +dependencies = [ + "semver", +] + [[package]] name = "scopeguard" version = "1.1.0" source = "registry+https://github.com/rust-lang/crates.io-index" checksum = "d29ab0c6d3fc0ee92fe66e2d99f700eab17a8d57d1c1d3b748380fb20baa78cd" +[[package]] +name = "semver" +version = "1.0.12" +source = "registry+https://github.com/rust-lang/crates.io-index" +checksum = "a2333e6df6d6598f2b1974829f853c2b4c5f4a6e503c10af918081aa6f8564e1" + [[package]] name = "signal-hook" version = "0.3.14" @@ -435,5 +469,6 @@ version = "2.0.0" dependencies = [ "approx", "crossterm", + "derive_more", "mockall", ] diff --git a/Cargo.toml b/Cargo.toml index c9e42c5..ab6fd1b 100644 --- a/Cargo.toml +++ b/Cargo.toml @@ -8,6 +8,7 @@ authors = ["Rico Riedel"] [dependencies] crossterm = "0.24" +derive_more = "0.99" [dev-dependencies] mockall = "0.11" diff --git a/src/convert/char.rs b/src/convert/char.rs index 3f2fbfe..1825df6 100644 --- a/src/convert/char.rs +++ b/src/convert/char.rs @@ -1,27 +1,40 @@ +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum CharSample { + Keep, + Draw(char), + Clear, +} + #[cfg_attr(test, mockall::automock)] pub trait CharConverter { - fn convert(&self, level: f32) -> char; + fn convert(&self, level: f32) -> CharSample; } pub struct CharConverterImpl { chars: String, - count: usize, + count: f32, } impl CharConverterImpl { pub fn new(chars: String) -> Self { - let count = chars.chars().count(); + let count = chars.chars().count() as f32; Self { chars, count } } } impl CharConverter for CharConverterImpl { - fn convert(&self, level: f32) -> char { - let len = self.count as f32; - let index = (level * len).rem_euclid(len) as usize; + fn convert(&self, level: f32) -> CharSample { + if level < 0.0 { + CharSample::Keep + } else if level < 1.0 { + let index = (level * self.count) as usize; + let char = self.chars.chars().nth(index).unwrap(); - self.chars.chars().nth(index).unwrap() + CharSample::Draw(char) + } else { + CharSample::Clear + } } } @@ -30,37 +43,26 @@ mod test { use super::*; #[test] - fn convert_negative_index() { + fn convert_keep() { let converter = CharConverterImpl::new("abc".to_string()); - assert_eq!('c', converter.convert(-0.2)); + assert_eq!(CharSample::Keep, converter.convert(-0.1)); } #[test] - fn convert_index_zero() { - let converter = CharConverterImpl::new("abc".to_string()); + fn convert_draw() { + let converter = CharConverterImpl::new("xyz".to_string()); - assert_eq!('a', converter.convert(0.0)); + assert_eq!(CharSample::Draw('x'), converter.convert(0.0)); + assert_eq!(CharSample::Draw('y'), converter.convert(0.5)); + assert_eq!(CharSample::Draw('z'), converter.convert(0.9)); } #[test] - fn convert() { - let converter = CharConverterImpl::new("abc".to_string()); + fn convert_clear() { + let converter = CharConverterImpl::new("123".to_string()); - assert_eq!('b', converter.convert(0.5)); - } - - #[test] - fn convert_index_one() { - let converter = CharConverterImpl::new("abc".to_string()); - - assert_eq!('a', converter.convert(1.0)); - } - - #[test] - fn convert_index_above_one() { - let converter = CharConverterImpl::new("abc".to_string()); - - assert_eq!('b', converter.convert(1.5)); + assert_eq!(CharSample::Clear, converter.convert(1.0)); + assert_eq!(CharSample::Clear, converter.convert(1.5)); } } diff --git a/src/convert/level.rs b/src/convert/level.rs deleted file mode 100644 index b968468..0000000 --- a/src/convert/level.rs +++ /dev/null @@ -1,55 +0,0 @@ -#[derive(Copy, Clone, PartialEq, Debug)] -pub enum Level { - Keep, - Draw(f32), - Clear, -} - -#[cfg_attr(test, mockall::automock)] -pub trait LevelConverter { - fn convert(&self, level: f32) -> Level; -} - -#[derive(Default)] -pub struct LevelConverterImpl; - -impl LevelConverter for LevelConverterImpl { - fn convert(&self, level: f32) -> Level { - if level < 0.0 { - Level::Keep - } else if level < 1.0 { - Level::Draw(level) - } else { - Level::Clear - } - } -} - -#[cfg(test)] -mod test { - use super::*; - - #[test] - fn convert_keep() { - let converter = LevelConverterImpl::default(); - - assert_eq!(Level::Keep, converter.convert(-0.1)); - } - - #[test] - fn convert_draw() { - let converter = LevelConverterImpl::default(); - - assert_eq!(Level::Draw(0.0), converter.convert(0.0)); - assert_eq!(Level::Draw(0.5), converter.convert(0.5)); - assert_eq!(Level::Draw(0.9), converter.convert(0.9)); - } - - #[test] - fn convert_clear() { - let converter = LevelConverterImpl::default(); - - assert_eq!(Level::Clear, converter.convert(1.0)); - assert_eq!(Level::Clear, converter.convert(1.5)); - } -} diff --git a/src/convert/mod.rs b/src/convert/mod.rs index 87d0616..cac3826 100644 --- a/src/convert/mod.rs +++ b/src/convert/mod.rs @@ -1,44 +1,30 @@ -use crate::convert::char::CharConverter; +use crate::convert::char::{CharConverter, CharSample}; use crate::convert::color::ColorConverter; -use crate::convert::level::{Level, LevelConverter}; use crossterm::style::Color; -mod char; -mod color; -mod level; +pub mod char; +pub mod color; +#[cfg_attr(test, mockall::automock)] pub trait Converter { - fn char(&self, level: f32) -> char; + fn char(&self, level: f32) -> CharSample; fn color(&self, level: f32) -> Color; - fn level(&self, level: f32) -> Level; } -pub struct ConverterImpl { +#[derive(derive_more::Constructor)] +pub struct ConverterImpl { char: T1, color: T2, - level: T3, } -impl ConverterImpl { - pub fn new(char: T1, color: T2, level: T3) -> Self { - Self { char, color, level } - } -} - -impl Converter - for ConverterImpl -{ - fn char(&self, level: f32) -> char { +impl Converter for ConverterImpl { + fn char(&self, level: f32) -> CharSample { self.char.convert(level) } fn color(&self, level: f32) -> Color { self.color.convert(level) } - - fn level(&self, level: f32) -> Level { - self.level.convert(level) - } } #[cfg(test)] @@ -46,51 +32,34 @@ mod test { use super::*; use crate::convert::char::MockCharConverter; use crate::convert::color::MockColorConverter; - use crate::convert::level::MockLevelConverter; - use mockall::predicate::eq; + use mockall::predicate::*; #[test] fn char() { let mut char = MockCharConverter::new(); let color = MockColorConverter::new(); - let level = MockLevelConverter::new(); - char.expect_convert().with(eq(4.0)).return_const('M'); + char.expect_convert() + .with(eq(4.0)) + .return_const(CharSample::Draw('M')); - let converter = ConverterImpl::new(char, color, level); + let converter = ConverterImpl::new(char, color); - assert_eq!('M', converter.char(4.0)); + assert_eq!(CharSample::Draw('M'), converter.char(4.0)); } #[test] fn color() { let char = MockCharConverter::new(); let mut color = MockColorConverter::new(); - let level = MockLevelConverter::new(); color .expect_convert() .with(eq(2.0)) .return_const(Color::Yellow); - let converter = ConverterImpl::new(char, color, level); + let converter = ConverterImpl::new(char, color); assert_eq!(Color::Yellow, converter.color(2.0)); } - - #[test] - fn level() { - let char = MockCharConverter::new(); - let color = MockColorConverter::new(); - let mut level = MockLevelConverter::new(); - - level - .expect_convert() - .with(eq(3.0)) - .return_const(Level::Draw(2.0)); - - let converter = ConverterImpl::new(char, color, level); - - assert_eq!(Level::Draw(2.0), converter.level(3.0)); - } } diff --git a/src/main.rs b/src/main.rs index d1ae43b..1f22017 100644 --- a/src/main.rs +++ b/src/main.rs @@ -4,6 +4,7 @@ pub mod printer; pub mod convert; pub mod term; mod vec; +mod sampler; fn main() -> Result<(), error::Error> { Ok(()) diff --git a/src/pattern/mod.rs b/src/pattern/mod.rs index b7886aa..6cb1682 100644 --- a/src/pattern/mod.rs +++ b/src/pattern/mod.rs @@ -17,6 +17,7 @@ pub trait PatternFactory { fn create(&self, config: &Config) -> Box; } +#[cfg_attr(test, mockall::automock)] pub trait Pattern { fn sample(&self, pos: Vector) -> f32; } diff --git a/src/sampler.rs b/src/sampler.rs new file mode 100644 index 0000000..32de27f --- /dev/null +++ b/src/sampler.rs @@ -0,0 +1,142 @@ +use crate::convert::char::CharSample; +use crate::convert::Converter; +use crate::pattern::{Config, Pattern, PatternFactory}; +use crate::vec::Vector; +use crossterm::style::Color; +use std::rc::Rc; + +#[derive(Copy, Clone, PartialEq, Debug)] +pub enum Sample { + Keep, + Draw { char: char, color: Color }, + Clear, +} + +pub trait SamplerFactory { + fn create(&self, config: &Config) -> Box; +} + +pub trait Sampler { + fn sample(&self, pos: Vector) -> Sample; +} + +#[derive(derive_more::Constructor)] +pub struct SamplerFactoryImpl { + char: Box, + color: Box, + converter: Rc, +} + +#[derive(derive_more::Constructor)] +pub struct SamplerImpl { + char: Box, + color: Box, + converter: Rc, +} + +impl SamplerFactory for SamplerFactoryImpl { + fn create(&self, config: &Config) -> Box { + Box::new(SamplerImpl::new( + self.char.create(config), + self.color.create(config), + self.converter.clone(), + )) + } +} + +impl Sampler for SamplerImpl { + fn sample(&self, pos: Vector) -> Sample { + match self.converter.char(self.char.sample(pos)) { + CharSample::Keep => Sample::Keep, + CharSample::Draw(char) => Sample::Draw { + char, + color: self.converter.color(self.color.sample(pos)), + }, + CharSample::Clear => Sample::Clear, + } + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::convert::MockConverter; + use crate::pattern::MockPattern; + use mockall::predicate::eq; + + #[test] + fn sample_keep() { + let color = MockPattern::new(); + let mut char = MockPattern::new(); + let mut converter = MockConverter::new(); + + char.expect_sample().return_const(3.0); + converter.expect_char().return_const(CharSample::Keep); + + let sampler = SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter)); + + assert_eq!(Sample::Keep, sampler.sample(Vector::default())); + } + + #[test] + fn sample_draw() { + let mut char = MockPattern::new(); + let mut color = MockPattern::new(); + let mut converter = MockConverter::new(); + + char.expect_sample().return_const(3.0); + color.expect_sample().return_const(2.0); + converter.expect_char().return_const(CharSample::Draw('M')); + converter.expect_color().return_const(Color::Red); + + let sampler = SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter)); + + assert_eq!( + Sample::Draw { + char: 'M', + color: Color::Red + }, + sampler.sample(Vector::default()) + ); + } + + #[test] + fn sample_clear() { + let color = MockPattern::new(); + let mut char = MockPattern::new(); + let mut converter = MockConverter::new(); + + char.expect_sample().return_const(3.0); + converter.expect_char().return_const(CharSample::Clear); + + let sampler = SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter)); + + assert_eq!(Sample::Clear, sampler.sample(Vector::default())); + } + + #[test] + fn sample_args_correct() { + let mut char = MockPattern::new(); + let mut color = MockPattern::new(); + let mut converter = MockConverter::new(); + + char.expect_sample() + .with(eq(Vector::new(4.0, 2.0))) + .return_const(6.0); + color + .expect_sample() + .with(eq(Vector::new(4.0, 2.0))) + .return_const(7.0); + converter + .expect_char() + .with(eq(6.0)) + .return_const(CharSample::Draw('A')); + converter + .expect_color() + .with(eq(7.0)) + .return_const(Color::Reset); + + SamplerImpl::new(Box::new(char), Box::new(color), Rc::new(converter)) + .sample(Vector::new(4.0, 2.0)); + } +} diff --git a/src/vec.rs b/src/vec.rs index f81f78f..f6cc06e 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -1,6 +1,6 @@ use std::ops::Sub; -#[derive(Copy, Clone, PartialEq, Debug)] +#[derive(Copy, Clone, PartialEq, Debug, Default)] pub struct Vector { pub x: f32, pub y: f32,