diff --git a/.gitignore b/.gitignore index 893adbc..a9591cd 100644 --- a/.gitignore +++ b/.gitignore @@ -5,3 +5,5 @@ log .rider .codecov cobertura.xml +cov + diff --git a/rider-editor/src/app/app_state.rs b/rider-editor/src/app/app_state.rs index 351d429..fcdc06e 100644 --- a/rider-editor/src/app/app_state.rs +++ b/rider-editor/src/app/app_state.rs @@ -1,5 +1,5 @@ use crate::app::{UpdateResult, WindowCanvas as WC}; -use crate::renderer::Renderer; +use crate::renderer::CanvasRenderer; use crate::ui::*; use rider_config::*; use sdl2::rect::Point; @@ -27,7 +27,7 @@ impl AppState { } #[cfg_attr(tarpaulin, skip)] - pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) { + pub fn open_file(&mut self, file_path: String, renderer: &mut CanvasRenderer) { if let Ok(buffer) = read_to_string(&file_path) { let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone()); file.prepare_ui(renderer); @@ -41,7 +41,7 @@ impl AppState { } #[cfg_attr(tarpaulin, skip)] - pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) { + pub fn open_directory(&mut self, dir_path: String, renderer: &mut CanvasRenderer) { match self.open_file_modal.as_mut() { Some(modal) => modal.open_directory(dir_path, renderer), _ => (), @@ -74,20 +74,18 @@ impl AppState { } #[cfg_attr(tarpaulin, skip)] -impl Render for AppState { - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _context: &RenderContext) { - self.file_editor - .render(canvas, renderer, &RenderContext::Nothing); - self.menu_bar - .render(canvas, renderer, &RenderContext::Nothing); +impl AppState { + pub fn render(&self, canvas: &mut WC, renderer: &mut CanvasRenderer, _context: &RenderContext) { + self.file_editor.render(canvas, renderer); + self.menu_bar.render(canvas, &RenderContext::Nothing); match self.open_file_modal.as_ref() { Some(modal) => modal.render(canvas, renderer, &RenderContext::Nothing), _ => (), }; } - fn prepare_ui(&mut self, renderer: &mut Renderer) { - self.menu_bar.prepare_ui(renderer); + pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) { + self.menu_bar.prepare_ui(); self.file_editor.prepare_ui(renderer); } } diff --git a/rider-editor/src/app/application.rs b/rider-editor/src/app/application.rs index e2e34f1..593ce6f 100644 --- a/rider-editor/src/app/application.rs +++ b/rider-editor/src/app/application.rs @@ -1,5 +1,5 @@ pub use crate::app::app_state::AppState; -pub use crate::renderer::Renderer; +pub use crate::renderer::CanvasRenderer; use crate::ui::caret::{CaretPosition, MoveDirection}; use crate::ui::*; pub use rider_config::{Config, ConfigAccess, ConfigHolder}; @@ -115,7 +115,8 @@ impl Application { let texture_creator = self.canvas.texture_creator(); let sleep_time = Duration::new(0, 1_000_000_000u32 / 60); let mut app_state = AppState::new(Arc::clone(&self.config)); - let mut renderer = Renderer::new(Arc::clone(&self.config), &font_context, &texture_creator); + let mut renderer = + CanvasRenderer::new(Arc::clone(&self.config), &font_context, &texture_creator); app_state.prepare_ui(&mut renderer); 'running: loop { diff --git a/rider-editor/src/app/caret_manager.rs b/rider-editor/src/app/caret_manager.rs index 090cbc2..3012bf7 100644 --- a/rider-editor/src/app/caret_manager.rs +++ b/rider-editor/src/app/caret_manager.rs @@ -41,27 +41,185 @@ pub fn move_caret_left(file_editor: &mut FileEditor) { #[cfg(test)] mod test_move_right { use super::*; + use crate::renderer::managers::FontDetails; + use crate::renderer::managers::TextDetails; + use crate::renderer::renderer::Renderer; use crate::tests::support; + use rider_config::config::Config; + use rider_config::ConfigHolder; + use sdl2::rect::Rect; + use sdl2::render::Texture; + use sdl2::ttf::Font; + use std::rc::Rc; + use std::sync::Arc; + use std::sync::RwLock; + + struct RendererMock { + pub config: Arc>, + } + + impl RendererMock { + pub fn new(config: Arc>) -> Self { + Self { config } + } + } + + impl Renderer for RendererMock { + #[cfg_attr(tarpaulin, skip)] + fn load_font(&mut self, _details: FontDetails) -> Rc { + unimplemented!() + } + + fn load_text_tex( + &mut self, + _details: &mut TextDetails, + _font_details: FontDetails, + ) -> Result, String> { + Err("skip render character".to_owned()) + } + } + + impl ConfigHolder for RendererMock { + fn config(&self) -> &Arc> { + &self.config + } + } + + impl CharacterSizeManager for RendererMock { + fn load_character_size(&mut self, c: char) -> Rect { + match c { + '\n' => Rect::new(0, 0, 12, 13), + _ => Rect::new(0, 0, 14, 15), + } + } + } #[test] - fn must_do_nothing() { + fn assert_move_with_no_file() { let config = support::build_config(); let mut editor = FileEditor::new(config); assert_eq!(move_caret_right(&mut editor), ()); } + + #[test] + fn assert_move_caret_with_empty_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut editor = FileEditor::new(config.clone()); + let mut file = EditorFile::new("test.txt".to_owned(), "".to_owned(), config); + file.prepare_ui(&mut renderer); + editor.open_file(file); + editor.prepare_ui(&mut renderer); + editor.move_caret(MoveDirection::Left); + + assert_eq!(move_caret_right(&mut editor), ()); + } + + #[test] + fn assert_move_caret_with_filled_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut editor = FileEditor::new(config.clone()); + let mut file = EditorFile::new("test.txt".to_owned(), "hello".to_owned(), config); + file.prepare_ui(&mut renderer); + editor.open_file(file); + editor.prepare_ui(&mut renderer); + editor.move_caret(MoveDirection::Left); + + assert_eq!(move_caret_right(&mut editor), ()); + } } #[cfg(test)] mod test_move_left { use super::*; + use crate::renderer::managers::FontDetails; + use crate::renderer::managers::TextDetails; + use crate::renderer::renderer::Renderer; use crate::tests::support; + use rider_config::config::Config; + use rider_config::ConfigHolder; + use sdl2::rect::Rect; + use sdl2::render::Texture; + use sdl2::ttf::Font; + use std::rc::Rc; + use std::sync::Arc; + use std::sync::RwLock; + + struct RendererMock { + pub config: Arc>, + } + + impl RendererMock { + pub fn new(config: Arc>) -> Self { + Self { config } + } + } + + impl Renderer for RendererMock { + #[cfg_attr(tarpaulin, skip)] + fn load_font(&mut self, _details: FontDetails) -> Rc { + unimplemented!() + } + + fn load_text_tex( + &mut self, + _details: &mut TextDetails, + _font_details: FontDetails, + ) -> Result, String> { + Err("skip render character".to_owned()) + } + } + + impl ConfigHolder for RendererMock { + fn config(&self) -> &Arc> { + &self.config + } + } + + impl CharacterSizeManager for RendererMock { + fn load_character_size(&mut self, c: char) -> Rect { + match c { + '\n' => Rect::new(0, 0, 12, 13), + _ => Rect::new(0, 0, 14, 15), + } + } + } #[test] - fn must_do_nothing() { + fn assert_move_caret_without_file() { let config = support::build_config(); let mut editor = FileEditor::new(config); assert_eq!(move_caret_left(&mut editor), ()); } + + #[test] + fn assert_move_caret_with_empty_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut editor = FileEditor::new(config.clone()); + let mut file = EditorFile::new("test.txt".to_owned(), "".to_owned(), config); + file.prepare_ui(&mut renderer); + editor.open_file(file); + editor.prepare_ui(&mut renderer); + editor.move_caret(MoveDirection::Right); + + assert_eq!(move_caret_left(&mut editor), ()); + } + + #[test] + fn assert_move_caret_with_filled_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut editor = FileEditor::new(config.clone()); + let mut file = EditorFile::new("test.txt".to_owned(), "hello".to_owned(), config); + file.prepare_ui(&mut renderer); + editor.open_file(file); + editor.prepare_ui(&mut renderer); + editor.move_caret(MoveDirection::Right); + + assert_eq!(move_caret_left(&mut editor), ()); + } } diff --git a/rider-editor/src/app/file_content_manager.rs b/rider-editor/src/app/file_content_manager.rs index acab0ff..d73c062 100644 --- a/rider-editor/src/app/file_content_manager.rs +++ b/rider-editor/src/app/file_content_manager.rs @@ -1,5 +1,5 @@ use crate::app::*; -use crate::renderer::Renderer; +use crate::renderer::renderer::Renderer; use crate::ui::*; use sdl2::rect::{Point, Rect}; use std::sync::*; @@ -11,7 +11,10 @@ pub fn current_file_path(file_editor: &mut FileEditor) -> String { } #[cfg_attr(tarpaulin, skip)] -pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { +pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut R) +where + R: ConfigHolder + CharacterSizeManager + Renderer, +{ let mut buffer: String = if let Some(file) = file_editor.file() { file } else { @@ -53,7 +56,10 @@ pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { } #[cfg_attr(tarpaulin, skip)] -pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) { +pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut R) +where + R: ConfigHolder + CharacterSizeManager + Renderer, +{ let file: &EditorFile = if let Some(file) = file_editor.file() { file } else { @@ -70,30 +76,27 @@ pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) { file_editor.replace_current_file(new_file); } -#[cfg_attr(tarpaulin, skip)] -pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) { - let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer()); - if buffer.is_empty() { - return; - } - - let current = match file_editor - .file() - .and_then(|file| file.get_character_at(file_editor.caret().text_position())) - { - Some(c) => c, - _ => return, +pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut R) +where + R: ConfigHolder + CharacterSizeManager + Renderer, +{ + let mut buffer: String = match file_editor.file() { + Some(f) => f.buffer(), + None => return, }; - let mut pos = if current.is_new_line() { - current.dest().top_left() - + Point::new(0, renderer.load_character_size('\n').height() as i32) - } else { - current.dest().top_left() + + let maybe_character = file_editor + .file() + .and_then(|file| file.get_character_at(file_editor.caret().text_position())); + + let mut pos = match maybe_character { + Some(ref current) => current.dest().top_left(), + None => Point::new(0, 0), }; let mut position: CaretPosition = file_editor.caret().position().clone(); - for character in text.chars() { - buffer.insert(position.text_position(), character); - let rect = renderer.load_character_size(character); + for c in text.chars() { + buffer.insert(position.text_position(), c); + let rect = renderer.load_character_size(c); pos = pos + Point::new(rect.width() as i32, 0); position = position.moved(1, 0, 0); file_editor.caret_mut().move_caret(position, pos.clone()); @@ -108,28 +111,27 @@ pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Re file_editor.replace_current_file(new_file); } -#[cfg_attr(tarpaulin, skip)] -pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { - let mut buffer: String = if let Some(file) = file_editor.file() { - file - } else { - return; - } - .buffer(); - let current = match file_editor - .file() - .and_then(|file| file.get_character_at(file_editor.caret().text_position())) - { - Some(c) => c, - _ => return, +pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut R) +where + R: ConfigHolder + CharacterSizeManager + Renderer, +{ + let mut buffer: String = match file_editor.file() { + Some(file) => file.buffer(), + None => return, }; - let mut pos = Point::new(current.dest().x(), current.dest().y()); + let maybe_character = file_editor + .file() + .and_then(|file| file.get_character_at(file_editor.caret().text_position())); + let mut pos = match maybe_character { + None => Point::new(0, 0), + Some(current) => current.dest().top_left(), + }; let mut position: CaretPosition = file_editor.caret().position().clone(); buffer.insert(position.text_position(), '\n'); let rect = renderer.load_character_size('\n'); pos = Point::new(0, pos.y() + rect.height() as i32); - position = position.moved(0, 1, 0); + position = position.moved(1, 1, -(position.line_position() as i32)); file_editor.caret_mut().move_caret(position, pos.clone()); let mut new_file = EditorFile::new( @@ -144,7 +146,54 @@ pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { #[cfg(test)] mod tests { use super::*; + use crate::renderer::managers::FontDetails; + use crate::renderer::managers::TextDetails; use crate::tests::support; + use sdl2::render::Texture; + use sdl2::ttf::Font; + use std::rc::Rc; + use std::sync::Arc; + use std::sync::RwLock; + + struct RendererMock { + pub config: Arc>, + } + + impl RendererMock { + pub fn new(config: Arc>) -> Self { + Self { config } + } + } + + impl Renderer for RendererMock { + #[cfg_attr(tarpaulin, skip)] + fn load_font(&mut self, _details: FontDetails) -> Rc { + unimplemented!() + } + + fn load_text_tex( + &mut self, + _details: &mut TextDetails, + _font_details: FontDetails, + ) -> Result, String> { + Err("skip render character".to_owned()) + } + } + + impl ConfigHolder for RendererMock { + fn config(&self) -> &Arc> { + &self.config + } + } + + impl CharacterSizeManager for RendererMock { + fn load_character_size(&mut self, c: char) -> Rect { + match c { + '\n' => Rect::new(0, 0, 12, 13), + _ => Rect::new(0, 0, 14, 15), + } + } + } #[test] fn must_return_empty_string_when_no_file() { @@ -167,4 +216,156 @@ mod tests { let result = current_file_path(&mut editor); assert_eq!(result, "/foo/bar".to_owned()); } + + //################################################## + // insert text + //################################################## + + #[test] + fn assert_insert_text_without_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + widget.prepare_ui(&mut renderer); + widget.insert_text("foo".to_owned(), &mut renderer); + let expected = CaretPosition::new(0, 0, 0); + assert_eq!(widget.caret().position(), &expected); + } + + #[test] + fn assert_insert_text_to_empty_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + let file = EditorFile::new("".to_owned(), "".to_owned(), config.clone()); + widget.open_file(file); + widget.prepare_ui(&mut renderer); + widget.insert_text("foo".to_owned(), &mut renderer); + let expected = CaretPosition::new(3, 0, 0); + assert_eq!(widget.caret().position(), &expected); + } + + #[test] + fn assert_insert_text_to_file_without_new_line() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + let mut file = EditorFile::new("".to_owned(), "bar".to_owned(), config.clone()); + file.prepare_ui(&mut renderer); + widget.open_file(file); + widget.prepare_ui(&mut renderer); + widget.insert_text("foo".to_owned(), &mut renderer); + let expected = CaretPosition::new(3, 0, 0); + assert_eq!(widget.caret().position(), &expected); + assert_eq!(widget.file().is_some(), true); + let buffer = widget.file().unwrap().buffer(); + let expected = "foobar"; + assert_eq!(buffer, expected); + } + + #[test] + fn assert_insert_text_to_file_with_new_line() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + let mut file = EditorFile::new("".to_owned(), "bar\n".to_owned(), config.clone()); + file.prepare_ui(&mut renderer); + widget.open_file(file); + widget.prepare_ui(&mut renderer); + widget.insert_text("foo".to_owned(), &mut renderer); + let expected = CaretPosition::new(3, 0, 0); + assert_eq!(widget.caret().position(), &expected); + assert_eq!(widget.file().is_some(), true); + let buffer = widget.file().unwrap().buffer(); + let expected = "foobar\n"; + assert_eq!(buffer, expected); + } + + #[test] + fn assert_insert_text_to_file_with_new_line_with_caret_at_new_line() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + let mut file = EditorFile::new("".to_owned(), "old content\n".to_owned(), config.clone()); + file.prepare_ui(&mut renderer); + widget.open_file(file); + widget.prepare_ui(&mut renderer); + widget.set_caret_to_end_of_line(0); + widget.insert_text("new content".to_owned(), &mut renderer); + let expected = CaretPosition::new(22, 0, 0); + assert_eq!(widget.caret().position(), &expected); + assert_eq!(widget.file().is_some(), true); + let buffer = widget.file().unwrap().buffer(); + let expected = "old contentnew content\n"; + assert_eq!(buffer, expected); + } + + //################################################## + // insert new line + //################################################## + + #[test] + fn assert_insert_new_line_without_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + widget.prepare_ui(&mut renderer); + widget.insert_new_line(&mut renderer); + let expected = CaretPosition::new(0, 0, 0); + assert_eq!(widget.caret().position(), &expected); + let expected = Rect::new(0, 0, 6, 15); + assert_eq!(widget.caret().dest(), expected); + } + + #[test] + fn assert_insert_new_line_to_empty_file() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + let file = EditorFile::new("".to_owned(), "".to_owned(), config.clone()); + widget.open_file(file); + widget.prepare_ui(&mut renderer); + widget.insert_new_line(&mut renderer); + let expected = CaretPosition::new(1, 1, 0); + assert_eq!(widget.caret().position(), &expected); + let expected = Rect::new(0, 13, 6, 15); + assert_eq!(widget.caret().dest(), expected); + } + + #[test] + fn assert_insert_new_line_to_file_at_beginning() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + let file = EditorFile::new("".to_owned(), "foo".to_owned(), config.clone()); + widget.open_file(file); + widget.prepare_ui(&mut renderer); + widget.insert_new_line(&mut renderer); + let expected = CaretPosition::new(1, 1, 0); + assert_eq!(widget.caret().position(), &expected); + let expected = Rect::new(0, 13, 6, 15); + assert_eq!(widget.caret().dest(), expected); + assert_eq!(widget.file().is_some(), true); + assert_eq!(widget.file().unwrap().buffer(), "\nfoo".to_owned()); + } + + #[test] + fn assert_insert_new_line_to_file_in_middle() { + let config = support::build_config(); + let mut renderer = RendererMock::new(config.clone()); + let mut widget = FileEditor::new(config.clone()); + let mut file = EditorFile::new("hello.txt".to_owned(), "abcd".to_owned(), config.clone()); + file.prepare_ui(&mut renderer); + widget.open_file(file); + widget.prepare_ui(&mut renderer); + widget.move_caret(MoveDirection::Right); + widget.move_caret(MoveDirection::Right); + widget.insert_new_line(&mut renderer); + let expected = CaretPosition::new(3, 1, 0); + assert_eq!(widget.caret().position(), &expected); + let expected = Rect::new(0, 13, 6, 15); + assert_eq!(widget.caret().dest(), expected); + assert_eq!(widget.file().is_some(), true); + assert_eq!(widget.file().unwrap().buffer(), "ab\ncd".to_owned()); + } } diff --git a/rider-editor/src/renderer/managers.rs b/rider-editor/src/renderer/managers.rs index ad6afdd..4921725 100644 --- a/rider-editor/src/renderer/managers.rs +++ b/rider-editor/src/renderer/managers.rs @@ -181,7 +181,7 @@ pub trait TextTextureManager<'l> { fn load_text( &mut self, details: &mut TextDetails, - font: &Rc, + font: Rc, ) -> Result>, String>; } @@ -191,7 +191,7 @@ impl<'l> TextTextureManager<'l> for TextureManager<'l> { fn load_text( &mut self, details: &mut TextDetails, - font: &Rc, + font: Rc, ) -> Result>, String> { let key = details.get_cache_key(); self.cache.get(key.as_str()).cloned().map_or_else( diff --git a/rider-editor/src/renderer/renderer.rs b/rider-editor/src/renderer/renderer.rs index cb1aa7b..8eab64a 100644 --- a/rider-editor/src/renderer/renderer.rs +++ b/rider-editor/src/renderer/renderer.rs @@ -3,12 +3,25 @@ use crate::ui::get_text_character_rect; use crate::ui::text_character::CharacterSizeManager; use rider_config::{ConfigAccess, ConfigHolder}; use sdl2::rect::Rect; +use sdl2::render::Texture; use sdl2::render::TextureCreator; +use sdl2::ttf::Font; use sdl2::ttf::Sdl2TtfContext; use std::collections::HashMap; +use std::rc::Rc; + +pub trait Renderer { + fn load_font(&mut self, details: FontDetails) -> Rc; + + fn load_text_tex( + &mut self, + details: &mut TextDetails, + font_details: FontDetails, + ) -> Result, String>; +} #[cfg_attr(tarpaulin, skip)] -pub struct Renderer<'l> { +pub struct CanvasRenderer<'l> { config: ConfigAccess, font_manager: FontManager<'l>, texture_manager: TextureManager<'l>, @@ -16,7 +29,7 @@ pub struct Renderer<'l> { } #[cfg_attr(tarpaulin, skip)] -impl<'l> Renderer<'l> { +impl<'l> CanvasRenderer<'l> { pub fn new( config: ConfigAccess, font_context: &'l Sdl2TtfContext, @@ -37,7 +50,8 @@ impl<'l> Renderer<'l> { } } -impl<'l> CharacterSizeManager for Renderer<'l> { +#[cfg_attr(tarpaulin, skip)] +impl<'l> CharacterSizeManager for CanvasRenderer<'l> { fn load_character_size(&mut self, c: char) -> Rect { let (font_path, font_size) = { let config = self.config().read().unwrap(); @@ -65,7 +79,7 @@ impl<'l> CharacterSizeManager for Renderer<'l> { } #[cfg_attr(tarpaulin, skip)] -impl<'l> ManagersHolder<'l> for Renderer<'l> { +impl<'l> ManagersHolder<'l> for CanvasRenderer<'l> { fn font_manager(&mut self) -> &mut FontManager<'l> { &mut self.font_manager } @@ -76,8 +90,31 @@ impl<'l> ManagersHolder<'l> for Renderer<'l> { } #[cfg_attr(tarpaulin, skip)] -impl<'l> ConfigHolder for Renderer<'l> { +impl<'l> ConfigHolder for CanvasRenderer<'l> { fn config(&self) -> &ConfigAccess { &self.config } } + +#[cfg_attr(tarpaulin, skip)] +impl<'l> Renderer for CanvasRenderer<'l> { + fn load_font(&mut self, details: FontDetails) -> Rc { + self.font_manager() + .load(&details) + .unwrap_or_else(|_| panic!("Font not found {:?}", details)) + } + + fn load_text_tex( + &mut self, + details: &mut TextDetails, + font_details: FontDetails, + ) -> Result, String> { + use crate::renderer::managers::TextTextureManager; + let font = self + .font_manager() + .load(&font_details) + .unwrap_or_else(|_| panic!("Font not found {:?}", details)); + let tex_manager = self.texture_manager(); + tex_manager.load_text(details, font) + } +} diff --git a/rider-editor/src/ui/caret/caret.rs b/rider-editor/src/ui/caret/caret.rs index 7efb947..e673bba 100644 --- a/rider-editor/src/ui/caret/caret.rs +++ b/rider-editor/src/ui/caret/caret.rs @@ -1,5 +1,4 @@ -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::renderer::*; +use crate::app::UpdateResult as UR; use crate::ui::*; use rider_config::ConfigAccess; use sdl2::rect::{Point, Rect}; @@ -7,7 +6,6 @@ use std::ops::Deref; #[derive(Clone, Debug, PartialEq)] pub struct Caret { - pending: bool, blink_delay: u8, state: CaretState, position: CaretPosition, @@ -25,7 +23,6 @@ impl Caret { blink_delay: 0, dest: Rect::new(0, 0, 6, 0), colors: CaretColor::new(bright, blur), - pending: true, position: CaretPosition::new(0, 0, 0), } } @@ -67,8 +64,11 @@ impl Deref for Caret { } #[cfg_attr(tarpaulin, skip)] -impl Render for Caret { - fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { +impl Caret { + pub fn render(&self, canvas: &mut T, context: &RenderContext) + where + T: CanvasAccess, + { use std::borrow::*; let dest = match context.borrow() { @@ -82,26 +82,22 @@ impl Render for Caret { CaretState::Blur => self.colors.blur(), } .clone(); - canvas.set_draw_color(color); canvas - .draw_line(start, end) + .render_line(start, end, color) .unwrap_or_else(|_| panic!("Failed to draw a caret")); } - fn prepare_ui(&mut self, renderer: &mut Renderer) { - if !self.pending { - return; - } - - if let Some(rect) = get_text_character_rect('W', renderer) { - self.dest.set_height(rect.height()); - } - self.pending = false; + pub fn prepare_ui(&mut self, renderer: &mut T) + where + T: CharacterSizeManager, + { + let rect = renderer.load_character_size('I'); + self.dest.set_height(rect.height()); } } -impl Update for Caret { - fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR { +impl Caret { + pub fn update(&mut self) -> UR { self.blink_delay += 1; if self.blink_delay >= 30 { self.blink_delay = 0; @@ -312,3 +308,91 @@ mod test_click_handler { assert_eq!(result, expected); } } + +#[cfg(test)] +mod test_render { + use crate::tests::support::build_config; + use crate::ui::*; + use sdl2::pixels::Color; + use sdl2::rect::{Point, Rect}; + use sdl2::render::Texture; + use std::rc::Rc; + + struct CanvasMock { + pub start: Point, + pub end: Point, + pub color: sdl2::pixels::Color, + } + + impl CanvasAccess for CanvasMock { + fn render_rect(&mut self, _rect: Rect, _color: Color) -> Result<(), String> { + unimplemented!() + } + + fn render_border(&mut self, _rect: Rect, _color: Color) -> Result<(), String> { + unimplemented!() + } + + fn render_image( + &mut self, + _tex: Rc, + _src: Rect, + _dest: Rect, + ) -> Result<(), String> { + unimplemented!() + } + + fn render_line( + &mut self, + start: Point, + end: Point, + color: sdl2::pixels::Color, + ) -> Result<(), String> { + self.start = start; + self.end = end; + self.color = color; + Ok(()) + } + + fn set_clipping(&mut self, _rect: Rect) { + unimplemented!() + } + } + + impl CharacterSizeManager for CanvasMock { + fn load_character_size(&mut self, _c: char) -> Rect { + Rect::new(0, 2, 12, 23) + } + } + + #[test] + fn assert_render_line() { + let config = build_config(); + let context = RenderContext::RelativePosition(Point::new(10, 14)); + let mut canvas = CanvasMock { + start: Point::new(0, 0), + end: Point::new(0, 0), + color: sdl2::pixels::Color::RGB(0, 0, 0), + }; + let mut widget = Caret::new(config); + widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(23, 23)); + widget.render(&mut canvas, &context); + assert_eq!(canvas.start, Point::new(33, 37)); + assert_eq!(canvas.end, Point::new(33, 38)); + assert_eq!(canvas.color, sdl2::pixels::Color::RGBA(121, 121, 121, 0)); + } + + #[test] + fn assert_prepare_ui() { + let config = build_config(); + let mut canvas = CanvasMock { + start: Point::new(0, 0), + end: Point::new(0, 0), + color: sdl2::pixels::Color::RGB(0, 0, 0), + }; + let mut widget = Caret::new(config); + widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(11, 12)); + widget.prepare_ui(&mut canvas); + assert_eq!(widget.dest(), Rect::new(11, 12, 6, 23)); + } +} diff --git a/rider-editor/src/ui/file/editor_file.rs b/rider-editor/src/ui/file/editor_file.rs index 8741d8a..f11534d 100644 --- a/rider-editor/src/ui/file/editor_file.rs +++ b/rider-editor/src/ui/file/editor_file.rs @@ -1,12 +1,13 @@ use sdl2::rect::{Point, Rect}; use std::sync::*; -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::renderer::Renderer; +use crate::app::UpdateResult as UR; +use crate::renderer::renderer::Renderer; use crate::ui::file::editor_file_section::EditorFileSection; use crate::ui::text_character::TextCharacter; use crate::ui::*; use rider_config::Config; +use rider_config::ConfigHolder; #[derive(Clone, Debug)] pub struct EditorFile { @@ -113,21 +114,27 @@ impl TextCollection for EditorFile { } } -#[cfg_attr(tarpaulin, skip)] -impl Render for EditorFile { - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { +impl EditorFile { + pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + R: Renderer + ConfigHolder, + C: CanvasAccess, + { for section in self.sections.iter() { section.render(canvas, renderer, context); } } - fn prepare_ui(&mut self, renderer: &mut Renderer) { + pub fn prepare_ui(&mut self, renderer: &mut R) + where + R: ConfigHolder + CharacterSizeManager + Renderer, + { for section in self.sections.iter_mut() { section.prepare_ui(renderer); } - if let Some(r) = get_text_character_rect('W', renderer) { - self.line_height = r.height(); - } + + let r = renderer.load_character_size('W'); + self.line_height = r.height(); self.refresh_characters_position(); } } @@ -202,11 +209,72 @@ impl RenderBox for EditorFile { } #[cfg(test)] -mod test_render_box { +mod tests { use crate::tests::support; use crate::ui::*; use sdl2::rect::{Point, Rect}; + //################################################## + // path + //################################################## + + #[test] + fn assert_path_txt() { + let config = support::build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let widget = EditorFile::new(path, buffer, config); + assert_eq!(widget.path(), "/example.txt".to_owned()); + } + + #[test] + fn assert_path_rs() { + let config = support::build_config(); + let buffer = "".to_owned(); + let path = "/example.rs".to_owned(); + let widget = EditorFile::new(path, buffer, config); + assert_eq!(widget.path(), "/example.rs".to_owned()); + } + + //################################################## + // buffer + //################################################## + + #[test] + fn assert_empty_buffer() { + let config = support::build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let widget = EditorFile::new(path, buffer, config); + assert_eq!(widget.buffer(), "".to_owned()); + } + + #[test] + fn assert_some_buffer() { + let config = support::build_config(); + let buffer = "fn main(){}".to_owned(); + let path = "some.rs".to_owned(); + let widget = EditorFile::new(path, buffer, config); + assert_eq!(widget.buffer(), "fn main(){}".to_owned()); + } + + //################################################## + // line height + //################################################## + + #[test] + fn assert_initial_line_height() { + let config = support::build_config(); + let buffer = "".to_owned(); + let path = "/example.txt".to_owned(); + let widget = EditorFile::new(path, buffer, config); + assert_eq!(widget.line_height(), 0); + } + + //################################################## + // render box + //################################################## + #[test] fn assert_dest() { let config = support::build_config(); diff --git a/rider-editor/src/ui/file/editor_file_section.rs b/rider-editor/src/ui/file/editor_file_section.rs index c972616..300d1e7 100644 --- a/rider-editor/src/ui/file/editor_file_section.rs +++ b/rider-editor/src/ui/file/editor_file_section.rs @@ -1,12 +1,13 @@ use sdl2::rect::{Point, Rect}; use std::sync::*; -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::renderer::Renderer; +use crate::app::UpdateResult as UR; +use crate::renderer::renderer::Renderer; use crate::ui::file::editor_file_token::EditorFileToken; use crate::ui::text_character::TextCharacter; use crate::ui::*; use rider_config::Config; +use rider_config::ConfigHolder; use rider_lexers; use rider_lexers::Language; @@ -26,7 +27,7 @@ impl EditorFileSection { .get(ext.as_str()) .unwrap_or(&Language::PlainText) .clone(); - let lexer_tokens = rider_lexers::parse(buffer.clone(), language); + let lexer_tokens = rider_lexers::parse(buffer.clone(), language.clone()); let mut tokens: Vec = vec![]; let mut iterator = lexer_tokens.iter().peekable(); @@ -43,7 +44,6 @@ impl EditorFileSection { ); tokens.push(token); } - let language = Language::PlainText; Self { tokens, language, @@ -51,11 +51,34 @@ impl EditorFileSection { } } + pub fn language(&self) -> Language { + self.language + } + pub fn update_positions(&mut self, current: &mut Rect) { for c in self.tokens.iter_mut() { c.update_position(current); } } + + pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + R: Renderer + ConfigHolder, + C: CanvasAccess, + { + for token in self.tokens.iter() { + token.render(canvas, renderer, context); + } + } + + pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T) + where + T: ConfigHolder + CharacterSizeManager + Renderer, + { + for token in self.tokens.iter_mut() { + token.prepare_ui(renderer); + } + } } impl TextWidget for EditorFileSection { @@ -125,21 +148,6 @@ impl TextCollection for EditorFileSection { } } -#[cfg_attr(tarpaulin, skip)] -impl Render for EditorFileSection { - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { - for token in self.tokens.iter() { - token.render(canvas, renderer, context); - } - } - - fn prepare_ui(&mut self, renderer: &mut Renderer) { - for token in self.tokens.iter_mut() { - token.prepare_ui(renderer); - } - } -} - impl Update for EditorFileSection { fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { let mut result = UR::NoOp; @@ -179,3 +187,35 @@ impl ClickHandler for EditorFileSection { false } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::support::build_config; + + impl EditorFileSection { + pub fn tokens(&self) -> Vec { + self.tokens.clone() + } + + pub fn tokens_count(&self) -> usize { + self.tokens.len() + } + } + + #[test] + fn assert_new() { + let config = build_config(); + let widget = EditorFileSection::new("".to_owned(), "rs".to_owned(), config); + assert_eq!(widget.language(), Language::Rust); + assert_eq!(widget.tokens_count(), 0); + } + + #[test] + fn assert_new_with_content() { + let config = build_config(); + let widget = EditorFileSection::new("fn main() {}".to_owned(), "rs".to_owned(), config); + assert_eq!(widget.language(), Language::Rust); + assert_eq!(widget.tokens_count(), 8); + } +} diff --git a/rider-editor/src/ui/file/editor_file_token.rs b/rider-editor/src/ui/file/editor_file_token.rs index b47bdf9..a6f5ff7 100644 --- a/rider-editor/src/ui/file/editor_file_token.rs +++ b/rider-editor/src/ui/file/editor_file_token.rs @@ -1,7 +1,8 @@ -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; +use crate::app::UpdateResult as UR; use crate::renderer::*; use crate::ui::*; use rider_config::Config; +use rider_config::ConfigHolder; use rider_lexers::TokenType; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; @@ -123,13 +124,16 @@ impl TextCollection for EditorFileToken { } } -#[cfg_attr(tarpaulin, skip)] -impl Render for EditorFileToken { +impl EditorFileToken { /** * Must first create targets so even if new line appear renderer will know * where move render starting point */ - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { + pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + R: Renderer + ConfigHolder, + C: CanvasAccess, + { if self.token_type.is_new_line() { return; } @@ -138,7 +142,10 @@ impl Render for EditorFileToken { } } - fn prepare_ui(&mut self, renderer: &mut Renderer) { + pub fn prepare_ui(&mut self, renderer: &mut R) + where + R: ConfigHolder + CharacterSizeManager + Renderer, + { if !self.characters.is_empty() { return; } @@ -188,3 +195,505 @@ impl ClickHandler for EditorFileToken { false } } + +#[cfg(test)] +mod tests { + use super::*; + use crate::tests::support::build_config; + use rider_lexers::Token; + use sdl2::pixels::PixelFormatEnum; + use sdl2::render::Texture; + use sdl2::render::TextureCreator; + use sdl2::surface::Surface; + use sdl2::surface::SurfaceContext; + use sdl2::ttf::Font; + use std::fmt::Debug; + use std::fmt::Error; + use std::fmt::Formatter; + use std::rc::Rc; + use std::sync::{Arc, RwLock}; + + //################################################## + // models + //################################################## + + #[derive(Debug, PartialEq)] + struct RendererRect { + pub rect: Rect, + pub color: Color, + } + + #[cfg_attr(tarpaulin, skip)] + struct CanvasMock { + pub rects: Vec, + pub borders: Vec, + pub lines: Vec, + pub clippings: Vec, + } + + #[cfg_attr(tarpaulin, skip)] + impl Debug for CanvasMock { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!(f, "CanvasMock {{}}") + } + } + + #[cfg_attr(tarpaulin, skip)] + impl PartialEq for CanvasMock { + fn eq(&self, other: &CanvasMock) -> bool { + self.rects == other.rects + && self.borders == other.borders + && self.clippings == other.clippings + && self.lines == other.lines + } + } + + #[cfg_attr(tarpaulin, skip)] + impl CanvasMock { + pub fn new() -> Self { + Self { + rects: vec![], + borders: vec![], + lines: vec![], + clippings: vec![], + } + } + } + + #[cfg_attr(tarpaulin, skip)] + impl CanvasAccess for CanvasMock { + fn render_rect(&mut self, rect: Rect, color: Color) -> Result<(), String> { + self.rects.push(RendererRect { rect, color }); + Ok(()) + } + + fn render_border(&mut self, rect: Rect, color: Color) -> Result<(), String> { + self.borders.push(RendererRect { rect, color }); + Ok(()) + } + + fn render_image( + &mut self, + _tex: Rc, + _src: Rect, + _dest: Rect, + ) -> Result<(), String> { + unimplemented!() + } + + fn render_line(&mut self, start: Point, end: Point, color: Color) -> Result<(), String> { + self.lines.push(RendererRect { + rect: Rect::new(start.x(), start.y(), end.x() as u32, end.y() as u32), + color, + }); + Ok(()) + } + + fn set_clipping(&mut self, rect: Rect) { + self.clippings.push(rect); + } + } + + #[cfg_attr(tarpaulin, skip)] + struct RendererMock<'l> { + pub config: Arc>, + pub canvas: sdl2::render::Canvas>, + pub map: Vec>>, + pub creator: TextureCreator>, + } + + #[cfg_attr(tarpaulin, skip)] + impl<'l> RendererMock<'l> { + pub fn new(config: Arc>, surface: Surface<'l>) -> Self { + let canvas = sdl2::render::Canvas::from_surface(surface).unwrap(); + Self { + config, + creator: canvas.texture_creator(), + canvas, + map: vec![], + } + } + } + + #[cfg_attr(tarpaulin, skip)] + impl<'l> Renderer for RendererMock<'l> { + fn load_font(&mut self, _details: FontDetails) -> Rc { + unimplemented!("load_font") + } + + fn load_text_tex( + &mut self, + _details: &mut TextDetails, + _font_details: FontDetails, + ) -> Result, String> { + // self.map.get(0).cloned().map_or_else(|| { + // let surface = font + // .render(details.text.as_str()) + // .blended(details.color) + // .unwrap(); + // let texture = self.loader.create_texture_from_surface(&surface).unwrap(); + // let resource = Rc::new(texture); + // self.map.push(resource.clone()); + // Ok(resource) + // }, Ok) + Err("".to_owned()) + } + } + + impl<'l> CharacterSizeManager for RendererMock<'l> { + fn load_character_size(&mut self, _c: char) -> Rect { + Rect::new(0, 0, 13, 14) + } + } + + impl<'l> ConfigHolder for RendererMock<'l> { + fn config(&self) -> &Arc> { + &self.config + } + } + + //################################################## + // token_to_color + //################################################## + + #[test] + fn assert_whitespace_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::Whitespace { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + #[test] + fn assert_keyword_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::Keyword { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + #[test] + fn assert_string_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::String { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + #[test] + fn assert_identifier_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::Identifier { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + #[test] + fn assert_literal_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::Literal { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + #[test] + fn assert_comment_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::Comment { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + #[test] + fn assert_operator_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::Operator { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + #[test] + fn assert_separator_to_color() { + let config = build_config(); + let surface = Surface::new(1024, 800, PixelFormatEnum::RGBA8888).unwrap(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token_type = TokenType::Separator { + token: Token::new("".to_owned(), 0, 0, 0, 0), + }; + let mut token = EditorFileToken::new(&token_type, false, config.clone()); + token.prepare_ui(&mut renderer); + } + + //################################################## + // render + //################################################## + + #[test] + fn assert_is_last_in_line() { + let config = build_config(); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let widget = EditorFileToken::new(&token, true, config); + assert_eq!(widget.is_last_in_line(), true); + } + + #[test] + fn assert_is_not_last_in_line() { + let config = build_config(); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let widget = EditorFileToken::new(&token, false, config); + assert_eq!(widget.is_last_in_line(), false); + } + + #[test] + fn assert_is_new_line() { + let config = build_config(); + let token = TokenType::Whitespace { + token: Token::new("\n".to_string(), 0, 0, 0, 0), + }; + let widget = EditorFileToken::new(&token, true, config); + assert_eq!(widget.is_new_line(), true); + } + + #[test] + fn assert_is_not_new_line() { + let config = build_config(); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let widget = EditorFileToken::new(&token, false, config); + assert_eq!(widget.is_new_line(), false); + } + + #[test] + fn assert_empty_characters_update_position() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + let mut rect = Rect::new(1, 2, 3, 4); + widget.prepare_ui(&mut renderer); + widget.update_position(&mut rect); + assert_eq!(widget.is_new_line(), false); + } + + #[test] + fn assert_some_characters_update_position() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("foo bar".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + let mut rect = Rect::new(1, 2, 3, 4); + widget.prepare_ui(&mut renderer); + widget.update_position(&mut rect); + assert_eq!(widget.is_new_line(), false); + } + + #[test] + fn assert_prepare_ui_non_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("foo bar".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + widget.prepare_ui(&mut renderer); + widget.prepare_ui(&mut renderer); + assert_eq!(widget.is_new_line(), false); + } + + #[test] + fn assert_update_empty() { + let config = build_config(); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + widget.update(0, &UpdateContext::Nothing); + assert_eq!(widget.is_new_line(), false); + } + + #[test] + fn assert_update_non_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("foo bar".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + widget.prepare_ui(&mut renderer); + widget.update(0, &UpdateContext::Nothing); + assert_eq!(widget.is_new_line(), false); + } + + #[test] + fn assert_get_character_on_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + widget.prepare_ui(&mut renderer); + let res = widget.get_character_at(0); + assert_eq!(res.is_none(), true); + } + + #[test] + fn assert_get_character_non_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("foo bar".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config.clone()); + widget.prepare_ui(&mut renderer); + let res = widget.get_character_at(0); + assert_eq!(res.is_none(), false); + let mut expected = + TextCharacter::new('f', 0, 0, false, Color::RGBA(135, 175, 95, 0), config); + expected.prepare_ui(&mut renderer); + assert_eq!(res, Some(expected)); + } + + #[test] + fn assert_get_character_at_5_pos_with_non_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("foo bar".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config.clone()); + widget.prepare_ui(&mut renderer); + let res = widget.get_character_at(5); + assert_eq!(res.is_none(), false); + let mut expected = + TextCharacter::new('a', 5, 0, false, Color::RGBA(135, 175, 95, 0), config); + expected.prepare_ui(&mut renderer); + assert_eq!(res, Some(expected)); + } + + #[test] + fn assert_get_full_rect_on_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + widget.prepare_ui(&mut renderer); + let res = widget.full_rect(); + let expected = Rect::new(0, 0, 1, 1); + assert_eq!(res, expected); + } + + #[test] + fn assert_get_full_rect_non_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + // let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("foo bar".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config.clone()); + widget.prepare_ui(&mut renderer); + let res = widget.full_rect(); + let expected = Rect::new(0, 0, 13, 14); + assert_eq!(res, expected); + } + + #[test] + fn assert_render_on_empty() { + let config = build_config(); + + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config); + widget.prepare_ui(&mut renderer); + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + // let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + let expected = CanvasMock::new(); + assert_eq!(canvas, expected); + } + + #[test] + fn assert_render_non_empty() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::String { + token: Token::new("foo bar".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config.clone()); + widget.prepare_ui(&mut renderer); + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + let expected = CanvasMock::new(); + assert_eq!(canvas, expected); + } + + #[test] + fn assert_render_new_line() { + let config = build_config(); + let surface = Surface::new(1024, 1024, PixelFormatEnum::RGBA8888).unwrap(); + let mut canvas = CanvasMock::new(); + let mut renderer = RendererMock::new(config.clone(), surface); + let token = TokenType::Whitespace { + token: Token::new("\n".to_string(), 0, 0, 0, 0), + }; + let mut widget = EditorFileToken::new(&token, false, config.clone()); + widget.prepare_ui(&mut renderer); + widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing); + let expected = CanvasMock::new(); + assert_eq!(canvas, expected); + } +} diff --git a/rider-editor/src/ui/file_editor/file_editor.rs b/rider-editor/src/ui/file_editor/file_editor.rs index 683aac1..1b3cfeb 100644 --- a/rider-editor/src/ui/file_editor/file_editor.rs +++ b/rider-editor/src/ui/file_editor/file_editor.rs @@ -1,5 +1,6 @@ +use crate::app::UpdateResult as UR; use crate::app::*; -use crate::app::{UpdateResult as UR, WindowCanvas as WS}; +use crate::renderer::renderer::Renderer; use crate::ui::scroll_bar::horizontal_scroll_bar::*; use crate::ui::scroll_bar::vertical_scroll_bar::*; use crate::ui::scroll_bar::Scrollable; @@ -40,19 +41,31 @@ impl FileEditor { } } - pub fn delete_front(&mut self, renderer: &mut Renderer) { + pub fn delete_front(&mut self, renderer: &mut R) + where + R: ConfigHolder + CharacterSizeManager + Renderer, + { file_content_manager::delete_front(self, renderer); } - pub fn delete_back(&mut self, renderer: &mut Renderer) { + pub fn delete_back(&mut self, renderer: &mut R) + where + R: ConfigHolder + CharacterSizeManager + Renderer, + { file_content_manager::delete_back(self, renderer); } - pub fn insert_text(&mut self, text: String, renderer: &mut Renderer) { + pub fn insert_text(&mut self, text: String, renderer: &mut R) + where + R: ConfigHolder + CharacterSizeManager + Renderer, + { file_content_manager::insert_text(self, text, renderer); } - pub fn insert_new_line(&mut self, renderer: &mut Renderer) { + pub fn insert_new_line(&mut self, renderer: &mut R) + where + R: ConfigHolder + CharacterSizeManager + Renderer, + { file_content_manager::insert_new_line(self, renderer); } @@ -196,10 +209,13 @@ impl CaretAccess for FileEditor { } } -#[cfg_attr(tarpaulin, skip)] -impl Render for FileEditor { - fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _context: &RenderContext) { - canvas.set_clip_rect(self.dest.clone()); +impl FileEditor { + pub fn render(&self, canvas: &mut C, renderer: &mut R) + where + R: Renderer + ConfigHolder, + C: CanvasAccess, + { + canvas.set_clipping(self.dest.clone()); match self.file() { Some(file) => file.render( canvas, @@ -210,7 +226,6 @@ impl Render for FileEditor { }; self.caret.render( canvas, - renderer, &RenderContext::RelativePosition(self.render_start_point()), ); self.vertical_scroll_bar.render( @@ -223,7 +238,10 @@ impl Render for FileEditor { ); } - fn prepare_ui(&mut self, renderer: &mut Renderer) { + pub fn prepare_ui(&mut self, renderer: &mut T) + where + T: CharacterSizeManager, + { self.caret.prepare_ui(renderer); } } @@ -258,7 +276,7 @@ impl Update for FileEditor { .set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin)); self.horizontal_scroll_bar.update(ticks, context); - self.caret.update(ticks, context); + self.caret.update(); match self.file_mut() { Some(file) => file.update(ticks, context), _ => UR::NoOp, diff --git a/rider-editor/src/ui/filesystem/directory.rs b/rider-editor/src/ui/filesystem/directory.rs index 89f0638..e124b04 100644 --- a/rider-editor/src/ui/filesystem/directory.rs +++ b/rider-editor/src/ui/filesystem/directory.rs @@ -68,7 +68,7 @@ impl DirectoryView { &self.source } - pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) -> bool { + pub fn open_directory(&mut self, dir_path: String, renderer: &mut CanvasRenderer) -> bool { match dir_path { _ if dir_path == self.path => { if !self.opened { @@ -127,7 +127,7 @@ impl DirectoryView { } } - fn read_directory(&mut self, renderer: &mut Renderer) { + fn read_directory(&mut self, renderer: &mut CanvasRenderer) { let entries: fs::ReadDir = match fs::read_dir(self.path.clone()) { Ok(d) => d, _ => return, @@ -167,9 +167,9 @@ impl DirectoryView { self.directories.sort_by(|a, b| a.name().cmp(&b.name())); } - fn render_icon(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + fn render_icon(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect) where - T: RenderImage, + T: CanvasAccess, { let dir_texture_path = { let c = self.config.read().unwrap(); @@ -192,9 +192,9 @@ impl DirectoryView { .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); } - fn render_name(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + fn render_name(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect) where - T: RenderImage, + T: CanvasAccess, { let mut d = dest.clone(); d.set_x(dest.x() + NAME_MARGIN); @@ -213,7 +213,7 @@ impl DirectoryView { }; let text_texture = renderer .texture_manager() - .load_text(&mut text_details, &font) + .load_text(&mut text_details, font.clone()) .unwrap(); d.set_width(size.width()); d.set_height(size.height()); @@ -225,9 +225,9 @@ impl DirectoryView { } } - fn render_children(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + fn render_children(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect) where - T: RenderImage, + T: CanvasAccess, { if !self.expanded { return; @@ -249,7 +249,7 @@ impl DirectoryView { } } - fn calculate_size(&mut self, renderer: &mut Renderer) { + fn calculate_size(&mut self, renderer: &mut CanvasRenderer) { let size = renderer.load_character_size('W'); self.height = size.height(); self.icon_height = size.height(); @@ -287,9 +287,9 @@ impl ConfigHolder for DirectoryView { #[cfg_attr(tarpaulin, skip)] impl DirectoryView { - pub fn render(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext) + pub fn render(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RenderContext) where - T: RenderImage, + T: CanvasAccess, { let dest = self.dest(); let move_point = match context { @@ -302,7 +302,7 @@ impl DirectoryView { self.render_children::(canvas, renderer, &mut dest); } - pub fn prepare_ui(&mut self, renderer: &mut Renderer) { + pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) { if self.opened { for dir in self.directories.iter_mut() { dir.prepare_ui(renderer); diff --git a/rider-editor/src/ui/filesystem/file.rs b/rider-editor/src/ui/filesystem/file.rs index c90cd05..a6fd92b 100644 --- a/rider-editor/src/ui/filesystem/file.rs +++ b/rider-editor/src/ui/filesystem/file.rs @@ -70,9 +70,9 @@ impl FileEntry { ) } - fn render_icon(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + fn render_icon(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect) where - T: RenderImage, + T: CanvasAccess, { let dir_texture_path = { let c = self.config.read().unwrap(); @@ -92,9 +92,9 @@ impl FileEntry { .unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); } - fn render_name(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) + fn render_name(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect) where - T: RenderImage, + T: CanvasAccess, { let mut d = dest.clone(); d.set_x(dest.x() + NAME_MARGIN); @@ -115,7 +115,9 @@ impl FileEntry { text: c.to_string(), font: font_details.clone(), }; - let text_texture = texture_manager.load_text(&mut text_details, &font).unwrap(); + let text_texture = texture_manager + .load_text(&mut text_details, font.clone()) + .unwrap(); d.set_width(size.width()); d.set_height(size.height()); @@ -135,9 +137,9 @@ impl ConfigHolder for FileEntry { #[cfg_attr(tarpaulin, skip)] impl FileEntry { - pub fn render(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext) + pub fn render(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RenderContext) where - T: RenderImage, + T: CanvasAccess, { let mut dest = match context { &RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), @@ -147,7 +149,7 @@ impl FileEntry { self.render_name(canvas, renderer, &mut dest.clone()); } - pub fn prepare_ui(&mut self, renderer: &mut Renderer) { + pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) { let w_rect = get_text_character_rect('W', renderer).unwrap(); self.char_sizes.insert('W', w_rect.clone()); self.height = w_rect.height(); diff --git a/rider-editor/src/ui/menu_bar.rs b/rider-editor/src/ui/menu_bar.rs index 021a0ca..ac8fd03 100644 --- a/rider-editor/src/ui/menu_bar.rs +++ b/rider-editor/src/ui/menu_bar.rs @@ -1,5 +1,4 @@ -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::renderer::*; +use crate::app::UpdateResult as UR; use crate::ui::*; use rider_config::ConfigAccess; use sdl2::pixels::Color; @@ -34,32 +33,37 @@ impl MenuBar { pub fn background_color(&self) -> &Color { &self.background_color } -} -#[cfg_attr(tarpaulin, skip)] -impl Render for MenuBar { - fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { + pub fn render(&self, canvas: &mut C, context: &RenderContext) + where + C: CanvasAccess, + { use std::borrow::*; - canvas.set_clip_rect(self.dest.clone()); - canvas.set_draw_color(self.background_color.clone()); + canvas.set_clipping(self.dest.clone()); canvas - .fill_rect(match context.borrow() { - RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), - _ => self.dest(), - }) + .render_rect( + match context.borrow() { + RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), + _ => self.dest(), + }, + self.background_color.clone(), + ) .unwrap_or_else(|_| panic!("Failed to draw main menu background")); - - canvas.set_draw_color(self.border_color); canvas - .draw_rect(match context.borrow() { - RenderContext::RelativePosition(p) => move_render_point((*p).clone(), &self.dest), - _ => self.dest(), - }) + .render_border( + match context.borrow() { + RenderContext::RelativePosition(p) => { + move_render_point((*p).clone(), &self.dest) + } + _ => self.dest(), + }, + self.border_color.clone(), + ) .unwrap_or_else(|_| panic!("Failed to draw main menu background")); } - fn prepare_ui(&mut self, _renderer: &mut Renderer) { + pub fn prepare_ui(&mut self) { let width = self.config.read().unwrap().width(); let height = u32::from(self.config.read().unwrap().menu_height()); self.dest = Rect::new(0, 0, width, height); @@ -211,3 +215,87 @@ mod test_click_handler { assert_eq!(result, expected); } } + +#[cfg(test)] +mod test_render { + use crate::tests::*; + use crate::ui::*; + use sdl2::pixels::Color; + use sdl2::rect::{Point, Rect}; + use sdl2::render::Texture; + use std::rc::Rc; + use std::sync::*; + + #[derive(Debug, PartialEq)] + struct CanvasMock { + pub clipping: Rect, + pub background_rect: Rect, + pub background_color: Color, + pub border_rect: Rect, + pub border_color: Color, + } + + impl CanvasAccess for CanvasMock { + fn render_rect(&mut self, rect: Rect, color: Color) -> Result<(), String> { + self.background_color = color; + self.background_rect = rect; + Ok(()) + } + + fn render_border(&mut self, rect: Rect, color: Color) -> Result<(), String> { + self.border_color = color; + self.border_rect = rect; + Ok(()) + } + + fn render_image( + &mut self, + _tex: Rc, + _src: Rect, + _dest: Rect, + ) -> Result<(), String> { + unimplemented!() + } + + fn render_line(&mut self, _start: Point, _end: Point, _color: Color) -> Result<(), String> { + unimplemented!() + } + + fn set_clipping(&mut self, rect: Rect) { + self.clipping = rect; + } + } + + #[test] + fn assert_render() { + let context = RenderContext::Nothing; + let config = support::build_config(); + let mut canvas = CanvasMock { + clipping: Rect::new(0, 0, 0, 0), + background_rect: Rect::new(0, 0, 0, 0), + background_color: Color::RGB(0, 0, 0), + border_rect: Rect::new(0, 0, 0, 0), + border_color: Color::RGB(0, 0, 0), + }; + let mut widget = MenuBar::new(Arc::clone(&config)); + widget.prepare_ui(); + widget.render(&mut canvas, &context); + assert_eq!(widget.dest(), Rect::new(0, 0, 1024, 60)); + let expected = CanvasMock { + clipping: Rect::new(0, 0, 1024, 60), + background_rect: Rect::new(0, 0, 1024, 60), + background_color: Color::RGBA(18, 18, 18, 0), + border_rect: Rect::new(0, 0, 1024, 60), + border_color: Color::RGBA(200, 200, 200, 0), + }; + assert_eq!(canvas, expected); + } + + #[test] + fn assert_prepare_ui() { + let config = support::build_config(); + let mut widget = MenuBar::new(Arc::clone(&config)); + widget.prepare_ui(); + assert_eq!(widget.dest(), Rect::new(0, 0, 1024, 60)); + } +} diff --git a/rider-editor/src/ui/mod.rs b/rider-editor/src/ui/mod.rs index 198ba38..3d7d19e 100644 --- a/rider-editor/src/ui/mod.rs +++ b/rider-editor/src/ui/mod.rs @@ -4,9 +4,8 @@ use sdl2::render::Texture; use std::rc::Rc; use crate::app::application::WindowCanvas; -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; +use crate::app::UpdateResult as UR; use crate::renderer::managers::*; -use crate::renderer::Renderer; use rider_config::*; pub mod caret; @@ -42,36 +41,48 @@ pub enum RenderContext { RelativePosition(Point), } -pub trait RenderRect { +pub trait CanvasAccess { fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>; -} - -pub trait RenderBorder { fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>; -} - -pub trait RenderImage { fn render_image(&mut self, tex: Rc, src: Rect, dest: Rect) -> Result<(), String>; + fn render_line( + &mut self, + start: Point, + end: Point, + color: sdl2::pixels::Color, + ) -> Result<(), String>; + + fn set_clipping(&mut self, rect: Rect); } -impl RenderRect for WindowCanvas { +impl CanvasAccess for WindowCanvas { fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { self.set_draw_color(color); self.fill_rect(rect) } -} -impl RenderBorder for WindowCanvas { fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { self.set_draw_color(color); self.draw_rect(rect) } -} -impl RenderImage for WindowCanvas { fn render_image(&mut self, tex: Rc, src: Rect, dest: Rect) -> Result<(), String> { self.copy_ex(&tex, Some(src), Some(dest), 0.0, None, false, false) } + + fn render_line( + &mut self, + start: Point, + end: Point, + color: sdl2::pixels::Color, + ) -> Result<(), String> { + self.set_draw_color(color); + self.draw_line(start, end) + } + + fn set_clipping(&mut self, rect: Rect) { + self.set_clip_rect(rect); + } } #[inline] @@ -109,13 +120,6 @@ pub fn move_render_point(p: Point, d: &Rect) -> Rect { Rect::new(d.x() + p.x(), d.y() + p.y(), d.width(), d.height()) } -#[cfg_attr(tarpaulin, skip)] -pub trait Render { - fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext); - - fn prepare_ui(&mut self, renderer: &mut Renderer); -} - pub trait Update { fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR; } diff --git a/rider-editor/src/ui/modal/open_file.rs b/rider-editor/src/ui/modal/open_file.rs index 483faf2..4756cb3 100644 --- a/rider-editor/src/ui/modal/open_file.rs +++ b/rider-editor/src/ui/modal/open_file.rs @@ -1,4 +1,4 @@ -use crate::renderer::Renderer; +use crate::renderer::CanvasRenderer; use crate::ui::*; use crate::ui::{RenderContext as RC, UpdateContext as UC}; use rider_config::ConfigAccess; @@ -56,7 +56,7 @@ impl OpenFile { self.root_path.clone() } - pub fn open_directory(&mut self, dir_path: String, renderer: &mut Renderer) { + pub fn open_directory(&mut self, dir_path: String, renderer: &mut CanvasRenderer) { self.directory_view.open_directory(dir_path, renderer); { let dest = self.directory_view.dest(); @@ -147,9 +147,9 @@ impl Update for OpenFile { #[cfg_attr(tarpaulin, skip)] impl OpenFile { - pub fn render(&self, canvas: &mut T, renderer: &mut Renderer, context: &RC) + pub fn render(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RC) where - T: RenderRect + RenderBorder + RenderImage, + T: CanvasAccess, { let dest = match context { RC::RelativePosition(p) => move_render_point(p.clone(), &self.dest), @@ -183,7 +183,7 @@ impl OpenFile { ); } - pub fn prepare_ui(&mut self, renderer: &mut Renderer) { + pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) { self.directory_view.prepare_ui(renderer); } } diff --git a/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs b/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs index fdcc8a4..cd00887 100644 --- a/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs +++ b/rider-editor/src/ui/scroll_bar/horizontal_scroll_bar.rs @@ -57,7 +57,7 @@ impl Update for HorizontalScrollBar { impl HorizontalScrollBar { pub fn render(&self, canvas: &mut T, context: &RenderContext) where - T: RenderRect, + T: CanvasAccess, { if self.full_width < self.viewport { return; diff --git a/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs b/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs index 2655db2..340bc2a 100644 --- a/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs +++ b/rider-editor/src/ui/scroll_bar/vertical_scroll_bar.rs @@ -57,7 +57,7 @@ impl Update for VerticalScrollBar { impl VerticalScrollBar { pub fn render(&self, canvas: &mut T, context: &RenderContext) where - T: RenderBorder, + T: CanvasAccess, { if self.full_height() < self.viewport() { return; diff --git a/rider-editor/src/ui/text_character.rs b/rider-editor/src/ui/text_character.rs index 8d400fb..d411cd3 100644 --- a/rider-editor/src/ui/text_character.rs +++ b/rider-editor/src/ui/text_character.rs @@ -1,5 +1,4 @@ -use crate::app::{UpdateResult as UR, WindowCanvas as WC}; -use crate::renderer::managers::*; +use crate::app::UpdateResult as UR; use crate::renderer::*; use crate::ui::caret::CaretPosition; use crate::ui::*; @@ -7,12 +6,15 @@ use rider_config::{ConfigAccess, ConfigHolder}; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; +use std::fmt::Debug; +use std::fmt::Error; +use std::fmt::Formatter; pub trait CharacterSizeManager { fn load_character_size(&mut self, c: char) -> Rect; } -#[derive(Clone, Debug)] +#[derive(Clone)] pub struct TextCharacter { text_character: char, position: usize, @@ -99,13 +101,16 @@ impl TextCharacter { } } -#[cfg_attr(tarpaulin, skip)] impl TextCharacter { /** * Must first create targets so even if new line appear renderer will know * where move render starting point */ - pub fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { + pub fn render(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext) + where + R: Renderer + ConfigHolder, + C: CanvasAccess, + { if self.is_new_line() { return; } @@ -123,21 +128,9 @@ impl TextCharacter { _ => self.dest(), }; - let font = renderer - .font_manager() - .load(&font_details) - .unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details)); - if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) { + if let Ok(texture) = renderer.load_text_tex(&mut details, font_details) { canvas - .copy_ex( - &texture, - Some(self.source.clone()), - Some(dest.clone()), - 0.0, - None, - false, - false, - ) + .render_image(texture, self.source.clone(), dest) .unwrap(); } // let c = Color::RGB(255, 0, 0); @@ -147,10 +140,9 @@ impl TextCharacter { pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T) where - T: ConfigHolder + CharacterSizeManager + ManagersHolder<'l>, + T: ConfigHolder + CharacterSizeManager + Renderer, { let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into(); - let rect = renderer.load_character_size(self.text_character); self.set_source(&rect); self.set_dest(&rect); @@ -161,14 +153,12 @@ impl TextCharacter { font: font_details.clone(), }; - let font = renderer - .font_manager() - .load(&font_details) - .unwrap_or_else(|_| panic!("Font not found {:?}", font_details)); - renderer - .texture_manager() - .load_text(&mut details, &font) - .unwrap_or_else(|_| panic!("Could not create texture for {:?}", self.text_character)); + if let Err(error_message) = renderer.load_text_tex(&mut details, font_details) { + info!( + "Could not create texture for '{:?}' with {:?}", + self.text_character, error_message + ) + } } } @@ -205,6 +195,33 @@ impl RenderBox for TextCharacter { } } +impl PartialEq for TextCharacter { + fn eq(&self, other: &Self) -> bool { + self.line == other.line + && self.position == other.position + && self.last_in_line == other.last_in_line + && self.dest == other.dest + && self.source == other.source + && self.color == other.color + } +} + +impl Debug for TextCharacter { + fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { + write!( + f, + "TextCharacter {{ text_character: {:?}, position: {:?}, line: {:?}, last_in_line: {:?}, source: {:?}, dest: {:?}, color: {:?} }}", + self.text_character, + self.position, + self.line, + self.last_in_line, + self.source, + self.dest, + self.color + ) + } +} + #[cfg(test)] mod test_getters { use crate::tests::*; diff --git a/scripts/coverage.sh b/scripts/coverage.sh new file mode 100755 index 0000000..fd2ad97 --- /dev/null +++ b/scripts/coverage.sh @@ -0,0 +1,6 @@ +#!/bin/env bash + +rm -Rf cov +rm -Rf target/debug/*rider* +cargo test --no-run;kcov --exclude-pattern=github.com,target/debug --verify cov $(ls target/debug/rider_editor* | sed "s/\\.d\$//" | head -n1) + diff --git a/scripts/serve-editor.sh b/scripts/serve-editor.sh new file mode 100755 index 0000000..6f5ba38 --- /dev/null +++ b/scripts/serve-editor.sh @@ -0,0 +1,12 @@ +#!/bin/env bash + +nvm use 11.0.0 +here=$(pwd) +target="cov/$(ls ${here}/cov | grep editor | grep "\\." | head -n1)" +if [[ -e "${target}" ]]; +then + cd ${target} && echo "here: $(pwd)" && serve . && cd ${here} +else + echo "Target: '${target}' does not exists!" +fi +