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 character_destination = text_character.dest().clone();
// let pos = text_character.position().clone();
Some(text_character.clone())
})??;

View File

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

View File

@ -13,6 +13,7 @@ pub mod support {
use sdl2::rect::Rect;
use sdl2::render::Texture;
use sdl2::ttf::Font;
use std::collections::HashMap;
use std::fmt::Debug;
use std::fmt::Error;
use std::fmt::Formatter;
@ -35,10 +36,27 @@ pub mod support {
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)]
pub struct RendererRect {
pub rect: Rect,
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)]
@ -46,13 +64,18 @@ pub mod support {
pub rects: Vec<RendererRect>,
pub borders: 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)]
impl Debug for CanvasMock {
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![],
lines: vec![],
clippings: vec![],
character_sizes: HashMap::new(),
}
}
}
@ -81,35 +105,132 @@ pub mod support {
#[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 });
self.rects.push(RendererRect {
rect,
color,
shape: CanvasShape::Rectangle,
});
Ok(())
}
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(())
}
fn render_image(
&mut self,
_tex: Rc<Texture>,
_src: Rect,
_dest: Rect,
) -> Result<(), String> {
unimplemented!()
fn render_image(&mut self, _tex: Rc<Texture>, src: Rect, dest: Rect) -> Result<(), String> {
self.rects.push(RendererRect::new(
dest.clone(),
Color::RGBA(0, 0, 0, 255),
CanvasShape::Image(src.clone(), dest.clone(), format!("_tex: Rc<Texture>")),
));
Ok(())
}
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,
shape: CanvasShape::Line,
});
Ok(())
}
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);
}
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)]

View File

@ -317,85 +317,38 @@ mod test_click_handler {
#[cfg(test)]
mod test_render {
use crate::tests::support;
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::ParentPosition(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 canvas = support::CanvasMock::new();
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.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));
assert_eq!(
canvas.find_pixel_with_color(
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]
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 canvas = support::CanvasMock::new();
canvas.set_character_rect('I', Rect::new(11, 12, 6, 23));
let mut widget = Caret::new(config);
widget.move_caret(CaretPosition::new(0, 0, 0), Point::new(11, 12));
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> {
let mut current = None;
for section in self.sections.iter() {
let c = section.get_last_at_line(line);
if c.is_some() {
current = c;
let mut current: Option<TextCharacter> = None;
'scanning: for section in self.sections.iter().rev() {
for token in section.tokens().iter().rev() {
for text_character in token.characters().iter().rev() {
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> {
let new_path = file.path();
let mut file = Some(file);
let old_path = match self.file {
Some(ref f) => f.path(),
_ => format!(""),
};
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();
}
if old_path != new_path {
self.vertical_scroll_bar.reset();
self.horizontal_scroll_bar.reset();
}
file
}
@ -225,8 +232,12 @@ impl CaretAccess for FileEditor {
let rect = text_character.dest();
let position =
CaretPosition::new(text_character.position() + 1, line as usize, 0);
let p = if text_character.is_last_in_line() && text_character.is_new_line() {
rect.top_left()
let p = if text_character.is_new_line() {
file.get_character_at(text_character.position() + 1)
.map_or_else(
|| text_character.dest().top_left(),
|tc| tc.dest().top_left(),
)
} else {
rect.top_right()
};

View File

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

View File

@ -59,6 +59,8 @@ pub trait CanvasAccess {
) -> Result<(), String>;
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 {
@ -89,6 +91,14 @@ impl CanvasAccess for WindowCanvas {
fn set_clipping(&mut self, 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]