Gh 9/project tree sidebar (#17)
* Make render testable * Format code * Fix render caret after input * Add new lines between tests * Fix fmt * Add project tree to view * Add drag, clean up code * Add some tests * Add scroll, response to click on folder * Format code * Test file entry Format code Test file entry * Test dir view * Fix character position after delete last at line * Fmt * Fix tests
This commit is contained in:
parent
245550a74e
commit
c714ca15e0
@ -9,7 +9,7 @@ Text editor in rust
|
||||
```bash
|
||||
curl https://sh.rustup.rs -sSf | sh
|
||||
sudo apt-get install -q -y libsdl2-dev libsdl2-2.0-0 libsdl2-gfx-dev libsdl2-image-dev libsdl2-mixer-dev libsdl2-net-dev libsdl2-ttf-dev
|
||||
rustup run nightly cargo build
|
||||
rustup run nightly cargo build --all -rr
|
||||
```
|
||||
|
||||
## Road map
|
||||
|
@ -10,7 +10,7 @@ impl ScrollConfig {
|
||||
Self {
|
||||
width: 4,
|
||||
margin_right: 5,
|
||||
speed: 10,
|
||||
speed: 30,
|
||||
}
|
||||
}
|
||||
|
||||
@ -91,7 +91,7 @@ mod tests {
|
||||
fn assert_speed() {
|
||||
let config = ScrollConfig::new();
|
||||
let result = config.speed();
|
||||
let expected = 10;
|
||||
let expected = 30;
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
|
@ -1,4 +1,6 @@
|
||||
use crate::app::{UpdateResult, WindowCanvas as WC};
|
||||
use crate::app::application::Application;
|
||||
use crate::app::UpdateResult;
|
||||
use crate::renderer::renderer::Renderer;
|
||||
use crate::renderer::CanvasRenderer;
|
||||
use crate::ui::*;
|
||||
use rider_config::*;
|
||||
@ -9,6 +11,7 @@ use std::sync::*;
|
||||
|
||||
pub struct AppState {
|
||||
menu_bar: MenuBar,
|
||||
project_tree: ProjectTreeSidebar,
|
||||
files: Vec<EditorFile>,
|
||||
config: Arc<RwLock<Config>>,
|
||||
file_editor: FileEditor,
|
||||
@ -19,6 +22,10 @@ impl AppState {
|
||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
||||
Self {
|
||||
menu_bar: MenuBar::new(Arc::clone(&config)),
|
||||
project_tree: ProjectTreeSidebar::new(
|
||||
Application::current_working_directory(),
|
||||
config.clone(),
|
||||
),
|
||||
files: vec![],
|
||||
file_editor: FileEditor::new(Arc::clone(&config)),
|
||||
open_file_modal: None,
|
||||
@ -40,18 +47,22 @@ impl AppState {
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn open_directory(&mut self, dir_path: String, renderer: &mut CanvasRenderer) {
|
||||
pub fn open_directory<R>(&mut self, dir_path: String, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
match self.open_file_modal.as_mut() {
|
||||
Some(modal) => modal.open_directory(dir_path, renderer),
|
||||
_ => (),
|
||||
None => self.project_tree.open_directory(dir_path, renderer),
|
||||
};
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn file_editor(&self) -> &FileEditor {
|
||||
&self.file_editor
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn file_editor_mut(&mut self) -> &mut FileEditor {
|
||||
&mut self.file_editor
|
||||
}
|
||||
@ -75,23 +86,38 @@ impl AppState {
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl AppState {
|
||||
pub fn render(&self, canvas: &mut WC, renderer: &mut CanvasRenderer, _context: &RenderContext) {
|
||||
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, _context: &RenderContext)
|
||||
where
|
||||
C: CanvasAccess,
|
||||
R: Renderer + ConfigHolder + CharacterSizeManager,
|
||||
{
|
||||
// file editor
|
||||
self.file_editor.render(canvas, renderer);
|
||||
|
||||
// menu bar
|
||||
self.menu_bar.render(canvas, &RenderContext::Nothing);
|
||||
|
||||
// project tree
|
||||
self.project_tree.render(canvas, renderer);
|
||||
|
||||
// open file modal
|
||||
match self.open_file_modal.as_ref() {
|
||||
Some(modal) => modal.render(canvas, renderer, &RenderContext::Nothing),
|
||||
_ => (),
|
||||
};
|
||||
}
|
||||
|
||||
pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) {
|
||||
pub fn prepare_ui<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
self.menu_bar.prepare_ui();
|
||||
self.project_tree.prepare_ui(renderer);
|
||||
self.file_editor.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for AppState {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
pub fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
// open file modal
|
||||
let res = match self.open_file_modal.as_mut() {
|
||||
Some(modal) => modal.update(ticks, &UpdateContext::Nothing),
|
||||
_ => UpdateResult::NoOp,
|
||||
@ -100,8 +126,17 @@ impl Update for AppState {
|
||||
return res;
|
||||
}
|
||||
|
||||
// menu bar
|
||||
self.menu_bar.update(ticks, context);
|
||||
self.file_editor.update(ticks, context);
|
||||
|
||||
// sidebar
|
||||
self.project_tree.update(ticks);
|
||||
|
||||
// file editor
|
||||
let context = UpdateContext::ParentPosition(
|
||||
self.project_tree.full_rect().top_right() + Point::new(10, 0),
|
||||
);
|
||||
self.file_editor.update(ticks, &context);
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
@ -109,6 +144,14 @@ impl Update for AppState {
|
||||
impl AppState {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
||||
if self
|
||||
.project_tree
|
||||
.is_left_click_target(point, &UpdateContext::Nothing)
|
||||
{
|
||||
return self
|
||||
.project_tree
|
||||
.on_left_click(point, &UpdateContext::Nothing);
|
||||
}
|
||||
match self.open_file_modal.as_mut() {
|
||||
Some(modal) => return modal.on_left_click(point, &UpdateContext::Nothing),
|
||||
_ => (),
|
||||
|
@ -16,6 +16,7 @@ use sdl2::surface::Surface;
|
||||
use sdl2::video::Window;
|
||||
use sdl2::EventPump;
|
||||
use sdl2::{Sdl, TimerSubsystem, VideoSubsystem};
|
||||
use std::env;
|
||||
use std::process::Command;
|
||||
use std::sync::{Arc, RwLock};
|
||||
use std::thread::sleep;
|
||||
@ -29,6 +30,8 @@ pub enum UpdateResult {
|
||||
Stop,
|
||||
RefreshPositions,
|
||||
MouseLeftClicked(Point),
|
||||
MouseDragStart(Point),
|
||||
MouseDragStop(Point),
|
||||
MoveCaret(Rect, CaretPosition),
|
||||
DeleteFront,
|
||||
DeleteBack,
|
||||
@ -44,6 +47,7 @@ pub enum UpdateResult {
|
||||
OpenFile(String),
|
||||
OpenDirectory(String),
|
||||
OpenFileModal,
|
||||
FileDropped(String),
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
@ -187,14 +191,16 @@ impl Application {
|
||||
app_state.open_directory(dir_path.clone(), &mut renderer);
|
||||
}
|
||||
UpdateResult::OpenFileModal => {
|
||||
use std::env;
|
||||
let pwd = env::current_dir().unwrap().to_str().unwrap().to_string();
|
||||
let pwd = Self::current_working_directory();
|
||||
let mut modal =
|
||||
OpenFile::new(pwd.clone(), 400, 800, Arc::clone(&self.config));
|
||||
modal.prepare_ui(&mut renderer);
|
||||
modal.open_directory(pwd.clone(), &mut renderer);
|
||||
app_state.set_open_file_modal(Some(modal));
|
||||
}
|
||||
UpdateResult::MouseDragStart(_point) => (),
|
||||
UpdateResult::MouseDragStop(_point) => (),
|
||||
UpdateResult::FileDropped(_path) => (),
|
||||
}
|
||||
}
|
||||
self.tasks = new_tasks;
|
||||
@ -241,20 +247,21 @@ impl Application {
|
||||
Event::Quit { .. } => self.tasks.push(UpdateResult::Stop),
|
||||
Event::MouseButtonUp {
|
||||
mouse_btn, x, y, ..
|
||||
} => match mouse_btn {
|
||||
MouseButton::Left => self
|
||||
} if mouse_btn == MouseButton::Left => {
|
||||
self.tasks
|
||||
.push(UpdateResult::MouseDragStart(Point::new(x, y)));
|
||||
self.tasks
|
||||
.push(UpdateResult::MouseLeftClicked(Point::new(x, y)));
|
||||
}
|
||||
Event::DropFile { filename, .. } => {
|
||||
self.tasks.push(UpdateResult::FileDropped(filename))
|
||||
}
|
||||
Event::MouseButtonDown {
|
||||
x, y, mouse_btn, ..
|
||||
} if mouse_btn == MouseButton::Left => self
|
||||
.tasks
|
||||
.push(UpdateResult::MouseLeftClicked(Point::new(x, y))),
|
||||
_ => (),
|
||||
},
|
||||
Event::KeyDown { keycode, .. } => {
|
||||
let keycode = if keycode.is_some() {
|
||||
keycode.unwrap()
|
||||
} else {
|
||||
continue;
|
||||
};
|
||||
|
||||
match keycode {
|
||||
.push(UpdateResult::MouseDragStart(Point::new(x, y))),
|
||||
Event::KeyDown { keycode, .. } if keycode.is_some() => match keycode.unwrap() {
|
||||
Keycode::Backspace => {
|
||||
self.tasks.push(UpdateResult::DeleteFront);
|
||||
}
|
||||
@ -276,21 +283,17 @@ impl Application {
|
||||
Keycode::Down => {
|
||||
self.tasks.push(UpdateResult::MoveCaretDown);
|
||||
}
|
||||
Keycode::O => {
|
||||
if left_control_pressed && !shift_pressed {
|
||||
self.tasks.push(UpdateResult::OpenFileModal);
|
||||
}
|
||||
Keycode::O if left_control_pressed && !shift_pressed => {
|
||||
self.tasks.push(UpdateResult::OpenFileModal)
|
||||
}
|
||||
_ => {}
|
||||
};
|
||||
}
|
||||
},
|
||||
Event::TextInput { text, .. } => {
|
||||
self.tasks.push(UpdateResult::Input(text));
|
||||
}
|
||||
Event::MouseWheel {
|
||||
direction, x, y, ..
|
||||
} => {
|
||||
match direction {
|
||||
} => match direction {
|
||||
MouseWheelDirection::Normal => {
|
||||
self.tasks.push(UpdateResult::Scroll { x, y });
|
||||
}
|
||||
@ -300,21 +303,22 @@ impl Application {
|
||||
_ => {
|
||||
// ignore
|
||||
}
|
||||
};
|
||||
}
|
||||
},
|
||||
Event::Window {
|
||||
win_event: WindowEvent::Resized(w, h),
|
||||
..
|
||||
} => {
|
||||
self.tasks.push(UpdateResult::WindowResize {
|
||||
} => self.tasks.push(UpdateResult::WindowResize {
|
||||
width: w,
|
||||
height: h,
|
||||
});
|
||||
}
|
||||
}),
|
||||
_ => {}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
pub fn current_working_directory() -> String {
|
||||
env::current_dir().unwrap().to_str().unwrap().to_string()
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
|
@ -26,16 +26,18 @@ pub fn move_caret_left(file_editor: &mut FileEditor) {
|
||||
if file_editor.caret().text_position() == 0 {
|
||||
return;
|
||||
}
|
||||
let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() - 1) {
|
||||
let text_character: TextCharacter =
|
||||
match file.get_character_at(file_editor.caret().text_position() - 1) {
|
||||
Some(text_character) => text_character,
|
||||
None => return, // EOF
|
||||
};
|
||||
let pos = file_editor.caret().position();
|
||||
let d = c.dest().clone();
|
||||
let character_destination = text_character.dest().clone();
|
||||
let p = pos.moved(-1, 0, 0);
|
||||
file_editor
|
||||
.caret_mut()
|
||||
.move_caret(p, Point::new(d.x(), d.y()));
|
||||
file_editor.caret_mut().move_caret(
|
||||
p,
|
||||
Point::new(character_destination.x(), character_destination.y()),
|
||||
);
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
@ -77,6 +79,11 @@ mod test_move_right {
|
||||
) -> Result<Rc<Texture>, String> {
|
||||
Err("skip render character".to_owned())
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for RendererMock {
|
||||
@ -163,6 +170,11 @@ mod test_move_left {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn load_text_tex(
|
||||
&mut self,
|
||||
_details: &mut TextDetails,
|
||||
|
@ -15,12 +15,10 @@ 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() {
|
||||
file
|
||||
} else {
|
||||
return;
|
||||
}
|
||||
.buffer();
|
||||
let mut buffer: String = match file_editor.file() {
|
||||
Some(file) => file.buffer(),
|
||||
_ => return,
|
||||
};
|
||||
let position: CaretPosition = file_editor.caret().position().clone();
|
||||
if position.text_position() == 0 {
|
||||
return;
|
||||
@ -28,20 +26,16 @@ where
|
||||
let c: char = buffer.chars().collect::<Vec<char>>()[position.text_position() - 1];
|
||||
buffer.remove(position.text_position() - 1);
|
||||
let position = match c {
|
||||
'\n' if position.text_position() > 0 && position.line_number() > 0 => {
|
||||
position.moved(-1, -1, 0)
|
||||
}
|
||||
'\n' if !position.is_first() => position.moved(-1, -1, 0),
|
||||
'\n' => position.clone(),
|
||||
_ if position.text_position() > 0 => position.moved(-1, 0, 0),
|
||||
_ => position.moved(0, 0, 0),
|
||||
};
|
||||
let move_to = file_editor
|
||||
.file()
|
||||
.and_then(|f| f.get_character_at(file_editor.caret().text_position()))
|
||||
.and_then(|character| {
|
||||
let dest: Rect = character.dest();
|
||||
Some((position, Point::new(dest.x(), dest.y())))
|
||||
});
|
||||
.and_then(|f| f.get_character_at(position.text_position()))
|
||||
.map(|character| character.dest())
|
||||
.map(|dest| (position, Point::new(dest.x(), dest.y())));
|
||||
match move_to {
|
||||
Some((position, point)) => file_editor.caret_mut().move_caret(position, point),
|
||||
None => file_editor.caret_mut().reset_caret(),
|
||||
@ -60,10 +54,9 @@ 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() {
|
||||
file
|
||||
} else {
|
||||
return;
|
||||
let file: &EditorFile = match file_editor.file() {
|
||||
Some(file) => file,
|
||||
None => return,
|
||||
};
|
||||
let mut buffer: String = file.buffer();
|
||||
let position: usize = file_editor.caret().text_position();
|
||||
@ -178,6 +171,11 @@ mod tests {
|
||||
) -> Result<Rc<Texture>, String> {
|
||||
Err("skip render character".to_owned())
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||
unimplemented!()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for RendererMock {
|
||||
|
@ -7,3 +7,7 @@ pub use crate::app::app_state::*;
|
||||
pub use crate::app::application::*;
|
||||
pub use crate::app::caret_manager::*;
|
||||
pub use crate::app::file_content_manager::*;
|
||||
|
||||
pub trait Resize {
|
||||
fn resize_element(&mut self);
|
||||
}
|
||||
|
@ -18,6 +18,8 @@ pub trait Renderer {
|
||||
details: &mut TextDetails,
|
||||
font_details: FontDetails,
|
||||
) -> Result<Rc<Texture>, String>;
|
||||
|
||||
fn load_image(&mut self, path: String) -> Result<Rc<Texture>, String>;
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
@ -117,4 +119,8 @@ impl<'l> Renderer for CanvasRenderer<'l> {
|
||||
let tex_manager = self.texture_manager();
|
||||
tex_manager.load_text(details, font)
|
||||
}
|
||||
|
||||
fn load_image(&mut self, path: String) -> Result<Rc<Texture>, String> {
|
||||
self.texture_manager.load(path.as_str())
|
||||
}
|
||||
}
|
||||
|
@ -1,11 +1,159 @@
|
||||
#[cfg(test)]
|
||||
pub mod support {
|
||||
use crate::renderer::managers::FontDetails;
|
||||
use crate::renderer::managers::TextDetails;
|
||||
use crate::renderer::renderer::Renderer;
|
||||
use crate::ui::text_character::CharacterSizeManager;
|
||||
use crate::ui::CanvasAccess;
|
||||
use rider_config::Config;
|
||||
use rider_config::ConfigAccess;
|
||||
use rider_config::ConfigHolder;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::Point;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::Texture;
|
||||
use sdl2::ttf::Font;
|
||||
use std::fmt::Debug;
|
||||
use std::fmt::Error;
|
||||
use std::fmt::Formatter;
|
||||
use std::rc::Rc;
|
||||
use std::sync::*;
|
||||
|
||||
pub fn build_path(path: String) {
|
||||
use std::fs;
|
||||
|
||||
fs::create_dir_all(path.as_str()).unwrap();
|
||||
fs::write((path.clone() + &"/file1".to_owned()).as_str(), "foo").unwrap();
|
||||
fs::write((path.clone() + &"/file2".to_owned()).as_str(), "bar").unwrap();
|
||||
fs::create_dir_all((path.clone() + &"/dir1".to_owned()).as_str()).unwrap();
|
||||
fs::create_dir_all((path.clone() + &"/dir2".to_owned()).as_str()).unwrap();
|
||||
}
|
||||
|
||||
pub fn build_config() -> Arc<RwLock<Config>> {
|
||||
let mut config = Config::new();
|
||||
config.set_theme(config.editor_config().current_theme().clone());
|
||||
Arc::new(RwLock::new(config))
|
||||
}
|
||||
|
||||
#[derive(Debug, PartialEq)]
|
||||
pub struct RendererRect {
|
||||
pub rect: Rect,
|
||||
pub color: Color,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
pub 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)]
|
||||
pub struct SimpleRendererMock {
|
||||
config: ConfigAccess,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl SimpleRendererMock {
|
||||
pub fn new(config: ConfigAccess) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Renderer for SimpleRendererMock {
|
||||
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 text texture".to_owned())
|
||||
}
|
||||
|
||||
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||
Err("skip img texture".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl CharacterSizeManager for SimpleRendererMock {
|
||||
fn load_character_size(&mut self, _c: char) -> Rect {
|
||||
Rect::new(0, 0, 13, 14)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl ConfigHolder for SimpleRendererMock {
|
||||
fn config(&self) -> &Arc<RwLock<Config>> {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
}
|
||||
|
@ -99,7 +99,7 @@ impl Caret {
|
||||
impl Caret {
|
||||
pub fn update(&mut self) -> UR {
|
||||
self.blink_delay += 1;
|
||||
if self.blink_delay >= 30 {
|
||||
if self.blink_delay >= 15 {
|
||||
self.blink_delay = 0;
|
||||
self.toggle_state();
|
||||
}
|
||||
|
@ -14,32 +14,39 @@ impl CaretPosition {
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn text_position(&self) -> usize {
|
||||
self.text_position
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn line_number(&self) -> usize {
|
||||
self.line_number
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn line_position(&self) -> usize {
|
||||
self.line_position
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn reset(&mut self) {
|
||||
self.text_position = 0;
|
||||
self.line_number = 0;
|
||||
self.line_position = 0;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_text_position(&mut self, n: usize) {
|
||||
self.text_position = n;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_line_number(&mut self, n: usize) {
|
||||
self.line_number = n;
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn set_line_position(&mut self, n: usize) {
|
||||
self.line_position = n;
|
||||
}
|
||||
@ -51,6 +58,11 @@ impl CaretPosition {
|
||||
line_position: (self.line_position as i32 + line_position) as usize,
|
||||
}
|
||||
}
|
||||
|
||||
#[inline]
|
||||
pub fn is_first(&self) -> bool {
|
||||
self.line_number == 0 && self.text_position == 0
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
|
@ -200,6 +200,7 @@ impl ClickHandler for EditorFileToken {
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::support::build_config;
|
||||
use crate::tests::support::CanvasMock;
|
||||
use rider_lexers::Token;
|
||||
use sdl2::pixels::PixelFormatEnum;
|
||||
use sdl2::render::Texture;
|
||||
@ -207,9 +208,6 @@ mod tests {
|
||||
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};
|
||||
|
||||
@ -217,83 +215,6 @@ mod tests {
|
||||
// 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>>,
|
||||
@ -317,10 +238,16 @@ mod tests {
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl<'l> Renderer for RendererMock<'l> {
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn load_font(&mut self, _details: FontDetails) -> Rc<Font> {
|
||||
unimplemented!("load_font")
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||
unimplemented!()
|
||||
}
|
||||
|
||||
fn load_text_tex(
|
||||
&mut self,
|
||||
_details: &mut TextDetails,
|
||||
@ -367,6 +294,7 @@ mod tests {
|
||||
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();
|
||||
@ -378,6 +306,7 @@ mod tests {
|
||||
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();
|
||||
@ -389,6 +318,7 @@ mod tests {
|
||||
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();
|
||||
@ -400,6 +330,7 @@ mod tests {
|
||||
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();
|
||||
@ -411,6 +342,7 @@ mod tests {
|
||||
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();
|
||||
@ -422,6 +354,7 @@ mod tests {
|
||||
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();
|
||||
@ -433,6 +366,7 @@ mod tests {
|
||||
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();
|
||||
|
@ -1,432 +0,0 @@
|
||||
use crate::app::UpdateResult as UR;
|
||||
use crate::app::*;
|
||||
use crate::renderer::renderer::Renderer;
|
||||
use crate::ui::scroll_bar::horizontal_scroll_bar::*;
|
||||
use crate::ui::scroll_bar::vertical_scroll_bar::*;
|
||||
use crate::ui::scroll_bar::Scrollable;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::mem;
|
||||
use std::sync::*;
|
||||
|
||||
pub struct FileEditor {
|
||||
dest: Rect,
|
||||
full_rect: Rect,
|
||||
caret: Caret,
|
||||
file: Option<EditorFile>,
|
||||
config: ConfigAccess,
|
||||
vertical_scroll_bar: VerticalScrollBar,
|
||||
horizontal_scroll_bar: HorizontalScrollBar,
|
||||
}
|
||||
|
||||
impl FileEditor {
|
||||
pub fn new(config: ConfigAccess) -> Self {
|
||||
let dest = {
|
||||
let c = config.read().unwrap();
|
||||
Rect::new(
|
||||
c.editor_left_margin(),
|
||||
c.editor_top_margin(),
|
||||
c.width() - c.editor_left_margin() as u32,
|
||||
c.height() - c.editor_top_margin() as u32,
|
||||
)
|
||||
};
|
||||
Self {
|
||||
dest,
|
||||
full_rect: Rect::new(0, 0, 0, 0),
|
||||
caret: Caret::new(Arc::clone(&config)),
|
||||
vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)),
|
||||
horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)),
|
||||
file: None,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_front<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||
{
|
||||
file_content_manager::delete_front(self, renderer);
|
||||
}
|
||||
|
||||
pub fn delete_back<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||
{
|
||||
file_content_manager::delete_back(self, 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);
|
||||
}
|
||||
|
||||
pub fn insert_new_line<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||
{
|
||||
file_content_manager::insert_new_line(self, renderer);
|
||||
}
|
||||
|
||||
fn is_text_character_clicked(&self, point: &Point) -> bool {
|
||||
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||
self.file()
|
||||
.map_or(false, |file| file.is_left_click_target(point, &context))
|
||||
}
|
||||
|
||||
fn is_editor_clicked(&self, point: &Point) -> bool {
|
||||
self.dest
|
||||
.contains_point(move_render_point(point.clone(), &self.dest).top_left())
|
||||
}
|
||||
|
||||
fn resolve_line_from_point(&self, point: &Point) -> i32 {
|
||||
let file = match self.file() {
|
||||
Some(f) => f,
|
||||
_ => return 0,
|
||||
};
|
||||
let mut y = point.y() - self.render_start_point().y();
|
||||
if y < 0 {
|
||||
y = 0;
|
||||
}
|
||||
y / (file.line_height() as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableView for FileEditor {
|
||||
fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
let read_config = self.config.read().unwrap();
|
||||
|
||||
let value_x = read_config.scroll().speed() * x;
|
||||
let value_y = read_config.scroll().speed() * y;
|
||||
let old_x = self.horizontal_scroll_bar.scroll_value();
|
||||
let old_y = self.vertical_scroll_bar.scroll_value();
|
||||
|
||||
if value_x + old_x >= 0 {
|
||||
self.horizontal_scroll_bar.scroll_to(value_x + old_x);
|
||||
if self.horizontal_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.horizontal_scroll_bar.scroll_to(old_x);
|
||||
}
|
||||
}
|
||||
if value_y + old_y >= 0 {
|
||||
self.vertical_scroll_bar.scroll_to(value_y + old_y);
|
||||
if self.vertical_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.vertical_scroll_bar.scroll_to(old_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll(&self) -> Point {
|
||||
Point::new(
|
||||
-self.horizontal_scroll_bar.scroll_value(),
|
||||
-self.vertical_scroll_bar.scroll_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FileAccess for FileEditor {
|
||||
fn has_file(&self) -> bool {
|
||||
self.file.is_some()
|
||||
}
|
||||
|
||||
fn file(&self) -> Option<&EditorFile> {
|
||||
self.file.as_ref()
|
||||
}
|
||||
|
||||
fn file_mut(&mut self) -> Option<&mut EditorFile> {
|
||||
self.file.as_mut()
|
||||
}
|
||||
|
||||
fn open_file(&mut self, file: EditorFile) -> Option<EditorFile> {
|
||||
let mut file = Some(file);
|
||||
mem::swap(&mut self.file, &mut file);
|
||||
if let Some(f) = self.file.as_ref() {
|
||||
self.full_rect = f.full_rect();
|
||||
}
|
||||
self.vertical_scroll_bar.set_location(0);
|
||||
self.horizontal_scroll_bar.set_location(0);
|
||||
file
|
||||
}
|
||||
|
||||
fn drop_file(&mut self) -> Option<EditorFile> {
|
||||
if self.has_file() {
|
||||
let mut file = None;
|
||||
mem::swap(&mut self.file, &mut file);
|
||||
file
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_current_file(&mut self, file: EditorFile) {
|
||||
self.open_file(file);
|
||||
}
|
||||
}
|
||||
|
||||
impl CaretAccess for FileEditor {
|
||||
fn caret(&self) -> &Caret {
|
||||
&self.caret
|
||||
}
|
||||
|
||||
fn caret_mut(&mut self) -> &mut Caret {
|
||||
&mut self.caret
|
||||
}
|
||||
|
||||
fn move_caret(&mut self, dir: MoveDirection) {
|
||||
match dir {
|
||||
MoveDirection::Left => caret_manager::move_caret_left(self),
|
||||
MoveDirection::Right => caret_manager::move_caret_right(self),
|
||||
MoveDirection::Up => {}
|
||||
MoveDirection::Down => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_caret_to_end_of_line(&mut self, line: i32) {
|
||||
let file = match self.file_mut() {
|
||||
Some(f) => f,
|
||||
_ => return,
|
||||
};
|
||||
let mut line = line;
|
||||
while line >= 0 {
|
||||
match file.get_last_at_line(line.clone() as usize) {
|
||||
Some(text_character) => {
|
||||
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()
|
||||
} else {
|
||||
rect.top_right()
|
||||
};
|
||||
self.caret.move_caret(position, p);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
line -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileEditor {
|
||||
pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + ConfigHolder,
|
||||
C: CanvasAccess,
|
||||
{
|
||||
canvas.set_clipping(self.dest.clone());
|
||||
match self.file() {
|
||||
Some(file) => file.render(
|
||||
canvas,
|
||||
renderer,
|
||||
&RenderContext::RelativePosition(self.render_start_point()),
|
||||
),
|
||||
_ => (),
|
||||
};
|
||||
self.caret.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.render_start_point()),
|
||||
);
|
||||
self.vertical_scroll_bar.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
self.horizontal_scroll_bar.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn prepare_ui<T>(&mut self, renderer: &mut T)
|
||||
where
|
||||
T: CharacterSizeManager,
|
||||
{
|
||||
self.caret.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for FileEditor {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||
let (width, height, editor_left_margin, editor_top_margin, scroll_width, scroll_margin) = {
|
||||
let config: RwLockReadGuard<Config> = self.config.read().unwrap();
|
||||
(
|
||||
config.width(),
|
||||
config.height(),
|
||||
config.editor_left_margin() as u32,
|
||||
config.editor_top_margin() as u32,
|
||||
config.scroll().width(),
|
||||
config.scroll().margin_right(),
|
||||
)
|
||||
};
|
||||
self.dest.set_width(width - editor_left_margin);
|
||||
self.dest.set_height(height - editor_top_margin);
|
||||
|
||||
self.vertical_scroll_bar
|
||||
.set_full_size(self.full_rect.height());
|
||||
self.vertical_scroll_bar.set_viewport(self.dest.height());
|
||||
self.vertical_scroll_bar
|
||||
.set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin));
|
||||
self.vertical_scroll_bar.update(ticks, context);
|
||||
|
||||
self.horizontal_scroll_bar
|
||||
.set_full_size(self.full_rect.width());
|
||||
self.horizontal_scroll_bar.set_viewport(self.dest.width());
|
||||
self.horizontal_scroll_bar
|
||||
.set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin));
|
||||
self.horizontal_scroll_bar.update(ticks, context);
|
||||
|
||||
self.caret.update();
|
||||
match self.file_mut() {
|
||||
Some(file) => file.update(ticks, context),
|
||||
_ => UR::NoOp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for FileEditor {
|
||||
fn on_left_click(&mut self, point: &Point, _context: &UpdateContext) -> UR {
|
||||
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||
|
||||
if self.is_text_character_clicked(point) {
|
||||
let file = if let Some(file) = self.file_mut() {
|
||||
file
|
||||
} else {
|
||||
return UR::NoOp;
|
||||
};
|
||||
match file.on_left_click(point, &context) {
|
||||
UR::MoveCaret(rect, position) => {
|
||||
self.caret
|
||||
.move_caret(position, Point::new(rect.x(), rect.y()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
self.set_caret_to_end_of_line(self.resolve_line_from_point(point));
|
||||
}
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, _context: &UpdateContext) -> bool {
|
||||
self.is_text_character_clicked(point) || self.is_editor_clicked(point)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for FileEditor {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left() + self.scroll()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for FileEditor {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ui::*;
|
||||
use rider_config::Config;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
fn replace_file() {
|
||||
let config = Arc::new(RwLock::new(Config::new()));
|
||||
let mut editor = FileEditor::new(Arc::clone(&config));
|
||||
let first_file =
|
||||
EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone());
|
||||
let second_file =
|
||||
EditorFile::new("./bar.txt".to_string(), "bar".to_string(), config.clone());
|
||||
editor.open_file(first_file.clone());
|
||||
let result = editor.open_file(second_file.clone());
|
||||
assert_eq!(result.is_some(), true);
|
||||
let file = result.as_ref().unwrap();
|
||||
assert_eq!(file.path(), first_file.path());
|
||||
assert_eq!(file.buffer(), first_file.buffer());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_config_holder {
|
||||
use crate::app::*;
|
||||
use crate::tests::support;
|
||||
use crate::ui::*;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
fn assert_config() {
|
||||
let config = support::build_config();
|
||||
let widget = FileEditor::new(Arc::clone(&config));
|
||||
let result = widget.config();
|
||||
{
|
||||
let mut w = config.write().unwrap();
|
||||
w.set_height(1240);
|
||||
w.set_width(1024);
|
||||
}
|
||||
let local = config.read().unwrap();
|
||||
let widget_config = result.read().unwrap();
|
||||
assert_eq!(widget_config.width(), local.width());
|
||||
assert_eq!(widget_config.height(), local.height());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_render_box {
|
||||
use crate::tests::support;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
|
||||
impl FileEditor {
|
||||
pub fn set_full_rect(&mut self, r: Rect) {
|
||||
self.full_rect = r;
|
||||
}
|
||||
|
||||
pub fn set_dest(&mut self, r: Rect) {
|
||||
self.dest = r;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_dest() {
|
||||
let config = support::build_config();
|
||||
let (x, y, mw, mh) = {
|
||||
let c = config.read().unwrap();
|
||||
(
|
||||
c.editor_left_margin(),
|
||||
c.editor_top_margin(),
|
||||
c.width(),
|
||||
c.height(),
|
||||
)
|
||||
};
|
||||
let widget = FileEditor::new(config);
|
||||
let result = widget.dest().clone();
|
||||
let expected = Rect::new(x, y, mw - x as u32, mh - y as u32);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_render_start_point() {
|
||||
let config = support::build_config();
|
||||
let (x, y, ss) = {
|
||||
let c = config.read().unwrap();
|
||||
(
|
||||
c.editor_left_margin(),
|
||||
c.editor_top_margin(),
|
||||
c.scroll().speed(),
|
||||
)
|
||||
};
|
||||
let mut widget = FileEditor::new(config);
|
||||
widget.set_dest(Rect::new(x.clone(), y.clone(), 999, 999));
|
||||
widget.set_full_rect(Rect::new(0, 0, 99999, 99999));
|
||||
widget.update(1, &UpdateContext::Nothing);
|
||||
widget.scroll_by(30, 40);
|
||||
let result = widget.render_start_point().clone();
|
||||
let expected = Point::new(x - (ss * 30), y - (ss * 40));
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
@ -1,8 +1,27 @@
|
||||
pub use crate::ui::file_editor::file_editor::*;
|
||||
use crate::ui::*;
|
||||
use crate::app::UpdateResult as UR;
|
||||
use crate::app::*;
|
||||
use crate::renderer::renderer::Renderer;
|
||||
use crate::ui::caret::caret::Caret;
|
||||
use crate::ui::caret::caret_position::CaretPosition;
|
||||
use crate::ui::caret::MoveDirection;
|
||||
use crate::ui::file::editor_file::EditorFile;
|
||||
use crate::ui::file::TextCollection;
|
||||
use crate::ui::file::TextWidget;
|
||||
use crate::ui::move_render_point;
|
||||
use crate::ui::scroll_bar::horizontal_scroll_bar::*;
|
||||
use crate::ui::scroll_bar::vertical_scroll_bar::*;
|
||||
use crate::ui::scroll_bar::Scrollable;
|
||||
use crate::ui::text_character::CharacterSizeManager;
|
||||
use crate::ui::CanvasAccess;
|
||||
use crate::ui::ClickHandler;
|
||||
use crate::ui::RenderBox;
|
||||
use crate::ui::RenderContext;
|
||||
use crate::ui::Update;
|
||||
use crate::ui::UpdateContext;
|
||||
use sdl2::rect::Point;
|
||||
|
||||
pub mod file_editor;
|
||||
use sdl2::rect::Rect;
|
||||
use std::mem;
|
||||
use std::sync::*;
|
||||
|
||||
pub trait FileAccess {
|
||||
fn has_file(&self) -> bool;
|
||||
@ -33,3 +52,430 @@ pub trait ScrollableView {
|
||||
|
||||
fn scroll(&self) -> Point;
|
||||
}
|
||||
|
||||
pub struct FileEditor {
|
||||
dest: Rect,
|
||||
full_rect: Rect,
|
||||
caret: Caret,
|
||||
file: Option<EditorFile>,
|
||||
config: ConfigAccess,
|
||||
vertical_scroll_bar: VerticalScrollBar,
|
||||
horizontal_scroll_bar: HorizontalScrollBar,
|
||||
}
|
||||
|
||||
impl FileEditor {
|
||||
pub fn new(config: ConfigAccess) -> Self {
|
||||
let dest = {
|
||||
let c = config.read().unwrap();
|
||||
Rect::new(
|
||||
c.editor_left_margin(),
|
||||
c.editor_top_margin(),
|
||||
c.width() - c.editor_left_margin() as u32,
|
||||
c.height() - c.editor_top_margin() as u32,
|
||||
)
|
||||
};
|
||||
Self {
|
||||
dest,
|
||||
full_rect: Rect::new(0, 0, 0, 0),
|
||||
caret: Caret::new(Arc::clone(&config)),
|
||||
vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)),
|
||||
horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)),
|
||||
file: None,
|
||||
config,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn delete_front<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||
{
|
||||
file_content_manager::delete_front(self, renderer);
|
||||
}
|
||||
|
||||
pub fn delete_back<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||
{
|
||||
file_content_manager::delete_back(self, 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);
|
||||
}
|
||||
|
||||
pub fn insert_new_line<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||
{
|
||||
file_content_manager::insert_new_line(self, renderer);
|
||||
}
|
||||
|
||||
fn is_text_character_clicked(&self, point: &Point) -> bool {
|
||||
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||
self.file()
|
||||
.map_or(false, |file| file.is_left_click_target(point, &context))
|
||||
}
|
||||
|
||||
fn is_editor_clicked(&self, point: &Point) -> bool {
|
||||
self.dest
|
||||
.contains_point(move_render_point(point.clone(), &self.dest).top_left())
|
||||
}
|
||||
|
||||
fn resolve_line_from_point(&self, point: &Point) -> i32 {
|
||||
let file = match self.file() {
|
||||
Some(f) => f,
|
||||
_ => return 0,
|
||||
};
|
||||
let mut y = point.y() - self.render_start_point().y();
|
||||
if y < 0 {
|
||||
y = 0;
|
||||
}
|
||||
y / (file.line_height() as i32)
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableView for FileEditor {
|
||||
fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
let read_config = self.config.read().unwrap();
|
||||
|
||||
let value_x = read_config.scroll().speed() * x;
|
||||
let value_y = read_config.scroll().speed() * y;
|
||||
let old_x = self.horizontal_scroll_bar.scroll_value();
|
||||
let old_y = self.vertical_scroll_bar.scroll_value();
|
||||
|
||||
if value_x + old_x >= 0 {
|
||||
self.horizontal_scroll_bar.scroll_to(value_x + old_x);
|
||||
if self.horizontal_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.horizontal_scroll_bar.scroll_to(old_x);
|
||||
}
|
||||
}
|
||||
if value_y + old_y >= 0 {
|
||||
self.vertical_scroll_bar.scroll_to(value_y + old_y);
|
||||
if self.vertical_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.vertical_scroll_bar.scroll_to(old_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll(&self) -> Point {
|
||||
Point::new(
|
||||
-self.horizontal_scroll_bar.scroll_value(),
|
||||
-self.vertical_scroll_bar.scroll_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl FileAccess for FileEditor {
|
||||
fn has_file(&self) -> bool {
|
||||
self.file.is_some()
|
||||
}
|
||||
|
||||
fn file(&self) -> Option<&EditorFile> {
|
||||
self.file.as_ref()
|
||||
}
|
||||
|
||||
fn file_mut(&mut self) -> Option<&mut EditorFile> {
|
||||
self.file.as_mut()
|
||||
}
|
||||
|
||||
fn open_file(&mut self, file: EditorFile) -> Option<EditorFile> {
|
||||
let mut file = Some(file);
|
||||
mem::swap(&mut self.file, &mut file);
|
||||
if let Some(f) = self.file.as_ref() {
|
||||
self.full_rect = f.full_rect();
|
||||
}
|
||||
self.vertical_scroll_bar.set_location(0);
|
||||
self.horizontal_scroll_bar.set_location(0);
|
||||
file
|
||||
}
|
||||
|
||||
fn drop_file(&mut self) -> Option<EditorFile> {
|
||||
if self.has_file() {
|
||||
let mut file = None;
|
||||
mem::swap(&mut self.file, &mut file);
|
||||
file
|
||||
} else {
|
||||
None
|
||||
}
|
||||
}
|
||||
|
||||
fn replace_current_file(&mut self, file: EditorFile) {
|
||||
self.open_file(file);
|
||||
}
|
||||
}
|
||||
|
||||
impl CaretAccess for FileEditor {
|
||||
fn caret(&self) -> &Caret {
|
||||
&self.caret
|
||||
}
|
||||
|
||||
fn caret_mut(&mut self) -> &mut Caret {
|
||||
&mut self.caret
|
||||
}
|
||||
|
||||
fn move_caret(&mut self, dir: MoveDirection) {
|
||||
match dir {
|
||||
MoveDirection::Left => caret_manager::move_caret_left(self),
|
||||
MoveDirection::Right => caret_manager::move_caret_right(self),
|
||||
MoveDirection::Up => {}
|
||||
MoveDirection::Down => {}
|
||||
}
|
||||
}
|
||||
|
||||
fn set_caret_to_end_of_line(&mut self, line: i32) {
|
||||
let file = match self.file_mut() {
|
||||
Some(f) => f,
|
||||
_ => return,
|
||||
};
|
||||
let mut line = line;
|
||||
while line >= 0 {
|
||||
match file.get_last_at_line(line.clone() as usize) {
|
||||
Some(text_character) => {
|
||||
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()
|
||||
} else {
|
||||
rect.top_right()
|
||||
};
|
||||
self.caret.move_caret(position, p);
|
||||
break;
|
||||
}
|
||||
_ => {
|
||||
line -= 1;
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl FileEditor {
|
||||
pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + ConfigHolder,
|
||||
C: CanvasAccess,
|
||||
{
|
||||
canvas.set_clipping(self.dest.clone());
|
||||
match self.file() {
|
||||
Some(file) => file.render(
|
||||
canvas,
|
||||
renderer,
|
||||
&RenderContext::RelativePosition(self.render_start_point()),
|
||||
),
|
||||
_ => (),
|
||||
};
|
||||
self.caret.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.render_start_point()),
|
||||
);
|
||||
self.vertical_scroll_bar.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
self.horizontal_scroll_bar.render(
|
||||
canvas,
|
||||
&RenderContext::RelativePosition(self.dest.top_left()),
|
||||
);
|
||||
}
|
||||
|
||||
pub fn prepare_ui<T>(&mut self, renderer: &mut T)
|
||||
where
|
||||
T: CharacterSizeManager,
|
||||
{
|
||||
self.caret.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for FileEditor {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR {
|
||||
let (width, height, editor_left_margin, editor_top_margin, scroll_width, scroll_margin) = {
|
||||
let config: RwLockReadGuard<Config> = self.config.read().unwrap();
|
||||
(
|
||||
config.width(),
|
||||
config.height(),
|
||||
config.editor_left_margin() as u32,
|
||||
config.editor_top_margin() as u32,
|
||||
config.scroll().width(),
|
||||
config.scroll().margin_right(),
|
||||
)
|
||||
};
|
||||
let editor_left_margin = match context {
|
||||
UpdateContext::ParentPosition(p) => p.x() as u32,
|
||||
_ => editor_left_margin as u32,
|
||||
};
|
||||
self.dest.set_x(editor_left_margin.clone() as i32);
|
||||
self.dest.set_width(width - editor_left_margin);
|
||||
self.dest.set_height(height - editor_top_margin);
|
||||
|
||||
self.vertical_scroll_bar
|
||||
.set_full_size(self.full_rect.height());
|
||||
self.vertical_scroll_bar.set_viewport(self.dest.height());
|
||||
self.vertical_scroll_bar
|
||||
.set_location(self.dest.width() as i32 - (scroll_width as i32 + scroll_margin));
|
||||
self.vertical_scroll_bar.update(ticks, context);
|
||||
|
||||
self.horizontal_scroll_bar
|
||||
.set_full_size(self.full_rect.width());
|
||||
self.horizontal_scroll_bar.set_viewport(self.dest.width());
|
||||
self.horizontal_scroll_bar
|
||||
.set_location(self.dest.height() as i32 - (scroll_width as i32 + scroll_margin));
|
||||
self.horizontal_scroll_bar.update(ticks, context);
|
||||
|
||||
self.caret.update();
|
||||
match self.file_mut() {
|
||||
Some(file) => file.update(ticks, context),
|
||||
_ => UR::NoOp,
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for FileEditor {
|
||||
fn on_left_click(&mut self, point: &Point, _context: &UpdateContext) -> UR {
|
||||
let context = UpdateContext::ParentPosition(self.render_start_point());
|
||||
|
||||
if self.is_text_character_clicked(point) {
|
||||
let file = if let Some(file) = self.file_mut() {
|
||||
file
|
||||
} else {
|
||||
return UR::NoOp;
|
||||
};
|
||||
match file.on_left_click(point, &context) {
|
||||
UR::MoveCaret(rect, position) => {
|
||||
self.caret
|
||||
.move_caret(position, Point::new(rect.x(), rect.y()));
|
||||
}
|
||||
_ => {}
|
||||
}
|
||||
} else {
|
||||
self.set_caret_to_end_of_line(self.resolve_line_from_point(point));
|
||||
}
|
||||
UR::NoOp
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, _context: &UpdateContext) -> bool {
|
||||
self.is_text_character_clicked(point) || self.is_editor_clicked(point)
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for FileEditor {
|
||||
fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left() + self.scroll()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for FileEditor {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::ui::*;
|
||||
use rider_config::Config;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
fn replace_file() {
|
||||
let config = Arc::new(RwLock::new(Config::new()));
|
||||
let mut editor = FileEditor::new(Arc::clone(&config));
|
||||
let first_file =
|
||||
EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone());
|
||||
let second_file =
|
||||
EditorFile::new("./bar.txt".to_string(), "bar".to_string(), config.clone());
|
||||
editor.open_file(first_file.clone());
|
||||
let result = editor.open_file(second_file.clone());
|
||||
assert_eq!(result.is_some(), true);
|
||||
let file = result.as_ref().unwrap();
|
||||
assert_eq!(file.path(), first_file.path());
|
||||
assert_eq!(file.buffer(), first_file.buffer());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_config_holder {
|
||||
use crate::app::*;
|
||||
use crate::tests::support;
|
||||
use crate::ui::*;
|
||||
use std::sync::*;
|
||||
|
||||
#[test]
|
||||
fn assert_config() {
|
||||
let config = support::build_config();
|
||||
let widget = FileEditor::new(Arc::clone(&config));
|
||||
let result = widget.config();
|
||||
{
|
||||
let mut w = config.write().unwrap();
|
||||
w.set_height(1240);
|
||||
w.set_width(1024);
|
||||
}
|
||||
let local = config.read().unwrap();
|
||||
let widget_config = result.read().unwrap();
|
||||
assert_eq!(widget_config.width(), local.width());
|
||||
assert_eq!(widget_config.height(), local.height());
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod test_render_box {
|
||||
use crate::tests::support;
|
||||
use crate::ui::*;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
|
||||
impl FileEditor {
|
||||
pub fn set_full_rect(&mut self, r: Rect) {
|
||||
self.full_rect = r;
|
||||
}
|
||||
|
||||
pub fn set_dest(&mut self, r: Rect) {
|
||||
self.dest = r;
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_dest() {
|
||||
let config = support::build_config();
|
||||
let (x, y, mw, mh) = {
|
||||
let c = config.read().unwrap();
|
||||
(
|
||||
c.editor_left_margin(),
|
||||
c.editor_top_margin(),
|
||||
c.width(),
|
||||
c.height(),
|
||||
)
|
||||
};
|
||||
let widget = FileEditor::new(config);
|
||||
let result = widget.dest().clone();
|
||||
let expected = Rect::new(x, y, mw - x as u32, mh - y as u32);
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_render_start_point() {
|
||||
let config = support::build_config();
|
||||
let (x, y, ss) = {
|
||||
let c = config.read().unwrap();
|
||||
(
|
||||
c.editor_left_margin(),
|
||||
c.editor_top_margin(),
|
||||
c.scroll().speed(),
|
||||
)
|
||||
};
|
||||
let mut widget = FileEditor::new(config);
|
||||
widget.set_dest(Rect::new(x.clone(), y.clone(), 999, 999));
|
||||
widget.set_full_rect(Rect::new(0, 0, 99999, 99999));
|
||||
widget.update(1, &UpdateContext::Nothing);
|
||||
widget.scroll_by(30, 40);
|
||||
let result = widget.render_start_point().clone();
|
||||
let expected = Point::new(x - (ss * 30), y - (ss * 40));
|
||||
assert_eq!(result, expected);
|
||||
}
|
||||
}
|
||||
|
@ -64,11 +64,23 @@ impl DirectoryView {
|
||||
}
|
||||
}
|
||||
|
||||
// pub fn dest(&self) -> Rect {
|
||||
// Rect::new(
|
||||
// self.pos.x(),
|
||||
// self.pos.y(),
|
||||
// self.icon_width,
|
||||
// self.icon_height,
|
||||
// )
|
||||
// }
|
||||
|
||||
pub fn source(&self) -> &Rect {
|
||||
&self.source
|
||||
}
|
||||
|
||||
pub fn open_directory(&mut self, dir_path: String, renderer: &mut CanvasRenderer) -> bool {
|
||||
pub fn open_directory<R>(&mut self, dir_path: String, renderer: &mut R) -> bool
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
match dir_path {
|
||||
_ if dir_path == self.path => {
|
||||
if !self.opened {
|
||||
@ -127,7 +139,10 @@ impl DirectoryView {
|
||||
}
|
||||
}
|
||||
|
||||
fn read_directory(&mut self, renderer: &mut CanvasRenderer) {
|
||||
fn read_directory<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
let entries: fs::ReadDir = match fs::read_dir(self.path.clone()) {
|
||||
Ok(d) => d,
|
||||
_ => return,
|
||||
@ -167,9 +182,10 @@ impl DirectoryView {
|
||||
self.directories.sort_by(|a, b| a.name().cmp(&b.name()));
|
||||
}
|
||||
|
||||
fn render_icon<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
|
||||
fn render_icon<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
C: CanvasAccess,
|
||||
R: Renderer,
|
||||
{
|
||||
let dir_texture_path = {
|
||||
let c = self.config.read().unwrap();
|
||||
@ -178,11 +194,7 @@ impl DirectoryView {
|
||||
themes_dir.push(path);
|
||||
themes_dir.to_str().unwrap().to_owned()
|
||||
};
|
||||
let texture = renderer
|
||||
.texture_manager()
|
||||
.load(dir_texture_path.as_str())
|
||||
.unwrap_or_else(|_| panic!("Failed to load directory entry texture"));
|
||||
|
||||
if let Ok(texture) = renderer.load_image(dir_texture_path.clone()) {
|
||||
canvas
|
||||
.render_image(
|
||||
texture,
|
||||
@ -191,15 +203,16 @@ impl DirectoryView {
|
||||
)
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
}
|
||||
}
|
||||
|
||||
fn render_name<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
|
||||
fn render_name<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
C: CanvasAccess,
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
let mut d = dest.clone();
|
||||
d.set_x(dest.x() + NAME_MARGIN);
|
||||
let font_details = build_font_details(self);
|
||||
let font = renderer.font_manager().load(&font_details).unwrap();
|
||||
let name = self.name();
|
||||
let config = self.config.read().unwrap();
|
||||
let text_color = config.theme().code_highlighting().title.color();
|
||||
@ -211,23 +224,24 @@ impl DirectoryView {
|
||||
text: c.to_string(),
|
||||
font: font_details.clone(),
|
||||
};
|
||||
let text_texture = renderer
|
||||
.texture_manager()
|
||||
.load_text(&mut text_details, font.clone())
|
||||
.unwrap();
|
||||
let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone());
|
||||
|
||||
if let Ok(texture) = maybe_texture {
|
||||
d.set_width(size.width());
|
||||
d.set_height(size.height());
|
||||
|
||||
canvas
|
||||
.render_image(text_texture, self.source.clone(), d.clone())
|
||||
.render_image(texture, self.source.clone(), d.clone())
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
d.set_x(d.x() + size.width() as i32);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn render_children<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
|
||||
fn render_children<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
C: CanvasAccess,
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
if !self.expanded {
|
||||
return;
|
||||
@ -249,7 +263,10 @@ impl DirectoryView {
|
||||
}
|
||||
}
|
||||
|
||||
fn calculate_size(&mut self, renderer: &mut CanvasRenderer) {
|
||||
fn calculate_size<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: CharacterSizeManager,
|
||||
{
|
||||
let size = renderer.load_character_size('W');
|
||||
self.height = size.height();
|
||||
self.icon_height = size.height();
|
||||
@ -277,19 +294,11 @@ impl DirectoryView {
|
||||
self.icon_height,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for DirectoryView {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl DirectoryView {
|
||||
pub fn render<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RenderContext)
|
||||
pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
R: Renderer + CharacterSizeManager,
|
||||
C: CanvasAccess,
|
||||
{
|
||||
let dest = self.dest();
|
||||
let move_point = match context {
|
||||
@ -297,12 +306,15 @@ impl DirectoryView {
|
||||
_ => Point::new(0, 0),
|
||||
};
|
||||
let mut dest = move_render_point(move_point, &dest);
|
||||
self.render_icon::<T>(canvas, renderer, &mut dest);
|
||||
self.render_name::<T>(canvas, renderer, &mut dest.clone());
|
||||
self.render_children::<T>(canvas, renderer, &mut dest);
|
||||
self.render_icon::<C, R>(canvas, renderer, &mut dest);
|
||||
self.render_name::<C, R>(canvas, renderer, &mut dest.clone());
|
||||
self.render_children::<C, R>(canvas, renderer, &mut dest);
|
||||
}
|
||||
|
||||
pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) {
|
||||
pub fn prepare_ui<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
if self.opened {
|
||||
for dir in self.directories.iter_mut() {
|
||||
dir.prepare_ui(renderer);
|
||||
@ -313,10 +325,8 @@ impl DirectoryView {
|
||||
}
|
||||
self.calculate_size(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for DirectoryView {
|
||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
pub fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||
if !path::Path::new(&self.path).exists() {
|
||||
return UpdateResult::RefreshFsTree;
|
||||
}
|
||||
@ -325,30 +335,17 @@ impl Update for DirectoryView {
|
||||
dir.update(ticks, context);
|
||||
}
|
||||
for file in self.files.iter_mut() {
|
||||
file.update(ticks, context);
|
||||
file.update();
|
||||
}
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for DirectoryView {
|
||||
fn render_start_point(&self) -> Point {
|
||||
pub fn render_start_point(&self) -> Point {
|
||||
self.pos.clone()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
Rect::new(
|
||||
self.pos.x(),
|
||||
self.pos.y(),
|
||||
self.icon_width,
|
||||
self.icon_height,
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for DirectoryView {
|
||||
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult {
|
||||
pub fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult {
|
||||
let dest = self.dest();
|
||||
let move_point = match context {
|
||||
&UpdateContext::ParentPosition(p) => p.clone(),
|
||||
@ -381,7 +378,7 @@ impl ClickHandler for DirectoryView {
|
||||
for file in self.files.iter_mut() {
|
||||
let context = UpdateContext::ParentPosition(p.clone());
|
||||
if file.is_left_click_target(&point, &context) {
|
||||
return file.on_left_click(&point, &context);
|
||||
return file.on_left_click();
|
||||
}
|
||||
p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN);
|
||||
}
|
||||
@ -389,7 +386,7 @@ impl ClickHandler for DirectoryView {
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
pub fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let dest = self.dest();
|
||||
let move_point = match context {
|
||||
UpdateContext::ParentPosition(p) => p.clone(),
|
||||
@ -430,3 +427,385 @@ impl ClickHandler for DirectoryView {
|
||||
false
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for DirectoryView {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::support::CanvasMock;
|
||||
use crate::tests::support::SimpleRendererMock;
|
||||
use crate::tests::support::{build_config, build_path};
|
||||
|
||||
//##########################################################
|
||||
// name_width
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_name_width() {
|
||||
let config = build_config();
|
||||
let widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
assert_eq!(widget.name_width(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_name_width() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.name_width(), 39);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// icon_width
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_icon_width() {
|
||||
let config = build_config();
|
||||
let widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
assert_eq!(widget.icon_width(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_icon_width() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.icon_width(), 14);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// height
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_height() {
|
||||
let config = build_config();
|
||||
let widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
assert_eq!(widget.height(), 16);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_height() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.height(), 14);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// name
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_name() {
|
||||
let config = build_config();
|
||||
let widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
assert_eq!(widget.name(), "foo".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_name() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.name(), "foo".to_owned());
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// path
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_path() {
|
||||
let config = build_config();
|
||||
let widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
assert_eq!(widget.path(), "/foo".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_path() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.path(), "/foo".to_owned());
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// source
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_source() {
|
||||
let config = build_config();
|
||||
let widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
assert_eq!(widget.source(), &Rect::new(0, 0, 64, 64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_source() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.source(), &Rect::new(0, 0, 64, 64));
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// dest
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_dest() {
|
||||
let config = build_config();
|
||||
let widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
assert_eq!(widget.dest(), Rect::new(0, 0, 36, 16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_dest() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.dest(), Rect::new(0, 0, 73, 14));
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// update
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_update_when_doesnt_exists() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(
|
||||
widget.update(0, &UpdateContext::Nothing),
|
||||
UpdateResult::RefreshFsTree
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_update_when_does_exists() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = DirectoryView::new("/tmp".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(
|
||||
widget.update(0, &UpdateContext::Nothing),
|
||||
UpdateResult::NoOp
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_update_expanded() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget =
|
||||
DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.open_directory(
|
||||
"/tmp/rider-editor/directory-view-test".to_owned(),
|
||||
&mut renderer,
|
||||
);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(
|
||||
widget.update(0, &UpdateContext::Nothing),
|
||||
UpdateResult::NoOp
|
||||
);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// render
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_render_no_expanded() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget =
|
||||
DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_render_expanded() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget =
|
||||
DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.open_directory(
|
||||
"/tmp/rider-editor/directory-view-test".to_owned(),
|
||||
&mut renderer,
|
||||
);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// is_left_click_target
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_when_target() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::Nothing;
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_when_target_with_parent() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::ParentPosition(Point::new(0, 0));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_expanded() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget =
|
||||
DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.open_directory(
|
||||
"/tmp/rider-editor/directory-view-test".to_owned(),
|
||||
&mut renderer,
|
||||
);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::ParentPosition(Point::new(0, 0));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refute_is_left_click_target_when_target() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(9000, 0);
|
||||
let context = UpdateContext::Nothing;
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refute_is_left_click_target_when_target_with_parent() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = DirectoryView::new("/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 9000);
|
||||
let context = UpdateContext::ParentPosition(Point::new(0, 0));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// on_left_click
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_on_left_click_when_target() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget =
|
||||
DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::Nothing;
|
||||
assert_eq!(
|
||||
widget.on_left_click(&p, &context),
|
||||
UpdateResult::OpenDirectory("/tmp/rider-editor/directory-view-test".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_on_left_click_when_target_with_parent() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget =
|
||||
DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::ParentPosition(Point::new(0, 0));
|
||||
assert_eq!(
|
||||
widget.on_left_click(&p, &context),
|
||||
UpdateResult::OpenDirectory("/tmp/rider-editor/directory-view-test".to_owned())
|
||||
);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_on_left_click_expanded() {
|
||||
build_path("/tmp/rider-editor/directory-view-test".to_owned());
|
||||
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget =
|
||||
DirectoryView::new("/tmp/rider-editor/directory-view-test".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.open_directory(
|
||||
"/tmp/rider-editor/directory-view-test".to_owned(),
|
||||
&mut renderer,
|
||||
);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::ParentPosition(Point::new(0, 0));
|
||||
assert_eq!(
|
||||
widget.on_left_click(&p, &context),
|
||||
UpdateResult::OpenDirectory("/tmp/rider-editor/directory-view-test".to_owned())
|
||||
);
|
||||
}
|
||||
}
|
||||
|
@ -6,6 +6,11 @@ use sdl2::rect::{Point, Rect};
|
||||
use std::collections::HashMap;
|
||||
use std::path;
|
||||
|
||||
const ICON_DEST_WIDTH: u32 = 16;
|
||||
const ICON_DEST_HEIGHT: u32 = 16;
|
||||
const ICON_SRC_WIDTH: u32 = 64;
|
||||
const ICON_SRC_HEIGHT: u32 = 64;
|
||||
|
||||
pub struct FileEntry {
|
||||
name_width: u32,
|
||||
icon_width: u32,
|
||||
@ -26,8 +31,8 @@ impl FileEntry {
|
||||
name_width: 0,
|
||||
icon_width: 0,
|
||||
height: 0,
|
||||
dest: Rect::new(0, 0, 16, 16),
|
||||
source: Rect::new(0, 0, 64, 64),
|
||||
dest: Rect::new(0, 0, ICON_DEST_WIDTH, ICON_DEST_HEIGHT),
|
||||
source: Rect::new(0, 0, ICON_SRC_WIDTH, ICON_SRC_HEIGHT),
|
||||
config,
|
||||
char_sizes: HashMap::new(),
|
||||
}
|
||||
@ -70,9 +75,10 @@ impl FileEntry {
|
||||
)
|
||||
}
|
||||
|
||||
fn render_icon<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
|
||||
fn render_icon<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
C: CanvasAccess,
|
||||
R: Renderer,
|
||||
{
|
||||
let dir_texture_path = {
|
||||
let c = self.config.read().unwrap();
|
||||
@ -81,27 +87,25 @@ impl FileEntry {
|
||||
themes_dir.push(path);
|
||||
themes_dir.to_str().unwrap().to_owned()
|
||||
};
|
||||
let texture = renderer
|
||||
.texture_manager()
|
||||
.load(dir_texture_path.as_str())
|
||||
.unwrap_or_else(|_| panic!("Failed to load directory entry texture"));
|
||||
dest.set_width(16);
|
||||
dest.set_height(16);
|
||||
let maybe_tex = renderer.load_image(dir_texture_path.clone());
|
||||
if let Ok(texture) = maybe_tex {
|
||||
dest.set_width(ICON_DEST_WIDTH);
|
||||
dest.set_height(ICON_DEST_HEIGHT);
|
||||
canvas
|
||||
.render_image(texture, self.source.clone(), dest.clone())
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
}
|
||||
}
|
||||
|
||||
fn render_name<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
|
||||
fn render_name<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
C: CanvasAccess,
|
||||
R: Renderer,
|
||||
{
|
||||
let mut d = dest.clone();
|
||||
d.set_x(dest.x() + NAME_MARGIN);
|
||||
|
||||
let font_details = build_font_details(self);
|
||||
let font = renderer.font_manager().load(&font_details).unwrap();
|
||||
let texture_manager = renderer.texture_manager();
|
||||
let name = self.name();
|
||||
|
||||
for c in name.chars() {
|
||||
@ -115,31 +119,24 @@ impl FileEntry {
|
||||
text: c.to_string(),
|
||||
font: font_details.clone(),
|
||||
};
|
||||
let text_texture = texture_manager
|
||||
.load_text(&mut text_details, font.clone())
|
||||
.unwrap();
|
||||
let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone());
|
||||
|
||||
if let Ok(texture) = maybe_texture {
|
||||
d.set_width(size.width());
|
||||
d.set_height(size.height());
|
||||
|
||||
canvas
|
||||
.render_image(text_texture, self.source.clone(), d.clone())
|
||||
.render_image(texture, self.source.clone(), d.clone())
|
||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||
d.set_x(d.x() + size.width() as i32)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for FileEntry {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl FileEntry {
|
||||
pub fn render<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RenderContext)
|
||||
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
C: CanvasAccess,
|
||||
R: Renderer,
|
||||
{
|
||||
let mut dest = match context {
|
||||
&RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
@ -149,48 +146,41 @@ impl FileEntry {
|
||||
self.render_name(canvas, renderer, &mut dest.clone());
|
||||
}
|
||||
|
||||
pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) {
|
||||
let w_rect = get_text_character_rect('W', renderer).unwrap();
|
||||
pub fn prepare_ui<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
let w_rect = renderer.load_character_size('W');
|
||||
self.char_sizes.insert('W', w_rect.clone());
|
||||
self.height = w_rect.height();
|
||||
self.icon_width = w_rect.height();
|
||||
self.name_width = 0;
|
||||
|
||||
for c in self.name().chars() {
|
||||
let size = { get_text_character_rect(c.clone(), renderer).unwrap() };
|
||||
let size = { renderer.load_character_size(c.clone()) };
|
||||
self.char_sizes.insert(c, size);
|
||||
self.name_width += size.width();
|
||||
}
|
||||
self.dest.set_width(w_rect.height());
|
||||
self.dest.set_height(w_rect.height());
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for FileEntry {
|
||||
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult {
|
||||
pub fn update(&mut self) -> UpdateResult {
|
||||
if !path::Path::new(&self.path).exists() {
|
||||
return UpdateResult::RefreshFsTree;
|
||||
}
|
||||
UpdateResult::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for FileEntry {
|
||||
fn render_start_point(&self) -> Point {
|
||||
pub fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for FileEntry {
|
||||
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UpdateResult {
|
||||
pub fn on_left_click(&mut self) -> UpdateResult {
|
||||
UpdateResult::OpenFile(self.path.clone())
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
pub fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let dest = Rect::new(
|
||||
self.dest.x(),
|
||||
self.dest.y(),
|
||||
@ -204,3 +194,270 @@ impl ClickHandler for FileEntry {
|
||||
rect.contains_point(point.clone())
|
||||
}
|
||||
}
|
||||
|
||||
impl ConfigHolder for FileEntry {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::support::build_config;
|
||||
use crate::tests::support::CanvasMock;
|
||||
use crate::tests::support::SimpleRendererMock;
|
||||
|
||||
//##########################################################
|
||||
// name_width
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_name_width() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.name_width(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_name_width() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.name_width(), 91);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// icon_width
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_icon_width() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.icon_width(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_icon_width() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.icon_width(), 14);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// height
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_height() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.height(), 0);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_height() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.height(), 14);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// name
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_name() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.name(), "bar.txt".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_name() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.name(), "bar.txt".to_owned());
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// path
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_path() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.path(), "/foo".to_owned());
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_path() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.path(), "/foo".to_owned());
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// source
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_source() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.source(), &Rect::new(0, 0, 64, 64));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_source() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.source(), &Rect::new(0, 0, 64, 64));
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// dest
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_dest() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.dest(), &Rect::new(0, 0, 16, 16));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_dest() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.dest(), &Rect::new(0, 0, 14, 14));
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// full_dest
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_initial_full_dest() {
|
||||
let config = build_config();
|
||||
let widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
assert_eq!(widget.full_dest(), Rect::new(0, 0, 20, 1));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepared_full_dest() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.full_dest(), Rect::new(0, 0, 125, 14));
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// update
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_update_when_doesnt_exists() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.update(), UpdateResult::RefreshFsTree);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_update_when_does_exists() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/tmp".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.update(), UpdateResult::NoOp);
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// render
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_render() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
assert_eq!(widget.full_dest(), Rect::new(0, 0, 125, 14));
|
||||
}
|
||||
|
||||
//##########################################################
|
||||
// is_left_click_target
|
||||
//##########################################################
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_when_target() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::Nothing;
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_when_target_with_parent() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 0);
|
||||
let context = UpdateContext::ParentPosition(Point::new(0, 0));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), true);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refute_is_left_click_target_when_target() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(9000, 0);
|
||||
let context = UpdateContext::Nothing;
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn refute_is_left_click_target_when_target_with_parent() {
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let mut widget = FileEntry::new("bar.txt".to_owned(), "/foo".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
widget.render(&mut canvas, &mut renderer, &RenderContext::Nothing);
|
||||
let p = Point::new(0, 9000);
|
||||
let context = UpdateContext::ParentPosition(Point::new(0, 0));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
}
|
||||
|
@ -1,7 +1,8 @@
|
||||
use crate::renderer::CanvasRenderer;
|
||||
use crate::renderer::renderer::Renderer;
|
||||
use crate::ui::*;
|
||||
use crate::ui::{RenderContext as RC, UpdateContext as UC};
|
||||
use rider_config::ConfigAccess;
|
||||
use rider_config::ConfigHolder;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::{Point, Rect};
|
||||
use std::sync::Arc;
|
||||
@ -56,7 +57,10 @@ impl OpenFile {
|
||||
self.root_path.clone()
|
||||
}
|
||||
|
||||
pub fn open_directory(&mut self, dir_path: String, renderer: &mut CanvasRenderer) {
|
||||
pub fn open_directory<R>(&mut self, dir_path: String, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
self.directory_view.open_directory(dir_path, renderer);
|
||||
{
|
||||
let dest = self.directory_view.dest();
|
||||
@ -73,10 +77,8 @@ impl OpenFile {
|
||||
pub fn full_rect(&self) -> &Rect {
|
||||
&self.full_dest
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableView for OpenFile {
|
||||
fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
pub fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
let read_config = self.config.read().unwrap();
|
||||
|
||||
let value_x = read_config.scroll().speed() * x;
|
||||
@ -98,16 +100,14 @@ impl ScrollableView for OpenFile {
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll(&self) -> Point {
|
||||
pub fn scroll(&self) -> Point {
|
||||
Point::new(
|
||||
-self.horizontal_scroll_bar.scroll_value(),
|
||||
-self.vertical_scroll_bar.scroll_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
impl Update for OpenFile {
|
||||
fn update(&mut self, ticks: i32, context: &UC) -> UR {
|
||||
pub fn update(&mut self, ticks: i32, context: &UC) -> UR {
|
||||
let (window_width, window_height, color, scroll_width, scroll_margin) = {
|
||||
let c = self.config.read().unwrap();
|
||||
(
|
||||
@ -123,6 +123,7 @@ impl Update for OpenFile {
|
||||
.set_x((window_width / 2) as i32 - (self.dest.width() / 2) as i32);
|
||||
self.dest
|
||||
.set_y((window_height / 2) as i32 - (self.dest.height() / 2) as i32);
|
||||
|
||||
self.background_color = color;
|
||||
|
||||
// Scroll bars
|
||||
@ -143,21 +144,19 @@ impl Update for OpenFile {
|
||||
// End
|
||||
UR::NoOp
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl OpenFile {
|
||||
pub fn render<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RC)
|
||||
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RC)
|
||||
where
|
||||
T: CanvasAccess,
|
||||
C: CanvasAccess,
|
||||
R: Renderer + CharacterSizeManager + ConfigHolder,
|
||||
{
|
||||
let dest = match context {
|
||||
RC::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest,
|
||||
_ => self.dest.clone(),
|
||||
};
|
||||
|
||||
// Background
|
||||
// canvas.set_clip_rect(dest.clone());
|
||||
canvas.set_clipping(dest.clone());
|
||||
canvas
|
||||
.render_rect(dest, self.background_color)
|
||||
.unwrap_or_else(|_| panic!("Failed to render open file modal background!"));
|
||||
@ -183,23 +182,22 @@ impl OpenFile {
|
||||
);
|
||||
}
|
||||
|
||||
pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) {
|
||||
pub fn prepare_ui<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
self.directory_view.prepare_ui(renderer);
|
||||
}
|
||||
}
|
||||
|
||||
impl RenderBox for OpenFile {
|
||||
fn render_start_point(&self) -> Point {
|
||||
pub fn render_start_point(&self) -> Point {
|
||||
self.dest.top_left()
|
||||
}
|
||||
|
||||
fn dest(&self) -> Rect {
|
||||
pub fn dest(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for OpenFile {
|
||||
fn on_left_click(&mut self, point: &Point, context: &UC) -> UR {
|
||||
pub fn on_left_click(&mut self, point: &Point, context: &UC) -> UR {
|
||||
let dest = match context {
|
||||
UC::ParentPosition(p) => move_render_point(*p, &self.dest),
|
||||
_ => self.dest,
|
||||
@ -221,15 +219,198 @@ impl ClickHandler for OpenFile {
|
||||
res
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UC) -> bool {
|
||||
pub fn is_left_click_target(&self, point: &Point, context: &UC) -> bool {
|
||||
let dest = match context {
|
||||
UC::ParentPosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest.clone(),
|
||||
};
|
||||
let context = UC::ParentPosition(
|
||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(),
|
||||
);
|
||||
self.directory_view.is_left_click_target(point, &context);
|
||||
let p =
|
||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll();
|
||||
let context = UC::ParentPosition(p);
|
||||
if self.directory_view.is_left_click_target(point, &context) {
|
||||
true
|
||||
} else {
|
||||
Rect::new(p.x(), p.y(), dest.width(), dest.height()).contains_point(point.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use super::*;
|
||||
use crate::tests::support::SimpleRendererMock;
|
||||
use crate::tests::support::{build_config, CanvasMock};
|
||||
use std::fs;
|
||||
|
||||
//#######################################################################
|
||||
// scroll
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_scroll() {
|
||||
let config = build_config();
|
||||
let mut widget = OpenFile::new("/tmp".to_owned(), 100, 100, config);
|
||||
widget.scroll_by(12, 13);
|
||||
assert_eq!(widget.scroll(), Point::new(0, -390));
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// dest
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_dest() {
|
||||
let config = build_config();
|
||||
let widget = OpenFile::new("/tmp".to_owned(), 120, 130, config);
|
||||
assert_eq!(widget.dest(), Rect::new(452, 365, 120, 130));
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// full_rect
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_full_rect() {
|
||||
let config = build_config();
|
||||
let widget = OpenFile::new("/tmp".to_owned(), 120, 130, config);
|
||||
assert_eq!(widget.full_rect(), &Rect::new(0, 0, 16, 16));
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// open_directory
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_open_directory() {
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
fs::create_dir_all(path).unwrap();
|
||||
let config = build_config();
|
||||
let mut renderer = SimpleRendererMock::new(config);
|
||||
let mut widget = OpenFile::new(path.to_owned(), 120, 130, renderer.config().clone());
|
||||
widget.open_directory(path.to_owned(), &mut renderer);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// update
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_update() {
|
||||
let config = build_config();
|
||||
let mut widget = OpenFile::new("/tmp".to_owned(), 100, 100, config);
|
||||
widget.update(0, &UpdateContext::Nothing);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// root_path
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_root_path() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
widget.update(0, &UpdateContext::Nothing);
|
||||
assert_eq!(widget.root_path(), path.to_owned());
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// render_start_point
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_render_start_point() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
widget.update(0, &UpdateContext::Nothing);
|
||||
assert_eq!(widget.render_start_point(), Point::new(462, 380));
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// on_left_click
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_on_left_click_with_nothing() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
let p = Point::new(100, 100);
|
||||
let context = UpdateContext::Nothing;
|
||||
widget.on_left_click(&p, &context);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_on_left_click_with_parent_position() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
let p = Point::new(100, 100);
|
||||
let context = UpdateContext::ParentPosition(Point::new(10, 10));
|
||||
widget.on_left_click(&p, &context);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// is_left_click_target
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_with_nothing() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
let p = Point::new(100, 100);
|
||||
let context = UpdateContext::Nothing;
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_with_parent_position() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
let p = Point::new(100, 100);
|
||||
let context = UpdateContext::ParentPosition(Point::new(10, 10));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_with_parent_position_in_box() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
let p = Point::new(500, 400);
|
||||
let context = UpdateContext::ParentPosition(Point::new(10, 10));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// render
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_render() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
let p = Point::new(100, 100);
|
||||
let context = RenderContext::RelativePosition(p);
|
||||
widget.render(&mut canvas, &mut renderer, &context);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// prepare_ui
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_prepare_ui() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut renderer = SimpleRendererMock::new(config.clone());
|
||||
let mut widget = OpenFile::new(path.to_owned(), 100, 100, config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
}
|
||||
}
|
||||
|
@ -1 +1,383 @@
|
||||
use crate::app::application::UpdateResult;
|
||||
use crate::renderer::renderer::Renderer;
|
||||
use crate::ui::file_editor::ScrollableView;
|
||||
use crate::ui::filesystem::directory::DirectoryView;
|
||||
use crate::ui::horizontal_scroll_bar::HorizontalScrollBar;
|
||||
use crate::ui::move_render_point;
|
||||
use crate::ui::scroll_bar::Scrollable;
|
||||
use crate::ui::text_character::CharacterSizeManager;
|
||||
use crate::ui::vertical_scroll_bar::VerticalScrollBar;
|
||||
use crate::ui::CanvasAccess;
|
||||
use crate::ui::ClickHandler;
|
||||
use crate::ui::RenderContext;
|
||||
use crate::ui::UpdateContext;
|
||||
use rider_config::config::Config;
|
||||
use sdl2::pixels::Color;
|
||||
use sdl2::rect::Point;
|
||||
use sdl2::rect::Rect;
|
||||
use std::sync::Arc;
|
||||
use std::sync::RwLock;
|
||||
|
||||
const CONTENT_MARGIN_LEFT: i32 = 16;
|
||||
const CONTENT_MARGIN_TOP: i32 = 24;
|
||||
const DEFAULT_ICON_SIZE: u32 = 16;
|
||||
|
||||
pub struct ProjectTreeSidebar {
|
||||
dest: Rect,
|
||||
full_dest: Rect,
|
||||
config: Arc<RwLock<Config>>,
|
||||
root: String,
|
||||
border_color: Color,
|
||||
background_color: Color,
|
||||
dir_view: DirectoryView,
|
||||
vertical_scroll_bar: VerticalScrollBar,
|
||||
horizontal_scroll_bar: HorizontalScrollBar,
|
||||
}
|
||||
|
||||
impl ProjectTreeSidebar {
|
||||
pub fn new(root: String, config: Arc<RwLock<Config>>) -> Self {
|
||||
let (background_color, border_color, h): (Color, Color, u32) = {
|
||||
let c = config.read().unwrap();
|
||||
(
|
||||
c.theme().background().into(),
|
||||
c.theme().border_color().into(),
|
||||
c.height(),
|
||||
)
|
||||
};
|
||||
|
||||
Self {
|
||||
dest: Rect::new(0, 0, 200, h),
|
||||
full_dest: Rect::new(0, 0, DEFAULT_ICON_SIZE, DEFAULT_ICON_SIZE),
|
||||
dir_view: DirectoryView::new(root.clone(), config.clone()),
|
||||
vertical_scroll_bar: VerticalScrollBar::new(Arc::clone(&config)),
|
||||
horizontal_scroll_bar: HorizontalScrollBar::new(Arc::clone(&config)),
|
||||
config,
|
||||
root,
|
||||
background_color,
|
||||
border_color,
|
||||
}
|
||||
}
|
||||
|
||||
pub fn update(&mut self, ticks: i32) {
|
||||
let config = self.config.read().unwrap();
|
||||
let height = config.height();
|
||||
// let left_margin = config.editor_left_margin();
|
||||
let top_margin = config.menu_height() as i32;
|
||||
// self.dest.set_x(left_margin);
|
||||
self.dest.set_y(top_margin);
|
||||
self.dest.set_height(height - top_margin as u32);
|
||||
self.dir_view.update(ticks, &UpdateContext::Nothing);
|
||||
}
|
||||
|
||||
pub fn prepare_ui<R>(&mut self, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
let config = self.config.read().unwrap();
|
||||
let height = config.height();
|
||||
let left_margin = 0;
|
||||
let top_margin = config.menu_height() as i32;
|
||||
self.dest.set_x(left_margin);
|
||||
self.dest.set_y(top_margin);
|
||||
self.dest.set_height(height);
|
||||
self.dir_view.prepare_ui(renderer);
|
||||
self.dir_view.open_directory(self.root.clone(), renderer);
|
||||
}
|
||||
|
||||
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
C: CanvasAccess,
|
||||
{
|
||||
canvas.set_clipping(self.dest.clone());
|
||||
canvas
|
||||
.render_rect(self.dest.clone(), self.background_color.clone())
|
||||
.unwrap();
|
||||
canvas
|
||||
.render_border(self.dest.clone(), self.border_color.clone())
|
||||
.unwrap();
|
||||
|
||||
// dir view
|
||||
let context = RenderContext::RelativePosition(
|
||||
self.dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP),
|
||||
);
|
||||
self.dir_view.render(canvas, renderer, &context);
|
||||
}
|
||||
|
||||
pub fn full_rect(&self) -> Rect {
|
||||
self.dest.clone()
|
||||
}
|
||||
|
||||
pub fn root(&self) -> String {
|
||||
self.root.clone()
|
||||
}
|
||||
|
||||
pub fn open_directory<R>(&mut self, dir_path: String, renderer: &mut R)
|
||||
where
|
||||
R: Renderer + CharacterSizeManager,
|
||||
{
|
||||
self.dir_view.open_directory(dir_path, renderer);
|
||||
{
|
||||
let dest = self.dir_view.dest();
|
||||
let full_dest = Rect::new(
|
||||
dest.x(),
|
||||
dest.y(),
|
||||
dest.width() + (2 * CONTENT_MARGIN_LEFT as u32),
|
||||
dest.height() + (2 * CONTENT_MARGIN_TOP as u32),
|
||||
);
|
||||
self.full_dest = full_dest;
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ClickHandler for ProjectTreeSidebar {
|
||||
fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult {
|
||||
let dest = match context {
|
||||
UpdateContext::ParentPosition(p) => move_render_point(*p, &self.dest),
|
||||
_ => self.dest,
|
||||
};
|
||||
let context = UpdateContext::ParentPosition(
|
||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(),
|
||||
);
|
||||
let res = self.dir_view.on_left_click(point, &context);
|
||||
{
|
||||
let dest = self.dir_view.dest();
|
||||
let full_dest = Rect::new(
|
||||
dest.x(),
|
||||
dest.y(),
|
||||
dest.width() + (2 * CONTENT_MARGIN_LEFT as u32),
|
||||
dest.height() + (2 * CONTENT_MARGIN_TOP as u32),
|
||||
);
|
||||
self.full_dest = full_dest;
|
||||
}
|
||||
res
|
||||
}
|
||||
|
||||
fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool {
|
||||
let dest = match context {
|
||||
UpdateContext::ParentPosition(p) => move_render_point(p.clone(), &self.dest),
|
||||
_ => self.dest.clone(),
|
||||
};
|
||||
let p =
|
||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll();
|
||||
let context = UpdateContext::ParentPosition(p);
|
||||
if self.dir_view.is_left_click_target(point, &context) {
|
||||
true
|
||||
} else {
|
||||
Rect::new(p.x(), p.y(), dest.width(), dest.height()).contains_point(point.clone())
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
impl ScrollableView for ProjectTreeSidebar {
|
||||
fn scroll_by(&mut self, x: i32, y: i32) {
|
||||
let read_config = self.config.read().unwrap();
|
||||
|
||||
let value_x = read_config.scroll().speed() * x;
|
||||
let value_y = read_config.scroll().speed() * y;
|
||||
let old_x = self.horizontal_scroll_bar.scroll_value();
|
||||
let old_y = self.vertical_scroll_bar.scroll_value();
|
||||
|
||||
if value_x + old_x >= 0 {
|
||||
self.horizontal_scroll_bar.scroll_to(value_x + old_x);
|
||||
if self.horizontal_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.horizontal_scroll_bar.scroll_to(old_x);
|
||||
}
|
||||
}
|
||||
if value_y + old_y >= 0 {
|
||||
self.vertical_scroll_bar.scroll_to(value_y + old_y);
|
||||
if self.vertical_scroll_bar.scrolled_part() > 1.0 {
|
||||
self.vertical_scroll_bar.scroll_to(old_y);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
fn scroll(&self) -> Point {
|
||||
Point::new(
|
||||
-self.horizontal_scroll_bar.scroll_value(),
|
||||
-self.vertical_scroll_bar.scroll_value(),
|
||||
)
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg(test)]
|
||||
mod tests {
|
||||
use crate::renderer::managers::FontDetails;
|
||||
use crate::renderer::managers::TextDetails;
|
||||
use crate::renderer::renderer::Renderer;
|
||||
use crate::tests::support::build_config;
|
||||
use crate::tests::support::CanvasMock;
|
||||
use crate::ui::file_editor::ScrollableView;
|
||||
use crate::ui::project_tree::ProjectTreeSidebar;
|
||||
use crate::ui::text_character::CharacterSizeManager;
|
||||
use crate::ui::ClickHandler;
|
||||
use crate::ui::UpdateContext;
|
||||
use rider_config::ConfigAccess;
|
||||
use rider_config::ConfigHolder;
|
||||
use sdl2::rect::Point;
|
||||
use sdl2::rect::Rect;
|
||||
use sdl2::render::Texture;
|
||||
use sdl2::ttf::Font;
|
||||
use std::rc::Rc;
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
struct RendererMock {
|
||||
config: ConfigAccess,
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl RendererMock {
|
||||
pub fn new(config: ConfigAccess) -> Self {
|
||||
Self { config }
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl Renderer for RendererMock {
|
||||
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 load text texture".to_owned())
|
||||
}
|
||||
|
||||
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||
Err("Skip render".to_owned())
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl ConfigHolder for RendererMock {
|
||||
fn config(&self) -> &ConfigAccess {
|
||||
&self.config
|
||||
}
|
||||
}
|
||||
|
||||
#[cfg_attr(tarpaulin, skip)]
|
||||
impl CharacterSizeManager for RendererMock {
|
||||
fn load_character_size(&mut self, _c: char) -> Rect {
|
||||
Rect::new(0, 0, 13, 14)
|
||||
}
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_full_rect() {
|
||||
let config = build_config();
|
||||
let mut renderer = RendererMock::new(config.clone());
|
||||
let mut widget = ProjectTreeSidebar::new("/tmp".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.full_rect(), Rect::new(0, 60, 200, 860));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_update() {
|
||||
let config = build_config();
|
||||
let mut widget = ProjectTreeSidebar::new("/tmp".to_owned(), config);
|
||||
widget.update(0);
|
||||
assert_eq!(widget.full_rect(), Rect::new(0, 60, 200, 800));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_prepare_ui() {
|
||||
let config = build_config();
|
||||
let mut renderer = RendererMock::new(config.clone());
|
||||
let mut widget = ProjectTreeSidebar::new("/tmp".to_owned(), config);
|
||||
widget.prepare_ui(&mut renderer);
|
||||
assert_eq!(widget.full_rect(), Rect::new(0, 60, 200, 860));
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_render() {
|
||||
let config = build_config();
|
||||
let mut renderer = RendererMock::new(config.clone());
|
||||
let mut canvas = CanvasMock::new();
|
||||
let widget = ProjectTreeSidebar::new("/tmp".to_owned(), config);
|
||||
widget.render(&mut canvas, &mut renderer);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// scroll
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_scroll() {
|
||||
let config = build_config();
|
||||
let widget = ProjectTreeSidebar::new("/tmp".to_owned(), config);
|
||||
let res = widget.scroll();
|
||||
let expected = Point::new(0, 0);
|
||||
assert_eq!(res, expected);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_scroll_by() {
|
||||
let config = build_config();
|
||||
let mut widget = ProjectTreeSidebar::new("/tmp".to_owned(), config);
|
||||
widget.scroll_by(10, 10);
|
||||
let res = widget.scroll();
|
||||
let expected = Point::new(0, -300);
|
||||
assert_eq!(res, expected);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// on_left_click
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_on_left_click_with_nothing() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut widget = ProjectTreeSidebar::new(path.to_owned(), config);
|
||||
let p = Point::new(100, 100);
|
||||
let context = UpdateContext::Nothing;
|
||||
widget.on_left_click(&p, &context);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_on_left_click_with_parent_position() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let mut widget = ProjectTreeSidebar::new(path.to_owned(), config);
|
||||
let p = Point::new(100, 100);
|
||||
let context = UpdateContext::ParentPosition(Point::new(10, 10));
|
||||
widget.on_left_click(&p, &context);
|
||||
}
|
||||
|
||||
//#######################################################################
|
||||
// is_left_click_target
|
||||
//#######################################################################
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_with_nothing() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let widget = ProjectTreeSidebar::new(path.to_owned(), config);
|
||||
let p = Point::new(400, 400);
|
||||
let context = UpdateContext::Nothing;
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_with_parent_position() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let widget = ProjectTreeSidebar::new(path.to_owned(), config);
|
||||
let p = Point::new(800, 800);
|
||||
let context = UpdateContext::ParentPosition(Point::new(10, 10));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
|
||||
#[test]
|
||||
fn assert_is_left_click_target_with_parent_position_in_box() {
|
||||
let config = build_config();
|
||||
let path = "/tmp/rider/test-open-file/open-directory";
|
||||
let widget = ProjectTreeSidebar::new(path.to_owned(), config);
|
||||
let p = Point::new(500, 400);
|
||||
let context = UpdateContext::ParentPosition(Point::new(10, 10));
|
||||
assert_eq!(widget.is_left_click_target(&p, &context), false);
|
||||
}
|
||||
}
|
||||
|
Loading…
Reference in New Issue
Block a user