Prevent constant refresh scrollbars, add tests functions, fix caret location on empty line

This commit is contained in:
Adrian Woźniak 2019-12-23 13:04:06 +01:00
parent f06cb0fc1d
commit 60d4c0be88
8 changed files with 227 additions and 156 deletions

View File

@ -56,8 +56,6 @@ pub fn move_caret_down(file_editor: &mut FileEditor) -> Option<TextCharacter> {
} }
} }
let text_character = text_character?; let text_character = text_character?;
// let character_destination = text_character.dest().clone();
// let pos = text_character.position().clone();
Some(text_character.clone()) Some(text_character.clone())
})??; })??;

View File

@ -1,3 +1,5 @@
#![feature(clamp)]
extern crate dirs; extern crate dirs;
#[macro_use] #[macro_use]
extern crate log; extern crate log;

View File

@ -13,6 +13,7 @@ pub mod support {
use sdl2::rect::Rect; use sdl2::rect::Rect;
use sdl2::render::Texture; use sdl2::render::Texture;
use sdl2::ttf::Font; use sdl2::ttf::Font;
use std::collections::HashMap;
use std::fmt::Debug; use std::fmt::Debug;
use std::fmt::Error; use std::fmt::Error;
use std::fmt::Formatter; use std::fmt::Formatter;
@ -35,10 +36,27 @@ pub mod support {
Arc::new(RwLock::new(config)) Arc::new(RwLock::new(config))
} }
#[cfg_attr(tarpaulin, skip)]
#[derive(Debug, PartialEq)]
pub enum CanvasShape {
Line,
Border,
Rectangle,
Image(Rect, Rect, String),
}
#[derive(Debug, PartialEq)] #[derive(Debug, PartialEq)]
pub struct RendererRect { pub struct RendererRect {
pub rect: Rect, pub rect: Rect,
pub color: Color, pub color: Color,
pub shape: CanvasShape,
}
#[cfg_attr(tarpaulin, skip)]
impl RendererRect {
pub fn new(rect: Rect, color: Color, shape: CanvasShape) -> Self {
Self { rect, color, shape }
}
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
@ -46,13 +64,18 @@ pub mod support {
pub rects: Vec<RendererRect>, pub rects: Vec<RendererRect>,
pub borders: Vec<RendererRect>, pub borders: Vec<RendererRect>,
pub lines: Vec<RendererRect>, pub lines: Vec<RendererRect>,
pub clippings: Vec<Rect>, pub clippings: Vec<Option<Rect>>,
pub character_sizes: HashMap<char, sdl2::rect::Rect>,
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl Debug for CanvasMock { impl Debug for CanvasMock {
fn fmt(&self, f: &mut Formatter) -> Result<(), Error> { fn fmt(&self, f: &mut Formatter) -> Result<(), Error> {
write!(f, "CanvasMock {{}}") write!(
f,
"CanvasMock {{ {:?} {:?} {:?} }}",
self.rects, self.lines, self.clippings
)
} }
} }
@ -74,6 +97,7 @@ pub mod support {
borders: vec![], borders: vec![],
lines: vec![], lines: vec![],
clippings: vec![], clippings: vec![],
character_sizes: HashMap::new(),
} }
} }
} }
@ -81,35 +105,132 @@ pub mod support {
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]
impl CanvasAccess for CanvasMock { impl CanvasAccess for CanvasMock {
fn render_rect(&mut self, rect: Rect, color: Color) -> Result<(), String> { fn render_rect(&mut self, rect: Rect, color: Color) -> Result<(), String> {
self.rects.push(RendererRect { rect, color }); self.rects.push(RendererRect {
rect,
color,
shape: CanvasShape::Rectangle,
});
Ok(()) Ok(())
} }
fn render_border(&mut self, rect: Rect, color: Color) -> Result<(), String> { fn render_border(&mut self, rect: Rect, color: Color) -> Result<(), String> {
self.borders.push(RendererRect { rect, color }); self.borders.push(RendererRect {
rect,
color,
shape: CanvasShape::Border,
});
Ok(()) Ok(())
} }
fn render_image( fn render_image(&mut self, _tex: Rc<Texture>, src: Rect, dest: Rect) -> Result<(), String> {
&mut self, self.rects.push(RendererRect::new(
_tex: Rc<Texture>, dest.clone(),
_src: Rect, Color::RGBA(0, 0, 0, 255),
_dest: Rect, CanvasShape::Image(src.clone(), dest.clone(), format!("_tex: Rc<Texture>")),
) -> Result<(), String> { ));
unimplemented!() Ok(())
} }
fn render_line(&mut self, start: Point, end: Point, color: Color) -> Result<(), String> { fn render_line(&mut self, start: Point, end: Point, color: Color) -> Result<(), String> {
self.lines.push(RendererRect { self.lines.push(RendererRect {
rect: Rect::new(start.x(), start.y(), end.x() as u32, end.y() as u32), rect: Rect::new(start.x(), start.y(), end.x() as u32, end.y() as u32),
color, color,
shape: CanvasShape::Line,
}); });
Ok(()) Ok(())
} }
fn set_clipping(&mut self, rect: Rect) { fn set_clipping(&mut self, rect: Rect) {
self.clippings.push(Some(rect));
}
fn set_clip_rect(&mut self, rect: Option<Rect>) {
self.clippings.push(rect); self.clippings.push(rect);
} }
fn clip_rect(&self) -> Option<Rect> {
self.clippings.last().cloned().unwrap_or_else(|| None)
}
}
impl CharacterSizeManager for CanvasMock {
fn load_character_size(&mut self, c: char) -> Rect {
match self.character_sizes.get(&c) {
Some(r) => r.clone(),
None => {
self.character_sizes.insert(c, Rect::new(0, 0, 1, 1));
self.character_sizes.get(&c).cloned().unwrap()
}
}
}
}
impl CanvasMock {
pub fn set_character_rect(&mut self, c: char, rect: Rect) {
self.character_sizes.insert(c, rect);
}
pub fn find_pixel_with_color(
&self,
point: sdl2::rect::Point,
color: sdl2::pixels::Color,
) -> Option<&RendererRect> {
for rect in self.rects.iter() {
if rect.rect.contains_point(point.clone()) && rect.color == color {
return Some(rect.clone());
}
}
for rect in self.borders.iter() {
if rect.rect.contains_point(point.clone()) && rect.color == color {
return Some(rect.clone());
}
}
for rect in self.lines.iter() {
if rect.rect.contains_point(point.clone()) && rect.color == color {
return Some(rect.clone());
}
}
None
}
pub fn find_rect_with_color(
&self,
subject: sdl2::rect::Rect,
color: sdl2::pixels::Color,
) -> Option<&RendererRect> {
for rect in self.rects.iter() {
if rect.rect == subject && rect.color == color {
return Some(rect.clone());
}
}
None
}
pub fn find_line_with_color(
&self,
subject: sdl2::rect::Rect,
color: sdl2::pixels::Color,
) -> Option<&RendererRect> {
for rect in self.lines.iter() {
if rect.rect == subject && rect.color == color {
return Some(rect.clone());
}
}
None
}
pub fn find_border_with_color(
&self,
subject: sdl2::rect::Rect,
color: sdl2::pixels::Color,
) -> Option<&RendererRect> {
for rect in self.borders.iter() {
if rect.rect == subject && rect.color == color {
return Some(rect.clone());
}
}
None
}
} }
#[cfg_attr(tarpaulin, skip)] #[cfg_attr(tarpaulin, skip)]

View File

@ -317,85 +317,38 @@ mod test_click_handler {
#[cfg(test)] #[cfg(test)]
mod test_render { mod test_render {
use crate::tests::support;
use crate::tests::support::build_config; use crate::tests::support::build_config;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::Color;
use sdl2::rect::{Point, Rect}; 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] #[test]
fn assert_render_line() { fn assert_render_line() {
let config = build_config(); let config = build_config();
let context = RenderContext::ParentPosition(Point::new(10, 14)); let context = RenderContext::ParentPosition(Point::new(10, 14));
let mut canvas = CanvasMock { let mut canvas = support::CanvasMock::new();
start: Point::new(0, 0),
end: Point::new(0, 0),
color: sdl2::pixels::Color::RGB(0, 0, 0),
};
let mut widget = Caret::new(config); let mut widget = Caret::new(config);
canvas.set_character_rect('I', Rect::new(11, 12, 6, 23));
widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(23, 23)); widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(23, 23));
widget.render(&mut canvas, &context); widget.render(&mut canvas, &context);
assert_eq!(canvas.start, Point::new(33, 37)); assert_eq!(
assert_eq!(canvas.end, Point::new(33, 38)); canvas.find_pixel_with_color(
assert_eq!(canvas.color, sdl2::pixels::Color::RGBA(121, 121, 121, 0)); Point::new(33, 37),
sdl2::pixels::Color::RGBA(121, 121, 121, 0)
),
Some(&support::RendererRect::new(
Rect::new(33, 37, 33, 38),
sdl2::pixels::Color::RGBA(121, 121, 121, 0),
support::CanvasShape::Line
))
);
} }
#[test] #[test]
fn assert_prepare_ui() { fn assert_prepare_ui() {
let config = build_config(); let config = build_config();
let mut canvas = CanvasMock { let mut canvas = support::CanvasMock::new();
start: Point::new(0, 0), canvas.set_character_rect('I', Rect::new(11, 12, 6, 23));
end: Point::new(0, 0),
color: sdl2::pixels::Color::RGB(0, 0, 0),
};
let mut widget = Caret::new(config); let mut widget = Caret::new(config);
widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(11, 12)); widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(11, 12));
widget.prepare_ui(&mut canvas); widget.prepare_ui(&mut canvas);

View File

@ -111,14 +111,38 @@ impl TextCollection for EditorFile {
} }
fn get_last_at_line(&self, line: usize) -> Option<TextCharacter> { fn get_last_at_line(&self, line: usize) -> Option<TextCharacter> {
let mut current = None; let mut current: Option<TextCharacter> = None;
for section in self.sections.iter() { 'scanning: for section in self.sections.iter().rev() {
let c = section.get_last_at_line(line); for token in section.tokens().iter().rev() {
if c.is_some() { for text_character in token.characters().iter().rev() {
current = c; match text_character.line() {
l if l > line => continue,
l if l < line => break 'scanning,
_ => (),
};
match (
current.clone().map(|ref c| c.is_new_line()),
text_character.is_new_line(),
) {
(None, true) => current = Some(text_character.clone()),
(None, false) => current = Some(text_character.clone()),
(Some(true), false) => current = Some(text_character.clone()),
(Some(false), false) => break,
_ => continue,
};
}
} }
} }
current match current {
Some(ref tc) => {
// Click on empty new line
if tc.is_new_line() && self.get_character_at(tc.position() - 1).is_some() {
return self.get_character_at(tc.position() - 1);
}
return current;
}
None => None,
}
} }
} }

View File

@ -170,13 +170,20 @@ impl FileAccess for FileEditor {
} }
fn open_file(&mut self, file: EditorFile) -> Option<EditorFile> { fn open_file(&mut self, file: EditorFile) -> Option<EditorFile> {
let new_path = file.path();
let mut file = Some(file); let mut file = Some(file);
let old_path = match self.file {
Some(ref f) => f.path(),
_ => format!(""),
};
mem::swap(&mut self.file, &mut file); mem::swap(&mut self.file, &mut file);
if let Some(f) = self.file.as_ref() { if let Some(ref f) = self.file {
self.full_rect = f.full_rect(); self.full_rect = f.full_rect();
} }
self.vertical_scroll_bar.reset(); if old_path != new_path {
self.horizontal_scroll_bar.reset(); self.vertical_scroll_bar.reset();
self.horizontal_scroll_bar.reset();
}
file file
} }
@ -225,8 +232,12 @@ impl CaretAccess for FileEditor {
let rect = text_character.dest(); let rect = text_character.dest();
let position = let position =
CaretPosition::new(text_character.position() + 1, line as usize, 0); CaretPosition::new(text_character.position() + 1, line as usize, 0);
let p = if text_character.is_last_in_line() && text_character.is_new_line() { let p = if text_character.is_new_line() {
rect.top_left() file.get_character_at(text_character.position() + 1)
.map_or_else(
|| text_character.dest().top_left(),
|tc| tc.dest().top_left(),
)
} else { } else {
rect.top_right() rect.top_right()
}; };

View File

@ -267,83 +267,35 @@ mod test_render {
use crate::tests::support::SimpleRendererMock; use crate::tests::support::SimpleRendererMock;
use crate::tests::*; use crate::tests::*;
use crate::ui::*; use crate::ui::*;
use sdl2::pixels::Color; use sdl2::rect::Rect;
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] #[test]
fn assert_render() { fn assert_render() {
let rect_color = sdl2::pixels::Color::RGBA(18, 18, 18, 0);
let border_color = sdl2::pixels::Color::RGBA(200, 200, 200, 0);
let context = RenderContext::Nothing; let context = RenderContext::Nothing;
let config = support::build_config(); let config = support::build_config();
let mut canvas = CanvasMock { let mut canvas = support::CanvasMock::new();
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 renderer = SimpleRendererMock::new(config.clone()); let mut renderer = SimpleRendererMock::new(config.clone());
let mut widget = MenuBar::new(Arc::clone(&config)); let mut widget = MenuBar::new(config.clone());
widget.prepare_ui(); widget.prepare_ui();
widget.render(&mut canvas, &mut renderer, &context); widget.render(&mut canvas, &mut renderer, &context);
assert_eq!(widget.dest(), Rect::new(0, 0, 1024, 40)); assert_eq!(widget.dest(), Rect::new(0, 0, 1024, 40));
let expected = CanvasMock { assert_eq!(
clipping: Rect::new(32, 10, 32, 32), canvas.find_rect_with_color(Rect::new(0, 0, 1024, 40), rect_color.clone()),
background_rect: Rect::new(0, 0, 1024, 40), Some(&support::RendererRect::new(
background_color: Color::RGBA(18, 18, 18, 0), Rect::new(0, 0, 1024, 40),
border_rect: Rect::new(0, 0, 1024, 40), rect_color,
border_color: Color::RGBA(200, 200, 200, 0), support::CanvasShape::Rectangle
}; ))
assert_eq!(canvas, expected); );
} assert_eq!(
canvas.find_border_with_color(Rect::new(0, 0, 1024, 40), border_color.clone()),
#[test] Some(&support::RendererRect::new(
fn assert_prepare_ui() { Rect::new(0, 0, 1024, 40),
let config = support::build_config(); border_color,
let mut widget = MenuBar::new(Arc::clone(&config)); support::CanvasShape::Border
widget.prepare_ui(); ))
assert_eq!(widget.dest(), Rect::new(0, 0, 1024, 40)); );
} }
} }

View File

@ -59,6 +59,8 @@ pub trait CanvasAccess {
) -> Result<(), String>; ) -> Result<(), String>;
fn set_clipping(&mut self, rect: Rect); fn set_clipping(&mut self, rect: Rect);
fn set_clip_rect(&mut self, rect: Option<Rect>);
fn clip_rect(&self) -> Option<Rect>;
} }
impl CanvasAccess for WindowCanvas { impl CanvasAccess for WindowCanvas {
@ -89,6 +91,14 @@ impl CanvasAccess for WindowCanvas {
fn set_clipping(&mut self, rect: Rect) { fn set_clipping(&mut self, rect: Rect) {
self.set_clip_rect(rect); self.set_clip_rect(rect);
} }
fn set_clip_rect(&mut self, rect: Option<Rect>) {
self.set_clip_rect(rect);
}
fn clip_rect(&self) -> Option<Rect> {
self.clip_rect()
}
} }
#[inline] #[inline]