Make render testable (#16)

* Make render testable

* Add some tests

* Format code

* Fix render caret after input

* Add kcov scripts

* Fix caret position

* Test move caret

* Test move caret

* Test move caret

* Additional tests

* Additional token to color test
This commit is contained in:
Adrian Woźniak 2019-03-02 23:27:07 +01:00 committed by GitHub
parent c2e1369738
commit 08a5987a4a
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
22 changed files with 1469 additions and 224 deletions

2
.gitignore vendored
View File

@ -5,3 +5,5 @@ log
.rider .rider
.codecov .codecov
cobertura.xml cobertura.xml
cov

View File

@ -1,5 +1,5 @@
use crate::app::{UpdateResult, WindowCanvas as WC}; use crate::app::{UpdateResult, WindowCanvas as WC};
use crate::renderer::Renderer; use crate::renderer::CanvasRenderer;
use crate::ui::*; use crate::ui::*;
use rider_config::*; use rider_config::*;
use sdl2::rect::Point; use sdl2::rect::Point;
@ -27,7 +27,7 @@ impl AppState {
} }
#[cfg_attr(tarpaulin, skip)] #[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) { if let Ok(buffer) = read_to_string(&file_path) {
let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone()); let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone());
file.prepare_ui(renderer); file.prepare_ui(renderer);
@ -41,7 +41,7 @@ impl AppState {
} }
#[cfg_attr(tarpaulin, skip)] #[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() { match self.open_file_modal.as_mut() {
Some(modal) => modal.open_directory(dir_path, renderer), Some(modal) => modal.open_directory(dir_path, renderer),
_ => (), _ => (),
@ -74,20 +74,18 @@ impl AppState {
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl Render for AppState { impl AppState {
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _context: &RenderContext) { pub fn render(&self, canvas: &mut WC, renderer: &mut CanvasRenderer, _context: &RenderContext) {
self.file_editor self.file_editor.render(canvas, renderer);
.render(canvas, renderer, &RenderContext::Nothing); self.menu_bar.render(canvas, &RenderContext::Nothing);
self.menu_bar
.render(canvas, renderer, &RenderContext::Nothing);
match self.open_file_modal.as_ref() { match self.open_file_modal.as_ref() {
Some(modal) => modal.render(canvas, renderer, &RenderContext::Nothing), Some(modal) => modal.render(canvas, renderer, &RenderContext::Nothing),
_ => (), _ => (),
}; };
} }
fn prepare_ui(&mut self, renderer: &mut Renderer) { pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) {
self.menu_bar.prepare_ui(renderer); self.menu_bar.prepare_ui();
self.file_editor.prepare_ui(renderer); self.file_editor.prepare_ui(renderer);
} }
} }

View File

@ -1,5 +1,5 @@
pub use crate::app::app_state::AppState; 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::caret::{CaretPosition, MoveDirection};
use crate::ui::*; use crate::ui::*;
pub use rider_config::{Config, ConfigAccess, ConfigHolder}; pub use rider_config::{Config, ConfigAccess, ConfigHolder};
@ -115,7 +115,8 @@ impl Application {
let texture_creator = self.canvas.texture_creator(); let texture_creator = self.canvas.texture_creator();
let sleep_time = Duration::new(0, 1_000_000_000u32 / 60); let sleep_time = Duration::new(0, 1_000_000_000u32 / 60);
let mut app_state = AppState::new(Arc::clone(&self.config)); 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); app_state.prepare_ui(&mut renderer);
'running: loop { 'running: loop {

View File

@ -41,27 +41,185 @@ pub fn move_caret_left(file_editor: &mut FileEditor) {
#[cfg(test)] #[cfg(test)]
mod test_move_right { mod test_move_right {
use super::*; use super::*;
use crate::renderer::managers::FontDetails;
use crate::renderer::managers::TextDetails;
use crate::renderer::renderer::Renderer;
use crate::tests::support; 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<RwLock<Config>>,
}
impl RendererMock {
pub fn new(config: Arc<RwLock<Config>>) -> Self {
Self { config }
}
}
impl Renderer for RendererMock {
#[cfg_attr(tarpaulin, skip)]
fn load_font(&mut self, _details: FontDetails) -> Rc<Font> {
unimplemented!()
}
fn load_text_tex(
&mut self,
_details: &mut TextDetails,
_font_details: FontDetails,
) -> Result<Rc<Texture>, String> {
Err("skip render character".to_owned())
}
}
impl ConfigHolder for RendererMock {
fn config(&self) -> &Arc<RwLock<Config>> {
&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] #[test]
fn must_do_nothing() { fn assert_move_with_no_file() {
let config = support::build_config(); let config = support::build_config();
let mut editor = FileEditor::new(config); let mut editor = FileEditor::new(config);
assert_eq!(move_caret_right(&mut editor), ()); 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)] #[cfg(test)]
mod test_move_left { mod test_move_left {
use super::*; use super::*;
use crate::renderer::managers::FontDetails;
use crate::renderer::managers::TextDetails;
use crate::renderer::renderer::Renderer;
use crate::tests::support; 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<RwLock<Config>>,
}
impl RendererMock {
pub fn new(config: Arc<RwLock<Config>>) -> Self {
Self { config }
}
}
impl Renderer for RendererMock {
#[cfg_attr(tarpaulin, skip)]
fn load_font(&mut self, _details: FontDetails) -> Rc<Font> {
unimplemented!()
}
fn load_text_tex(
&mut self,
_details: &mut TextDetails,
_font_details: FontDetails,
) -> Result<Rc<Texture>, String> {
Err("skip render character".to_owned())
}
}
impl ConfigHolder for RendererMock {
fn config(&self) -> &Arc<RwLock<Config>> {
&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] #[test]
fn must_do_nothing() { fn assert_move_caret_without_file() {
let config = support::build_config(); let config = support::build_config();
let mut editor = FileEditor::new(config); let mut editor = FileEditor::new(config);
assert_eq!(move_caret_left(&mut editor), ()); 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), ());
}
} }

View File

@ -1,5 +1,5 @@
use crate::app::*; use crate::app::*;
use crate::renderer::Renderer; use crate::renderer::renderer::Renderer;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::sync::*; use std::sync::*;
@ -11,7 +11,10 @@ pub fn current_file_path(file_editor: &mut FileEditor) -> String {
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { pub fn delete_front<R>(file_editor: &mut FileEditor, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
let mut buffer: String = if let Some(file) = file_editor.file() { let mut buffer: String = if let Some(file) = file_editor.file() {
file file
} else { } else {
@ -53,7 +56,10 @@ pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) {
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) { pub fn delete_back<R>(file_editor: &mut FileEditor, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
let file: &EditorFile = if let Some(file) = file_editor.file() { let file: &EditorFile = if let Some(file) = file_editor.file() {
file file
} else { } else {
@ -70,30 +76,27 @@ pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) {
file_editor.replace_current_file(new_file); file_editor.replace_current_file(new_file);
} }
#[cfg_attr(tarpaulin, skip)] pub fn insert_text<R>(file_editor: &mut FileEditor, text: String, renderer: &mut R)
pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) { where
let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer()); R: ConfigHolder + CharacterSizeManager + Renderer,
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, let mut buffer: String = match file_editor.file() {
_ => return, Some(f) => f.buffer(),
None => return,
}; };
let mut pos = if current.is_new_line() {
current.dest().top_left() let maybe_character = file_editor
+ Point::new(0, renderer.load_character_size('\n').height() as i32) .file()
} else { .and_then(|file| file.get_character_at(file_editor.caret().text_position()));
current.dest().top_left()
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(); let mut position: CaretPosition = file_editor.caret().position().clone();
for character in text.chars() { for c in text.chars() {
buffer.insert(position.text_position(), character); buffer.insert(position.text_position(), c);
let rect = renderer.load_character_size(character); let rect = renderer.load_character_size(c);
pos = pos + Point::new(rect.width() as i32, 0); pos = pos + Point::new(rect.width() as i32, 0);
position = position.moved(1, 0, 0); position = position.moved(1, 0, 0);
file_editor.caret_mut().move_caret(position, pos.clone()); 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); file_editor.replace_current_file(new_file);
} }
#[cfg_attr(tarpaulin, skip)] pub fn insert_new_line<R>(file_editor: &mut FileEditor, renderer: &mut R)
pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { where
let mut buffer: String = if let Some(file) = file_editor.file() { R: ConfigHolder + CharacterSizeManager + Renderer,
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, let mut buffer: String = match file_editor.file() {
_ => return, 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(); let mut position: CaretPosition = file_editor.caret().position().clone();
buffer.insert(position.text_position(), '\n'); buffer.insert(position.text_position(), '\n');
let rect = renderer.load_character_size('\n'); let rect = renderer.load_character_size('\n');
pos = Point::new(0, pos.y() + rect.height() as i32); 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()); file_editor.caret_mut().move_caret(position, pos.clone());
let mut new_file = EditorFile::new( let mut new_file = EditorFile::new(
@ -144,7 +146,54 @@ pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) {
#[cfg(test)] #[cfg(test)]
mod tests { mod tests {
use super::*; use super::*;
use crate::renderer::managers::FontDetails;
use crate::renderer::managers::TextDetails;
use crate::tests::support; 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<RwLock<Config>>,
}
impl RendererMock {
pub fn new(config: Arc<RwLock<Config>>) -> Self {
Self { config }
}
}
impl Renderer for RendererMock {
#[cfg_attr(tarpaulin, skip)]
fn load_font(&mut self, _details: FontDetails) -> Rc<Font> {
unimplemented!()
}
fn load_text_tex(
&mut self,
_details: &mut TextDetails,
_font_details: FontDetails,
) -> Result<Rc<Texture>, String> {
Err("skip render character".to_owned())
}
}
impl ConfigHolder for RendererMock {
fn config(&self) -> &Arc<RwLock<Config>> {
&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] #[test]
fn must_return_empty_string_when_no_file() { fn must_return_empty_string_when_no_file() {
@ -167,4 +216,156 @@ mod tests {
let result = current_file_path(&mut editor); let result = current_file_path(&mut editor);
assert_eq!(result, "/foo/bar".to_owned()); 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());
}
} }

View File

@ -181,7 +181,7 @@ pub trait TextTextureManager<'l> {
fn load_text( fn load_text(
&mut self, &mut self,
details: &mut TextDetails, details: &mut TextDetails,
font: &Rc<Font>, font: Rc<Font>,
) -> Result<Rc<Texture<'l>>, String>; ) -> Result<Rc<Texture<'l>>, String>;
} }
@ -191,7 +191,7 @@ impl<'l> TextTextureManager<'l> for TextureManager<'l> {
fn load_text( fn load_text(
&mut self, &mut self,
details: &mut TextDetails, details: &mut TextDetails,
font: &Rc<Font>, font: Rc<Font>,
) -> Result<Rc<Texture<'l>>, String> { ) -> Result<Rc<Texture<'l>>, String> {
let key = details.get_cache_key(); let key = details.get_cache_key();
self.cache.get(key.as_str()).cloned().map_or_else( self.cache.get(key.as_str()).cloned().map_or_else(

View File

@ -3,12 +3,25 @@ use crate::ui::get_text_character_rect;
use crate::ui::text_character::CharacterSizeManager; use crate::ui::text_character::CharacterSizeManager;
use rider_config::{ConfigAccess, ConfigHolder}; use rider_config::{ConfigAccess, ConfigHolder};
use sdl2::rect::Rect; use sdl2::rect::Rect;
use sdl2::render::Texture;
use sdl2::render::TextureCreator; use sdl2::render::TextureCreator;
use sdl2::ttf::Font;
use sdl2::ttf::Sdl2TtfContext; use sdl2::ttf::Sdl2TtfContext;
use std::collections::HashMap; use std::collections::HashMap;
use std::rc::Rc;
pub trait Renderer {
fn load_font(&mut self, details: FontDetails) -> Rc<Font>;
fn load_text_tex(
&mut self,
details: &mut TextDetails,
font_details: FontDetails,
) -> Result<Rc<Texture>, String>;
}
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
pub struct Renderer<'l> { pub struct CanvasRenderer<'l> {
config: ConfigAccess, config: ConfigAccess,
font_manager: FontManager<'l>, font_manager: FontManager<'l>,
texture_manager: TextureManager<'l>, texture_manager: TextureManager<'l>,
@ -16,7 +29,7 @@ pub struct Renderer<'l> {
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl<'l> Renderer<'l> { impl<'l> CanvasRenderer<'l> {
pub fn new( pub fn new(
config: ConfigAccess, config: ConfigAccess,
font_context: &'l Sdl2TtfContext, 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 { fn load_character_size(&mut self, c: char) -> Rect {
let (font_path, font_size) = { let (font_path, font_size) = {
let config = self.config().read().unwrap(); let config = self.config().read().unwrap();
@ -65,7 +79,7 @@ impl<'l> CharacterSizeManager for Renderer<'l> {
} }
#[cfg_attr(tarpaulin, skip)] #[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> { fn font_manager(&mut self) -> &mut FontManager<'l> {
&mut self.font_manager &mut self.font_manager
} }
@ -76,8 +90,31 @@ impl<'l> ManagersHolder<'l> for Renderer<'l> {
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl<'l> ConfigHolder for Renderer<'l> { impl<'l> ConfigHolder for CanvasRenderer<'l> {
fn config(&self) -> &ConfigAccess { fn config(&self) -> &ConfigAccess {
&self.config &self.config
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl<'l> Renderer for CanvasRenderer<'l> {
fn load_font(&mut self, details: FontDetails) -> Rc<Font> {
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<Rc<Texture>, 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)
}
}

View File

@ -1,5 +1,4 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::renderer::*;
use crate::ui::*; use crate::ui::*;
use rider_config::ConfigAccess; use rider_config::ConfigAccess;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
@ -7,7 +6,6 @@ use std::ops::Deref;
#[derive(Clone, Debug, PartialEq)] #[derive(Clone, Debug, PartialEq)]
pub struct Caret { pub struct Caret {
pending: bool,
blink_delay: u8, blink_delay: u8,
state: CaretState, state: CaretState,
position: CaretPosition, position: CaretPosition,
@ -25,7 +23,6 @@ impl Caret {
blink_delay: 0, blink_delay: 0,
dest: Rect::new(0, 0, 6, 0), dest: Rect::new(0, 0, 6, 0),
colors: CaretColor::new(bright, blur), colors: CaretColor::new(bright, blur),
pending: true,
position: CaretPosition::new(0, 0, 0), position: CaretPosition::new(0, 0, 0),
} }
} }
@ -67,8 +64,11 @@ impl Deref for Caret {
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl Render for Caret { impl Caret {
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { pub fn render<T>(&self, canvas: &mut T, context: &RenderContext)
where
T: CanvasAccess,
{
use std::borrow::*; use std::borrow::*;
let dest = match context.borrow() { let dest = match context.borrow() {
@ -82,26 +82,22 @@ impl Render for Caret {
CaretState::Blur => self.colors.blur(), CaretState::Blur => self.colors.blur(),
} }
.clone(); .clone();
canvas.set_draw_color(color);
canvas canvas
.draw_line(start, end) .render_line(start, end, color)
.unwrap_or_else(|_| panic!("Failed to draw a caret")); .unwrap_or_else(|_| panic!("Failed to draw a caret"));
} }
fn prepare_ui(&mut self, renderer: &mut Renderer) { pub fn prepare_ui<T>(&mut self, renderer: &mut T)
if !self.pending { where
return; T: CharacterSizeManager,
} {
let rect = renderer.load_character_size('I');
if let Some(rect) = get_text_character_rect('W', renderer) {
self.dest.set_height(rect.height()); self.dest.set_height(rect.height());
} }
self.pending = false;
}
} }
impl Update for Caret { impl Caret {
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR { pub fn update(&mut self) -> UR {
self.blink_delay += 1; self.blink_delay += 1;
if self.blink_delay >= 30 { if self.blink_delay >= 30 {
self.blink_delay = 0; self.blink_delay = 0;
@ -312,3 +308,91 @@ mod test_click_handler {
assert_eq!(result, expected); 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<Texture>,
_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));
}
}

View File

@ -1,12 +1,13 @@
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::sync::*; use std::sync::*;
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::renderer::Renderer; use crate::renderer::renderer::Renderer;
use crate::ui::file::editor_file_section::EditorFileSection; use crate::ui::file::editor_file_section::EditorFileSection;
use crate::ui::text_character::TextCharacter; use crate::ui::text_character::TextCharacter;
use crate::ui::*; use crate::ui::*;
use rider_config::Config; use rider_config::Config;
use rider_config::ConfigHolder;
#[derive(Clone, Debug)] #[derive(Clone, Debug)]
pub struct EditorFile { pub struct EditorFile {
@ -113,21 +114,27 @@ impl TextCollection for EditorFile {
} }
} }
#[cfg_attr(tarpaulin, skip)] impl EditorFile {
impl Render for EditorFile { pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { where
R: Renderer + ConfigHolder,
C: CanvasAccess,
{
for section in self.sections.iter() { for section in self.sections.iter() {
section.render(canvas, renderer, context); section.render(canvas, renderer, context);
} }
} }
fn prepare_ui(&mut self, renderer: &mut Renderer) { pub fn prepare_ui<R>(&mut self, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
for section in self.sections.iter_mut() { for section in self.sections.iter_mut() {
section.prepare_ui(renderer); section.prepare_ui(renderer);
} }
if let Some(r) = get_text_character_rect('W', renderer) {
let r = renderer.load_character_size('W');
self.line_height = r.height(); self.line_height = r.height();
}
self.refresh_characters_position(); self.refresh_characters_position();
} }
} }
@ -202,11 +209,72 @@ impl RenderBox for EditorFile {
} }
#[cfg(test)] #[cfg(test)]
mod test_render_box { mod tests {
use crate::tests::support; use crate::tests::support;
use crate::ui::*; use crate::ui::*;
use sdl2::rect::{Point, Rect}; 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] #[test]
fn assert_dest() { fn assert_dest() {
let config = support::build_config(); let config = support::build_config();

View File

@ -1,12 +1,13 @@
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::sync::*; use std::sync::*;
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::renderer::Renderer; use crate::renderer::renderer::Renderer;
use crate::ui::file::editor_file_token::EditorFileToken; use crate::ui::file::editor_file_token::EditorFileToken;
use crate::ui::text_character::TextCharacter; use crate::ui::text_character::TextCharacter;
use crate::ui::*; use crate::ui::*;
use rider_config::Config; use rider_config::Config;
use rider_config::ConfigHolder;
use rider_lexers; use rider_lexers;
use rider_lexers::Language; use rider_lexers::Language;
@ -26,7 +27,7 @@ impl EditorFileSection {
.get(ext.as_str()) .get(ext.as_str())
.unwrap_or(&Language::PlainText) .unwrap_or(&Language::PlainText)
.clone(); .clone();
let lexer_tokens = rider_lexers::parse(buffer.clone(), language); let lexer_tokens = rider_lexers::parse(buffer.clone(), language.clone());
let mut tokens: Vec<EditorFileToken> = vec![]; let mut tokens: Vec<EditorFileToken> = vec![];
let mut iterator = lexer_tokens.iter().peekable(); let mut iterator = lexer_tokens.iter().peekable();
@ -43,7 +44,6 @@ impl EditorFileSection {
); );
tokens.push(token); tokens.push(token);
} }
let language = Language::PlainText;
Self { Self {
tokens, tokens,
language, language,
@ -51,11 +51,34 @@ impl EditorFileSection {
} }
} }
pub fn language(&self) -> Language {
self.language
}
pub fn update_positions(&mut self, current: &mut Rect) { pub fn update_positions(&mut self, current: &mut Rect) {
for c in self.tokens.iter_mut() { for c in self.tokens.iter_mut() {
c.update_position(current); c.update_position(current);
} }
} }
pub fn render<R, C>(&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 { 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 { impl Update for EditorFileSection {
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
let mut result = UR::NoOp; let mut result = UR::NoOp;
@ -179,3 +187,35 @@ impl ClickHandler for EditorFileSection {
false false
} }
} }
#[cfg(test)]
mod tests {
use super::*;
use crate::tests::support::build_config;
impl EditorFileSection {
pub fn tokens(&self) -> Vec<EditorFileToken> {
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);
}
}

View File

@ -1,7 +1,8 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::*; use crate::ui::*;
use rider_config::Config; use rider_config::Config;
use rider_config::ConfigHolder;
use rider_lexers::TokenType; use rider_lexers::TokenType;
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
@ -123,13 +124,16 @@ impl TextCollection for EditorFileToken {
} }
} }
#[cfg_attr(tarpaulin, skip)] impl EditorFileToken {
impl Render for EditorFileToken {
/** /**
* Must first create targets so even if new line appear renderer will know * Must first create targets so even if new line appear renderer will know
* where move render starting point * where move render starting point
*/ */
fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
where
R: Renderer + ConfigHolder,
C: CanvasAccess,
{
if self.token_type.is_new_line() { if self.token_type.is_new_line() {
return; return;
} }
@ -138,7 +142,10 @@ impl Render for EditorFileToken {
} }
} }
fn prepare_ui(&mut self, renderer: &mut Renderer) { pub fn prepare_ui<R>(&mut self, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
if !self.characters.is_empty() { if !self.characters.is_empty() {
return; return;
} }
@ -188,3 +195,505 @@ impl ClickHandler for EditorFileToken {
false 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<RendererRect>,
pub borders: Vec<RendererRect>,
pub lines: Vec<RendererRect>,
pub clippings: Vec<Rect>,
}
#[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<Texture>,
_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<RwLock<Config>>,
pub canvas: sdl2::render::Canvas<Surface<'l>>,
pub map: Vec<Rc<Texture<'l>>>,
pub creator: TextureCreator<SurfaceContext<'l>>,
}
#[cfg_attr(tarpaulin, skip)]
impl<'l> RendererMock<'l> {
pub fn new(config: Arc<RwLock<Config>>, 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<Font> {
unimplemented!("load_font")
}
fn load_text_tex(
&mut self,
_details: &mut TextDetails,
_font_details: FontDetails,
) -> Result<Rc<Texture>, 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<RwLock<Config>> {
&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);
}
}

View File

@ -1,5 +1,6 @@
use crate::app::UpdateResult as UR;
use crate::app::*; 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::horizontal_scroll_bar::*;
use crate::ui::scroll_bar::vertical_scroll_bar::*; use crate::ui::scroll_bar::vertical_scroll_bar::*;
use crate::ui::scroll_bar::Scrollable; 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<R>(&mut self, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
file_content_manager::delete_front(self, renderer); file_content_manager::delete_front(self, renderer);
} }
pub fn delete_back(&mut self, renderer: &mut Renderer) { pub fn delete_back<R>(&mut self, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
file_content_manager::delete_back(self, renderer); file_content_manager::delete_back(self, renderer);
} }
pub fn insert_text(&mut self, text: String, renderer: &mut Renderer) { pub fn insert_text<R>(&mut self, text: String, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
file_content_manager::insert_text(self, text, renderer); file_content_manager::insert_text(self, text, renderer);
} }
pub fn insert_new_line(&mut self, renderer: &mut Renderer) { pub fn insert_new_line<R>(&mut self, renderer: &mut R)
where
R: ConfigHolder + CharacterSizeManager + Renderer,
{
file_content_manager::insert_new_line(self, renderer); file_content_manager::insert_new_line(self, renderer);
} }
@ -196,10 +209,13 @@ impl CaretAccess for FileEditor {
} }
} }
#[cfg_attr(tarpaulin, skip)] impl FileEditor {
impl Render for FileEditor { pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R)
fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _context: &RenderContext) { where
canvas.set_clip_rect(self.dest.clone()); R: Renderer + ConfigHolder,
C: CanvasAccess,
{
canvas.set_clipping(self.dest.clone());
match self.file() { match self.file() {
Some(file) => file.render( Some(file) => file.render(
canvas, canvas,
@ -210,7 +226,6 @@ impl Render for FileEditor {
}; };
self.caret.render( self.caret.render(
canvas, canvas,
renderer,
&RenderContext::RelativePosition(self.render_start_point()), &RenderContext::RelativePosition(self.render_start_point()),
); );
self.vertical_scroll_bar.render( 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<T>(&mut self, renderer: &mut T)
where
T: CharacterSizeManager,
{
self.caret.prepare_ui(renderer); 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)); .set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin));
self.horizontal_scroll_bar.update(ticks, context); self.horizontal_scroll_bar.update(ticks, context);
self.caret.update(ticks, context); self.caret.update();
match self.file_mut() { match self.file_mut() {
Some(file) => file.update(ticks, context), Some(file) => file.update(ticks, context),
_ => UR::NoOp, _ => UR::NoOp,

View File

@ -68,7 +68,7 @@ impl DirectoryView {
&self.source &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 { match dir_path {
_ if dir_path == self.path => { _ if dir_path == self.path => {
if !self.opened { 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()) { let entries: fs::ReadDir = match fs::read_dir(self.path.clone()) {
Ok(d) => d, Ok(d) => d,
_ => return, _ => return,
@ -167,9 +167,9 @@ impl DirectoryView {
self.directories.sort_by(|a, b| a.name().cmp(&b.name())); self.directories.sort_by(|a, b| a.name().cmp(&b.name()));
} }
fn render_icon<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) fn render_icon<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
where where
T: RenderImage, T: CanvasAccess,
{ {
let dir_texture_path = { let dir_texture_path = {
let c = self.config.read().unwrap(); let c = self.config.read().unwrap();
@ -192,9 +192,9 @@ impl DirectoryView {
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); .unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
} }
fn render_name<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) fn render_name<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
where where
T: RenderImage, T: CanvasAccess,
{ {
let mut d = dest.clone(); let mut d = dest.clone();
d.set_x(dest.x() + NAME_MARGIN); d.set_x(dest.x() + NAME_MARGIN);
@ -213,7 +213,7 @@ impl DirectoryView {
}; };
let text_texture = renderer let text_texture = renderer
.texture_manager() .texture_manager()
.load_text(&mut text_details, &font) .load_text(&mut text_details, font.clone())
.unwrap(); .unwrap();
d.set_width(size.width()); d.set_width(size.width());
d.set_height(size.height()); d.set_height(size.height());
@ -225,9 +225,9 @@ impl DirectoryView {
} }
} }
fn render_children<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) fn render_children<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
where where
T: RenderImage, T: CanvasAccess,
{ {
if !self.expanded { if !self.expanded {
return; 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'); let size = renderer.load_character_size('W');
self.height = size.height(); self.height = size.height();
self.icon_height = size.height(); self.icon_height = size.height();
@ -287,9 +287,9 @@ impl ConfigHolder for DirectoryView {
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl DirectoryView { impl DirectoryView {
pub fn render<T>(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext) pub fn render<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RenderContext)
where where
T: RenderImage, T: CanvasAccess,
{ {
let dest = self.dest(); let dest = self.dest();
let move_point = match context { let move_point = match context {
@ -302,7 +302,7 @@ impl DirectoryView {
self.render_children::<T>(canvas, renderer, &mut dest); self.render_children::<T>(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 { if self.opened {
for dir in self.directories.iter_mut() { for dir in self.directories.iter_mut() {
dir.prepare_ui(renderer); dir.prepare_ui(renderer);

View File

@ -70,9 +70,9 @@ impl FileEntry {
) )
} }
fn render_icon<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) fn render_icon<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
where where
T: RenderImage, T: CanvasAccess,
{ {
let dir_texture_path = { let dir_texture_path = {
let c = self.config.read().unwrap(); let c = self.config.read().unwrap();
@ -92,9 +92,9 @@ impl FileEntry {
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture")); .unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
} }
fn render_name<T>(&self, canvas: &mut T, renderer: &mut Renderer, dest: &mut Rect) fn render_name<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
where where
T: RenderImage, T: CanvasAccess,
{ {
let mut d = dest.clone(); let mut d = dest.clone();
d.set_x(dest.x() + NAME_MARGIN); d.set_x(dest.x() + NAME_MARGIN);
@ -115,7 +115,9 @@ impl FileEntry {
text: c.to_string(), text: c.to_string(),
font: font_details.clone(), 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_width(size.width());
d.set_height(size.height()); d.set_height(size.height());
@ -135,9 +137,9 @@ impl ConfigHolder for FileEntry {
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl FileEntry { impl FileEntry {
pub fn render<T>(&self, canvas: &mut T, renderer: &mut Renderer, context: &RenderContext) pub fn render<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RenderContext)
where where
T: RenderImage, T: CanvasAccess,
{ {
let mut dest = match context { let mut dest = match context {
&RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), &RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
@ -147,7 +149,7 @@ impl FileEntry {
self.render_name(canvas, renderer, &mut dest.clone()); 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(); let w_rect = get_text_character_rect('W', renderer).unwrap();
self.char_sizes.insert('W', w_rect.clone()); self.char_sizes.insert('W', w_rect.clone());
self.height = w_rect.height(); self.height = w_rect.height();

View File

@ -1,5 +1,4 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::renderer::*;
use crate::ui::*; use crate::ui::*;
use rider_config::ConfigAccess; use rider_config::ConfigAccess;
use sdl2::pixels::Color; use sdl2::pixels::Color;
@ -34,32 +33,37 @@ impl MenuBar {
pub fn background_color(&self) -> &Color { pub fn background_color(&self) -> &Color {
&self.background_color &self.background_color
} }
}
#[cfg_attr(tarpaulin, skip)] pub fn render<C>(&self, canvas: &mut C, context: &RenderContext)
impl Render for MenuBar { where
fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, context: &RenderContext) { C: CanvasAccess,
{
use std::borrow::*; use std::borrow::*;
canvas.set_clip_rect(self.dest.clone()); canvas.set_clipping(self.dest.clone());
canvas.set_draw_color(self.background_color.clone());
canvas canvas
.fill_rect(match context.borrow() { .render_rect(
match context.borrow() {
RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest), RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
_ => self.dest(), _ => self.dest(),
}) },
self.background_color.clone(),
)
.unwrap_or_else(|_| panic!("Failed to draw main menu background")); .unwrap_or_else(|_| panic!("Failed to draw main menu background"));
canvas.set_draw_color(self.border_color);
canvas canvas
.draw_rect(match context.borrow() { .render_border(
RenderContext::RelativePosition(p) => move_render_point((*p).clone(), &self.dest), match context.borrow() {
RenderContext::RelativePosition(p) => {
move_render_point((*p).clone(), &self.dest)
}
_ => self.dest(), _ => self.dest(),
}) },
self.border_color.clone(),
)
.unwrap_or_else(|_| panic!("Failed to draw main menu background")); .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 width = self.config.read().unwrap().width();
let height = u32::from(self.config.read().unwrap().menu_height()); let height = u32::from(self.config.read().unwrap().menu_height());
self.dest = Rect::new(0, 0, width, height); self.dest = Rect::new(0, 0, width, height);
@ -211,3 +215,87 @@ mod test_click_handler {
assert_eq!(result, expected); 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<Texture>,
_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));
}
}

View File

@ -4,9 +4,8 @@ use sdl2::render::Texture;
use std::rc::Rc; use std::rc::Rc;
use crate::app::application::WindowCanvas; 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::managers::*;
use crate::renderer::Renderer;
use rider_config::*; use rider_config::*;
pub mod caret; pub mod caret;
@ -42,36 +41,48 @@ pub enum RenderContext {
RelativePosition(Point), RelativePosition(Point),
} }
pub trait RenderRect { pub trait CanvasAccess {
fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>; 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>; fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String>;
}
pub trait RenderImage {
fn render_image(&mut self, tex: Rc<Texture>, src: Rect, dest: Rect) -> Result<(), String>; fn render_image(&mut self, tex: Rc<Texture>, 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> { fn render_rect(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> {
self.set_draw_color(color); self.set_draw_color(color);
self.fill_rect(rect) self.fill_rect(rect)
} }
}
impl RenderBorder for WindowCanvas {
fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> { fn render_border(&mut self, rect: Rect, color: sdl2::pixels::Color) -> Result<(), String> {
self.set_draw_color(color); self.set_draw_color(color);
self.draw_rect(rect) self.draw_rect(rect)
} }
}
impl RenderImage for WindowCanvas {
fn render_image(&mut self, tex: Rc<Texture>, src: Rect, dest: Rect) -> Result<(), String> { fn render_image(&mut self, tex: Rc<Texture>, src: Rect, dest: Rect) -> Result<(), String> {
self.copy_ex(&tex, Some(src), Some(dest), 0.0, None, false, false) 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] #[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()) 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 { pub trait Update {
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR; fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR;
} }

View File

@ -1,4 +1,4 @@
use crate::renderer::Renderer; use crate::renderer::CanvasRenderer;
use crate::ui::*; use crate::ui::*;
use crate::ui::{RenderContext as RC, UpdateContext as UC}; use crate::ui::{RenderContext as RC, UpdateContext as UC};
use rider_config::ConfigAccess; use rider_config::ConfigAccess;
@ -56,7 +56,7 @@ impl OpenFile {
self.root_path.clone() 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); self.directory_view.open_directory(dir_path, renderer);
{ {
let dest = self.directory_view.dest(); let dest = self.directory_view.dest();
@ -147,9 +147,9 @@ impl Update for OpenFile {
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl OpenFile { impl OpenFile {
pub fn render<T>(&self, canvas: &mut T, renderer: &mut Renderer, context: &RC) pub fn render<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RC)
where where
T: RenderRect + RenderBorder + RenderImage, T: CanvasAccess,
{ {
let dest = match context { let dest = match context {
RC::RelativePosition(p) => move_render_point(p.clone(), &self.dest), 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); self.directory_view.prepare_ui(renderer);
} }
} }

View File

@ -57,7 +57,7 @@ impl Update for HorizontalScrollBar {
impl HorizontalScrollBar { impl HorizontalScrollBar {
pub fn render<T>(&self, canvas: &mut T, context: &RenderContext) pub fn render<T>(&self, canvas: &mut T, context: &RenderContext)
where where
T: RenderRect, T: CanvasAccess,
{ {
if self.full_width < self.viewport { if self.full_width < self.viewport {
return; return;

View File

@ -57,7 +57,7 @@ impl Update for VerticalScrollBar {
impl VerticalScrollBar { impl VerticalScrollBar {
pub fn render<T>(&self, canvas: &mut T, context: &RenderContext) pub fn render<T>(&self, canvas: &mut T, context: &RenderContext)
where where
T: RenderBorder, T: CanvasAccess,
{ {
if self.full_height() < self.viewport() { if self.full_height() < self.viewport() {
return; return;

View File

@ -1,5 +1,4 @@
use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::app::UpdateResult as UR;
use crate::renderer::managers::*;
use crate::renderer::*; use crate::renderer::*;
use crate::ui::caret::CaretPosition; use crate::ui::caret::CaretPosition;
use crate::ui::*; use crate::ui::*;
@ -7,12 +6,15 @@ use rider_config::{ConfigAccess, ConfigHolder};
use sdl2::pixels::Color; use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; use sdl2::rect::{Point, Rect};
use std::fmt::Debug;
use std::fmt::Error;
use std::fmt::Formatter;
pub trait CharacterSizeManager { pub trait CharacterSizeManager {
fn load_character_size(&mut self, c: char) -> Rect; fn load_character_size(&mut self, c: char) -> Rect;
} }
#[derive(Clone, Debug)] #[derive(Clone)]
pub struct TextCharacter { pub struct TextCharacter {
text_character: char, text_character: char,
position: usize, position: usize,
@ -99,13 +101,16 @@ impl TextCharacter {
} }
} }
#[cfg_attr(tarpaulin, skip)]
impl TextCharacter { impl TextCharacter {
/** /**
* Must first create targets so even if new line appear renderer will know * Must first create targets so even if new line appear renderer will know
* where move render starting point * where move render starting point
*/ */
pub fn render(&self, canvas: &mut WC, renderer: &mut Renderer, context: &RenderContext) { pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
where
R: Renderer + ConfigHolder,
C: CanvasAccess,
{
if self.is_new_line() { if self.is_new_line() {
return; return;
} }
@ -123,21 +128,9 @@ impl TextCharacter {
_ => self.dest(), _ => self.dest(),
}; };
let font = renderer if let Ok(texture) = renderer.load_text_tex(&mut details, font_details) {
.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) {
canvas canvas
.copy_ex( .render_image(texture, self.source.clone(), dest)
&texture,
Some(self.source.clone()),
Some(dest.clone()),
0.0,
None,
false,
false,
)
.unwrap(); .unwrap();
} }
// let c = Color::RGB(255, 0, 0); // let c = Color::RGB(255, 0, 0);
@ -147,10 +140,9 @@ impl TextCharacter {
pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T) pub fn prepare_ui<'l, T>(&mut self, renderer: &mut T)
where where
T: ConfigHolder + CharacterSizeManager + ManagersHolder<'l>, T: ConfigHolder + CharacterSizeManager + Renderer,
{ {
let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into(); let font_details: FontDetails = renderer.config().read().unwrap().editor_config().into();
let rect = renderer.load_character_size(self.text_character); let rect = renderer.load_character_size(self.text_character);
self.set_source(&rect); self.set_source(&rect);
self.set_dest(&rect); self.set_dest(&rect);
@ -161,14 +153,12 @@ impl TextCharacter {
font: font_details.clone(), font: font_details.clone(),
}; };
let font = renderer if let Err(error_message) = renderer.load_text_tex(&mut details, font_details) {
.font_manager() info!(
.load(&font_details) "Could not create texture for '{:?}' with {:?}",
.unwrap_or_else(|_| panic!("Font not found {:?}", font_details)); self.text_character, error_message
renderer )
.texture_manager() }
.load_text(&mut details, &font)
.unwrap_or_else(|_| panic!("Could not create texture for {:?}", self.text_character));
} }
} }
@ -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)] #[cfg(test)]
mod test_getters { mod test_getters {
use crate::tests::*; use crate::tests::*;

6
scripts/coverage.sh Executable file
View File

@ -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)

12
scripts/serve-editor.sh Executable file
View File

@ -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