diff --git a/src/main.rs b/src/main.rs index d6d642f..71a7f4d 100644 --- a/src/main.rs +++ b/src/main.rs @@ -2,6 +2,7 @@ pub mod convert; pub mod error; pub mod pattern; pub mod printer; +mod renderer; pub mod term; mod vec; diff --git a/src/pattern/mod.rs b/src/pattern/mod.rs index f6527d7..a4ffa78 100644 --- a/src/pattern/mod.rs +++ b/src/pattern/mod.rs @@ -1,11 +1,11 @@ -mod circle; -mod line; -mod rhombus; -mod wheel; +pub mod circle; +pub mod line; +pub mod rhombus; +pub mod wheel; use crate::vec::Vector; -#[derive(Copy, Clone)] +#[derive(Copy, Clone, PartialEq, Debug)] pub struct Config { pub size: Vector, pub step: f32, @@ -22,8 +22,11 @@ pub trait Pattern { fn sample(&self, pos: Vector) -> f32; } +#[cfg_attr(test, mockall::automock(type Sampler = MockSampler;))] pub trait SamplerFactory { - fn create(&self, config: &Config) -> Box; + type Sampler: Sampler; + + fn create(&self, config: &Config) -> Self::Sampler; } #[cfg_attr(test, mockall::automock)] @@ -46,11 +49,10 @@ pub struct SamplerImpl { } impl SamplerFactory for SamplerFactoryImpl { - fn create(&self, config: &Config) -> Box { - Box::new(SamplerImpl::new( - self.char.create(config), - self.color.create(config), - )) + type Sampler = SamplerImpl; + + fn create(&self, config: &Config) -> Self::Sampler { + SamplerImpl::new(self.char.create(config), self.color.create(config)) } } diff --git a/src/printer.rs b/src/printer.rs index 45d3d11..6706cfa 100644 --- a/src/printer.rs +++ b/src/printer.rs @@ -4,6 +4,7 @@ use crossterm::cursor::*; use crossterm::style::*; use crossterm::terminal::*; +#[cfg_attr(test, mockall::automock)] pub trait Printer { fn show_cursor(&mut self) -> Result<(), Error>; fn hide_cursor(&mut self) -> Result<(), Error>; diff --git a/src/renderer.rs b/src/renderer.rs new file mode 100644 index 0000000..eaf297f --- /dev/null +++ b/src/renderer.rs @@ -0,0 +1,271 @@ +use crate::convert::char::CharSample; +use crate::convert::Converter; +use crate::error::Error; +use crate::pattern::{Config, Sampler, SamplerFactory}; +use crate::printer::Printer; +use crate::vec::Vector; + +pub trait Renderer { + fn begin(&mut self) -> Result<(), Error>; + fn render(&mut self, step: f32) -> Result<(), Error>; + fn end(&mut self) -> Result<(), Error>; +} + +#[derive(derive_more::Constructor)] +pub struct RendererImpl { + sampler: T1, + converter: T2, + printer: T3, +} + +impl Renderer for RendererImpl { + fn begin(&mut self) -> Result<(), Error> { + self.printer.hide_cursor() + } + + fn render(&mut self, step: f32) -> Result<(), Error> { + let (width, height) = self.printer.size()?; + let config = Config { + step, + size: Vector::from_terminal(width, height), + }; + let sampler = self.sampler.create(&config); + + for y in 0..height { + for x in 0..width { + let pos = Vector::from_terminal(x, y); + + match self.converter.char(sampler.char(pos)) { + CharSample::Draw(char) => { + let color = self.converter.color(sampler.color(pos)); + + self.printer.move_to(x, y)?; + self.printer.set_foreground(color)?; + self.printer.print(char)?; + } + CharSample::Clear => { + self.printer.move_to(x, y)?; + self.printer.print(' ')?; + } + CharSample::Keep => (), + } + } + } + self.printer.flush() + } + + fn end(&mut self) -> Result<(), Error> { + self.printer.move_to(0, 0)?; + self.printer.clear()?; + self.printer.show_cursor()?; + self.printer.flush() + } +} + +#[cfg(test)] +mod test { + use super::*; + use crate::convert::MockConverter; + use crate::pattern::MockSampler; + use crate::pattern::MockSamplerFactory; + use crate::printer::MockPrinter; + use crossterm::style::Color; + use mockall::predicate::eq; + use mockall::Sequence; + + #[test] + fn begin() { + let factory = MockSamplerFactory::new(); + let converter = MockConverter::new(); + let mut printer = MockPrinter::new(); + + printer.expect_hide_cursor().once().returning(|| Ok(())); + + let mut renderer = RendererImpl::new(factory, converter, printer); + + renderer.begin().unwrap(); + } + + #[test] + fn render_config_correct() { + let mut sampler = MockSamplerFactory::new(); + let mut converter = MockConverter::new(); + let mut printer = MockPrinter::new(); + + printer.expect_size().returning(|| Ok((2, 2))); + sampler.expect_create().returning(|_| { + let mut sampler = MockSampler::new(); + sampler + .expect_char() + .with(eq(Vector::new(0.0, 0.0))) + .return_const(1.0); + sampler + .expect_char() + .with(eq(Vector::new(1.0, 0.0))) + .return_const(2.0); + sampler + .expect_char() + .with(eq(Vector::new(0.0, 2.0))) + .return_const(3.0); + sampler + .expect_char() + .with(eq(Vector::new(1.0, 2.0))) + .return_const(4.0); + sampler + .expect_color() + .with(eq(Vector::new(0.0, 0.0))) + .return_const(5.0); + sampler + .expect_color() + .with(eq(Vector::new(1.0, 0.0))) + .return_const(6.0); + sampler + .expect_color() + .with(eq(Vector::new(0.0, 2.0))) + .return_const(7.0); + sampler + .expect_color() + .with(eq(Vector::new(1.0, 2.0))) + .return_const(8.0); + sampler + }); + converter + .expect_char() + .with(eq(1.0)) + .return_const(CharSample::Keep); + converter + .expect_char() + .with(eq(2.0)) + .return_const(CharSample::Clear); + converter + .expect_char() + .with(eq(3.0)) + .return_const(CharSample::Draw('A')); + converter + .expect_char() + .with(eq(4.0)) + .return_const(CharSample::Draw('X')); + converter + .expect_color() + .with(eq(7.0)) + .return_const(Color::Red); + converter + .expect_color() + .with(eq(8.0)) + .return_const(Color::Blue); + + let seq = &mut Sequence::new(); + + printer + .expect_move_to() + .once() + .with(eq(1), eq(0)) + .returning(|_, _| Ok(())) + .in_sequence(seq); + printer + .expect_print() + .once() + .with(eq(' ')) + .returning(|_| Ok(())) + .in_sequence(seq); + printer + .expect_move_to() + .once() + .with(eq(0), eq(1)) + .returning(|_, _| Ok(())) + .in_sequence(seq); + printer + .expect_set_foreground() + .once() + .with(eq(Color::Red)) + .returning(|_| Ok(())) + .in_sequence(seq); + printer + .expect_print() + .once() + .with(eq('A')) + .returning(|_| Ok(())) + .in_sequence(seq); + printer + .expect_move_to() + .once() + .with(eq(1), eq(1)) + .returning(|_, _| Ok(())) + .in_sequence(seq); + printer + .expect_set_foreground() + .once() + .with(eq(Color::Blue)) + .returning(|_| Ok(())) + .in_sequence(seq); + printer + .expect_print() + .once() + .with(eq('X')) + .returning(|_| Ok(())) + .in_sequence(seq); + printer + .expect_flush() + .once() + .returning(|| Ok(())) + .in_sequence(seq); + + let mut renderer = RendererImpl::new(sampler, converter, printer); + + renderer.render(0.0).unwrap(); + } + + #[test] + fn render() { + let mut sampler = MockSamplerFactory::new(); + let mut converter = MockConverter::new(); + let mut printer = MockPrinter::new(); + + sampler.expect_create().returning(|_| { + let mut sampler = MockSampler::new(); + sampler.expect_char().return_const(0.0); + sampler + }); + converter.expect_char().return_const(CharSample::Keep); + printer.expect_size().returning(|| Ok((3, 2))); + printer.expect_flush().returning(|| Ok(())); + + let mut renderer = RendererImpl::new(sampler, converter, printer); + + renderer.render(0.8).unwrap(); + } + + #[test] + fn end() { + let factory = MockSamplerFactory::new(); + let converter = MockConverter::new(); + let mut printer = MockPrinter::new(); + + let seq = &mut Sequence::new(); + printer + .expect_move_to() + .with(eq(0), eq(0)) + .once() + .returning(|_, _| Ok(())) + .in_sequence(seq); + printer + .expect_clear() + .once() + .returning(|| Ok(())) + .in_sequence(seq); + printer + .expect_show_cursor() + .once() + .returning(|| Ok(())) + .in_sequence(seq); + printer + .expect_flush() + .once() + .returning(|| Ok(())) + .in_sequence(seq); + + let mut renderer = RendererImpl::new(factory, converter, printer); + + renderer.end().unwrap(); + } +} diff --git a/src/vec.rs b/src/vec.rs index f6cc06e..2a789e4 100644 --- a/src/vec.rs +++ b/src/vec.rs @@ -11,6 +11,10 @@ impl Vector { Self { x, y } } + pub fn from_terminal(x: u16, y: u16) -> Self { + Vector::new(x as f32, y as f32 * 2.0) + } + pub fn len(&self) -> f32 { (self.x * self.x + self.y * self.y).sqrt() }