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
|
```bash
|
||||||
curl https://sh.rustup.rs -sSf | sh
|
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
|
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
|
## Road map
|
||||||
|
@ -10,7 +10,7 @@ impl ScrollConfig {
|
|||||||
Self {
|
Self {
|
||||||
width: 4,
|
width: 4,
|
||||||
margin_right: 5,
|
margin_right: 5,
|
||||||
speed: 10,
|
speed: 30,
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -91,7 +91,7 @@ mod tests {
|
|||||||
fn assert_speed() {
|
fn assert_speed() {
|
||||||
let config = ScrollConfig::new();
|
let config = ScrollConfig::new();
|
||||||
let result = config.speed();
|
let result = config.speed();
|
||||||
let expected = 10;
|
let expected = 30;
|
||||||
assert_eq!(result, expected);
|
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::renderer::CanvasRenderer;
|
||||||
use crate::ui::*;
|
use crate::ui::*;
|
||||||
use rider_config::*;
|
use rider_config::*;
|
||||||
@ -9,6 +11,7 @@ use std::sync::*;
|
|||||||
|
|
||||||
pub struct AppState {
|
pub struct AppState {
|
||||||
menu_bar: MenuBar,
|
menu_bar: MenuBar,
|
||||||
|
project_tree: ProjectTreeSidebar,
|
||||||
files: Vec<EditorFile>,
|
files: Vec<EditorFile>,
|
||||||
config: Arc<RwLock<Config>>,
|
config: Arc<RwLock<Config>>,
|
||||||
file_editor: FileEditor,
|
file_editor: FileEditor,
|
||||||
@ -19,6 +22,10 @@ impl AppState {
|
|||||||
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
pub fn new(config: Arc<RwLock<Config>>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
menu_bar: MenuBar::new(Arc::clone(&config)),
|
menu_bar: MenuBar::new(Arc::clone(&config)),
|
||||||
|
project_tree: ProjectTreeSidebar::new(
|
||||||
|
Application::current_working_directory(),
|
||||||
|
config.clone(),
|
||||||
|
),
|
||||||
files: vec![],
|
files: vec![],
|
||||||
file_editor: FileEditor::new(Arc::clone(&config)),
|
file_editor: FileEditor::new(Arc::clone(&config)),
|
||||||
open_file_modal: None,
|
open_file_modal: None,
|
||||||
@ -40,18 +47,22 @@ impl AppState {
|
|||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
pub fn open_directory<R>(&mut self, dir_path: String, renderer: &mut R)
|
||||||
pub fn open_directory(&mut self, dir_path: String, renderer: &mut CanvasRenderer) {
|
where
|
||||||
|
R: Renderer + CharacterSizeManager,
|
||||||
|
{
|
||||||
match self.open_file_modal.as_mut() {
|
match self.open_file_modal.as_mut() {
|
||||||
Some(modal) => modal.open_directory(dir_path, renderer),
|
Some(modal) => modal.open_directory(dir_path, renderer),
|
||||||
_ => (),
|
None => self.project_tree.open_directory(dir_path, renderer),
|
||||||
};
|
};
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
pub fn file_editor(&self) -> &FileEditor {
|
pub fn file_editor(&self) -> &FileEditor {
|
||||||
&self.file_editor
|
&self.file_editor
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
pub fn file_editor_mut(&mut self) -> &mut FileEditor {
|
pub fn file_editor_mut(&mut self) -> &mut FileEditor {
|
||||||
&mut self.file_editor
|
&mut self.file_editor
|
||||||
}
|
}
|
||||||
@ -75,23 +86,38 @@ impl AppState {
|
|||||||
|
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
impl AppState {
|
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);
|
self.file_editor.render(canvas, renderer);
|
||||||
|
|
||||||
|
// menu bar
|
||||||
self.menu_bar.render(canvas, &RenderContext::Nothing);
|
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() {
|
match self.open_file_modal.as_ref() {
|
||||||
Some(modal) => modal.render(canvas, renderer, &RenderContext::Nothing),
|
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.menu_bar.prepare_ui();
|
||||||
|
self.project_tree.prepare_ui(renderer);
|
||||||
self.file_editor.prepare_ui(renderer);
|
self.file_editor.prepare_ui(renderer);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Update for AppState {
|
pub fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
// open file modal
|
||||||
let res = match self.open_file_modal.as_mut() {
|
let res = match self.open_file_modal.as_mut() {
|
||||||
Some(modal) => modal.update(ticks, &UpdateContext::Nothing),
|
Some(modal) => modal.update(ticks, &UpdateContext::Nothing),
|
||||||
_ => UpdateResult::NoOp,
|
_ => UpdateResult::NoOp,
|
||||||
@ -100,8 +126,17 @@ impl Update for AppState {
|
|||||||
return res;
|
return res;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
// menu bar
|
||||||
self.menu_bar.update(ticks, context);
|
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
|
UpdateResult::NoOp
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@ -109,6 +144,14 @@ impl Update for AppState {
|
|||||||
impl AppState {
|
impl AppState {
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult {
|
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() {
|
match self.open_file_modal.as_mut() {
|
||||||
Some(modal) => return modal.on_left_click(point, &UpdateContext::Nothing),
|
Some(modal) => return modal.on_left_click(point, &UpdateContext::Nothing),
|
||||||
_ => (),
|
_ => (),
|
||||||
|
@ -16,6 +16,7 @@ use sdl2::surface::Surface;
|
|||||||
use sdl2::video::Window;
|
use sdl2::video::Window;
|
||||||
use sdl2::EventPump;
|
use sdl2::EventPump;
|
||||||
use sdl2::{Sdl, TimerSubsystem, VideoSubsystem};
|
use sdl2::{Sdl, TimerSubsystem, VideoSubsystem};
|
||||||
|
use std::env;
|
||||||
use std::process::Command;
|
use std::process::Command;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
use std::thread::sleep;
|
use std::thread::sleep;
|
||||||
@ -29,6 +30,8 @@ pub enum UpdateResult {
|
|||||||
Stop,
|
Stop,
|
||||||
RefreshPositions,
|
RefreshPositions,
|
||||||
MouseLeftClicked(Point),
|
MouseLeftClicked(Point),
|
||||||
|
MouseDragStart(Point),
|
||||||
|
MouseDragStop(Point),
|
||||||
MoveCaret(Rect, CaretPosition),
|
MoveCaret(Rect, CaretPosition),
|
||||||
DeleteFront,
|
DeleteFront,
|
||||||
DeleteBack,
|
DeleteBack,
|
||||||
@ -44,6 +47,7 @@ pub enum UpdateResult {
|
|||||||
OpenFile(String),
|
OpenFile(String),
|
||||||
OpenDirectory(String),
|
OpenDirectory(String),
|
||||||
OpenFileModal,
|
OpenFileModal,
|
||||||
|
FileDropped(String),
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
@ -187,14 +191,16 @@ impl Application {
|
|||||||
app_state.open_directory(dir_path.clone(), &mut renderer);
|
app_state.open_directory(dir_path.clone(), &mut renderer);
|
||||||
}
|
}
|
||||||
UpdateResult::OpenFileModal => {
|
UpdateResult::OpenFileModal => {
|
||||||
use std::env;
|
let pwd = Self::current_working_directory();
|
||||||
let pwd = env::current_dir().unwrap().to_str().unwrap().to_string();
|
|
||||||
let mut modal =
|
let mut modal =
|
||||||
OpenFile::new(pwd.clone(), 400, 800, Arc::clone(&self.config));
|
OpenFile::new(pwd.clone(), 400, 800, Arc::clone(&self.config));
|
||||||
modal.prepare_ui(&mut renderer);
|
modal.prepare_ui(&mut renderer);
|
||||||
modal.open_directory(pwd.clone(), &mut renderer);
|
modal.open_directory(pwd.clone(), &mut renderer);
|
||||||
app_state.set_open_file_modal(Some(modal));
|
app_state.set_open_file_modal(Some(modal));
|
||||||
}
|
}
|
||||||
|
UpdateResult::MouseDragStart(_point) => (),
|
||||||
|
UpdateResult::MouseDragStop(_point) => (),
|
||||||
|
UpdateResult::FileDropped(_path) => (),
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
self.tasks = new_tasks;
|
self.tasks = new_tasks;
|
||||||
@ -241,80 +247,78 @@ impl Application {
|
|||||||
Event::Quit { .. } => self.tasks.push(UpdateResult::Stop),
|
Event::Quit { .. } => self.tasks.push(UpdateResult::Stop),
|
||||||
Event::MouseButtonUp {
|
Event::MouseButtonUp {
|
||||||
mouse_btn, x, y, ..
|
mouse_btn, x, y, ..
|
||||||
} => match mouse_btn {
|
} if mouse_btn == MouseButton::Left => {
|
||||||
MouseButton::Left => self
|
self.tasks
|
||||||
.tasks
|
.push(UpdateResult::MouseDragStart(Point::new(x, y)));
|
||||||
.push(UpdateResult::MouseLeftClicked(Point::new(x, y))),
|
self.tasks
|
||||||
_ => (),
|
.push(UpdateResult::MouseLeftClicked(Point::new(x, y)));
|
||||||
},
|
|
||||||
Event::KeyDown { keycode, .. } => {
|
|
||||||
let keycode = if keycode.is_some() {
|
|
||||||
keycode.unwrap()
|
|
||||||
} else {
|
|
||||||
continue;
|
|
||||||
};
|
|
||||||
|
|
||||||
match keycode {
|
|
||||||
Keycode::Backspace => {
|
|
||||||
self.tasks.push(UpdateResult::DeleteFront);
|
|
||||||
}
|
|
||||||
Keycode::Delete => {
|
|
||||||
self.tasks.push(UpdateResult::DeleteBack);
|
|
||||||
}
|
|
||||||
Keycode::KpEnter | Keycode::Return => {
|
|
||||||
self.tasks.push(UpdateResult::InsertNewLine);
|
|
||||||
}
|
|
||||||
Keycode::Left => {
|
|
||||||
self.tasks.push(UpdateResult::MoveCaretLeft);
|
|
||||||
}
|
|
||||||
Keycode::Right => {
|
|
||||||
self.tasks.push(UpdateResult::MoveCaretRight);
|
|
||||||
}
|
|
||||||
Keycode::Up => {
|
|
||||||
self.tasks.push(UpdateResult::MoveCaretUp);
|
|
||||||
}
|
|
||||||
Keycode::Down => {
|
|
||||||
self.tasks.push(UpdateResult::MoveCaretDown);
|
|
||||||
}
|
|
||||||
Keycode::O => {
|
|
||||||
if left_control_pressed && !shift_pressed {
|
|
||||||
self.tasks.push(UpdateResult::OpenFileModal);
|
|
||||||
}
|
|
||||||
}
|
|
||||||
_ => {}
|
|
||||||
};
|
|
||||||
}
|
}
|
||||||
|
Event::DropFile { filename, .. } => {
|
||||||
|
self.tasks.push(UpdateResult::FileDropped(filename))
|
||||||
|
}
|
||||||
|
Event::MouseButtonDown {
|
||||||
|
x, y, mouse_btn, ..
|
||||||
|
} if mouse_btn == MouseButton::Left => self
|
||||||
|
.tasks
|
||||||
|
.push(UpdateResult::MouseDragStart(Point::new(x, y))),
|
||||||
|
Event::KeyDown { keycode, .. } if keycode.is_some() => match keycode.unwrap() {
|
||||||
|
Keycode::Backspace => {
|
||||||
|
self.tasks.push(UpdateResult::DeleteFront);
|
||||||
|
}
|
||||||
|
Keycode::Delete => {
|
||||||
|
self.tasks.push(UpdateResult::DeleteBack);
|
||||||
|
}
|
||||||
|
Keycode::KpEnter | Keycode::Return => {
|
||||||
|
self.tasks.push(UpdateResult::InsertNewLine);
|
||||||
|
}
|
||||||
|
Keycode::Left => {
|
||||||
|
self.tasks.push(UpdateResult::MoveCaretLeft);
|
||||||
|
}
|
||||||
|
Keycode::Right => {
|
||||||
|
self.tasks.push(UpdateResult::MoveCaretRight);
|
||||||
|
}
|
||||||
|
Keycode::Up => {
|
||||||
|
self.tasks.push(UpdateResult::MoveCaretUp);
|
||||||
|
}
|
||||||
|
Keycode::Down => {
|
||||||
|
self.tasks.push(UpdateResult::MoveCaretDown);
|
||||||
|
}
|
||||||
|
Keycode::O if left_control_pressed && !shift_pressed => {
|
||||||
|
self.tasks.push(UpdateResult::OpenFileModal)
|
||||||
|
}
|
||||||
|
_ => {}
|
||||||
|
},
|
||||||
Event::TextInput { text, .. } => {
|
Event::TextInput { text, .. } => {
|
||||||
self.tasks.push(UpdateResult::Input(text));
|
self.tasks.push(UpdateResult::Input(text));
|
||||||
}
|
}
|
||||||
Event::MouseWheel {
|
Event::MouseWheel {
|
||||||
direction, x, y, ..
|
direction, x, y, ..
|
||||||
} => {
|
} => match direction {
|
||||||
match direction {
|
MouseWheelDirection::Normal => {
|
||||||
MouseWheelDirection::Normal => {
|
self.tasks.push(UpdateResult::Scroll { x, y });
|
||||||
self.tasks.push(UpdateResult::Scroll { x, y });
|
}
|
||||||
}
|
MouseWheelDirection::Flipped => {
|
||||||
MouseWheelDirection::Flipped => {
|
self.tasks.push(UpdateResult::Scroll { x, y: -y });
|
||||||
self.tasks.push(UpdateResult::Scroll { x, y: -y });
|
}
|
||||||
}
|
_ => {
|
||||||
_ => {
|
// ignore
|
||||||
// ignore
|
}
|
||||||
}
|
},
|
||||||
};
|
|
||||||
}
|
|
||||||
Event::Window {
|
Event::Window {
|
||||||
win_event: WindowEvent::Resized(w, h),
|
win_event: WindowEvent::Resized(w, h),
|
||||||
..
|
..
|
||||||
} => {
|
} => self.tasks.push(UpdateResult::WindowResize {
|
||||||
self.tasks.push(UpdateResult::WindowResize {
|
width: w,
|
||||||
width: w,
|
height: h,
|
||||||
height: h,
|
}),
|
||||||
});
|
|
||||||
}
|
|
||||||
_ => {}
|
_ => {}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
pub fn current_working_directory() -> String {
|
||||||
|
env::current_dir().unwrap().to_str().unwrap().to_string()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
@ -26,16 +26,18 @@ pub fn move_caret_left(file_editor: &mut FileEditor) {
|
|||||||
if file_editor.caret().text_position() == 0 {
|
if file_editor.caret().text_position() == 0 {
|
||||||
return;
|
return;
|
||||||
}
|
}
|
||||||
let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() - 1) {
|
let text_character: TextCharacter =
|
||||||
Some(text_character) => text_character,
|
match file.get_character_at(file_editor.caret().text_position() - 1) {
|
||||||
None => return, // EOF
|
Some(text_character) => text_character,
|
||||||
};
|
None => return, // EOF
|
||||||
|
};
|
||||||
let pos = file_editor.caret().position();
|
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);
|
let p = pos.moved(-1, 0, 0);
|
||||||
file_editor
|
file_editor.caret_mut().move_caret(
|
||||||
.caret_mut()
|
p,
|
||||||
.move_caret(p, Point::new(d.x(), d.y()));
|
Point::new(character_destination.x(), character_destination.y()),
|
||||||
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg(test)]
|
#[cfg(test)]
|
||||||
@ -77,6 +79,11 @@ mod test_move_right {
|
|||||||
) -> Result<Rc<Texture>, String> {
|
) -> Result<Rc<Texture>, String> {
|
||||||
Err("skip render character".to_owned())
|
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 {
|
impl ConfigHolder for RendererMock {
|
||||||
@ -163,6 +170,11 @@ mod test_move_left {
|
|||||||
unimplemented!()
|
unimplemented!()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn load_text_tex(
|
fn load_text_tex(
|
||||||
&mut self,
|
&mut self,
|
||||||
_details: &mut TextDetails,
|
_details: &mut TextDetails,
|
||||||
|
@ -15,12 +15,10 @@ pub fn delete_front<R>(file_editor: &mut FileEditor, renderer: &mut R)
|
|||||||
where
|
where
|
||||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||||
{
|
{
|
||||||
let mut buffer: String = if let Some(file) = file_editor.file() {
|
let mut buffer: String = match file_editor.file() {
|
||||||
file
|
Some(file) => file.buffer(),
|
||||||
} else {
|
_ => return,
|
||||||
return;
|
};
|
||||||
}
|
|
||||||
.buffer();
|
|
||||||
let position: CaretPosition = file_editor.caret().position().clone();
|
let position: CaretPosition = file_editor.caret().position().clone();
|
||||||
if position.text_position() == 0 {
|
if position.text_position() == 0 {
|
||||||
return;
|
return;
|
||||||
@ -28,20 +26,16 @@ where
|
|||||||
let c: char = buffer.chars().collect::<Vec<char>>()[position.text_position() - 1];
|
let c: char = buffer.chars().collect::<Vec<char>>()[position.text_position() - 1];
|
||||||
buffer.remove(position.text_position() - 1);
|
buffer.remove(position.text_position() - 1);
|
||||||
let position = match c {
|
let position = match c {
|
||||||
'\n' if position.text_position() > 0 && position.line_number() > 0 => {
|
'\n' if !position.is_first() => position.moved(-1, -1, 0),
|
||||||
position.moved(-1, -1, 0)
|
|
||||||
}
|
|
||||||
'\n' => position.clone(),
|
'\n' => position.clone(),
|
||||||
_ if position.text_position() > 0 => position.moved(-1, 0, 0),
|
_ if position.text_position() > 0 => position.moved(-1, 0, 0),
|
||||||
_ => position.moved(0, 0, 0),
|
_ => position.moved(0, 0, 0),
|
||||||
};
|
};
|
||||||
let move_to = file_editor
|
let move_to = file_editor
|
||||||
.file()
|
.file()
|
||||||
.and_then(|f| f.get_character_at(file_editor.caret().text_position()))
|
.and_then(|f| f.get_character_at(position.text_position()))
|
||||||
.and_then(|character| {
|
.map(|character| character.dest())
|
||||||
let dest: Rect = character.dest();
|
.map(|dest| (position, Point::new(dest.x(), dest.y())));
|
||||||
Some((position, Point::new(dest.x(), dest.y())))
|
|
||||||
});
|
|
||||||
match move_to {
|
match move_to {
|
||||||
Some((position, point)) => file_editor.caret_mut().move_caret(position, point),
|
Some((position, point)) => file_editor.caret_mut().move_caret(position, point),
|
||||||
None => file_editor.caret_mut().reset_caret(),
|
None => file_editor.caret_mut().reset_caret(),
|
||||||
@ -60,10 +54,9 @@ pub fn delete_back<R>(file_editor: &mut FileEditor, renderer: &mut R)
|
|||||||
where
|
where
|
||||||
R: ConfigHolder + CharacterSizeManager + Renderer,
|
R: ConfigHolder + CharacterSizeManager + Renderer,
|
||||||
{
|
{
|
||||||
let file: &EditorFile = if let Some(file) = file_editor.file() {
|
let file: &EditorFile = match file_editor.file() {
|
||||||
file
|
Some(file) => file,
|
||||||
} else {
|
None => return,
|
||||||
return;
|
|
||||||
};
|
};
|
||||||
let mut buffer: String = file.buffer();
|
let mut buffer: String = file.buffer();
|
||||||
let position: usize = file_editor.caret().text_position();
|
let position: usize = file_editor.caret().text_position();
|
||||||
@ -178,6 +171,11 @@ mod tests {
|
|||||||
) -> Result<Rc<Texture>, String> {
|
) -> Result<Rc<Texture>, String> {
|
||||||
Err("skip render character".to_owned())
|
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 {
|
impl ConfigHolder for RendererMock {
|
||||||
|
@ -7,3 +7,7 @@ pub use crate::app::app_state::*;
|
|||||||
pub use crate::app::application::*;
|
pub use crate::app::application::*;
|
||||||
pub use crate::app::caret_manager::*;
|
pub use crate::app::caret_manager::*;
|
||||||
pub use crate::app::file_content_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,
|
details: &mut TextDetails,
|
||||||
font_details: FontDetails,
|
font_details: FontDetails,
|
||||||
) -> Result<Rc<Texture>, String>;
|
) -> Result<Rc<Texture>, String>;
|
||||||
|
|
||||||
|
fn load_image(&mut self, path: String) -> Result<Rc<Texture>, String>;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
@ -117,4 +119,8 @@ impl<'l> Renderer for CanvasRenderer<'l> {
|
|||||||
let tex_manager = self.texture_manager();
|
let tex_manager = self.texture_manager();
|
||||||
tex_manager.load_text(details, font)
|
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)]
|
#[cfg(test)]
|
||||||
pub mod support {
|
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::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::*;
|
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>> {
|
pub fn build_config() -> Arc<RwLock<Config>> {
|
||||||
let mut config = Config::new();
|
let mut config = Config::new();
|
||||||
config.set_theme(config.editor_config().current_theme().clone());
|
config.set_theme(config.editor_config().current_theme().clone());
|
||||||
Arc::new(RwLock::new(config))
|
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 {
|
impl Caret {
|
||||||
pub fn update(&mut self) -> UR {
|
pub fn update(&mut self) -> UR {
|
||||||
self.blink_delay += 1;
|
self.blink_delay += 1;
|
||||||
if self.blink_delay >= 30 {
|
if self.blink_delay >= 15 {
|
||||||
self.blink_delay = 0;
|
self.blink_delay = 0;
|
||||||
self.toggle_state();
|
self.toggle_state();
|
||||||
}
|
}
|
||||||
|
@ -14,32 +14,39 @@ impl CaretPosition {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn text_position(&self) -> usize {
|
pub fn text_position(&self) -> usize {
|
||||||
self.text_position
|
self.text_position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn line_number(&self) -> usize {
|
pub fn line_number(&self) -> usize {
|
||||||
self.line_number
|
self.line_number
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn line_position(&self) -> usize {
|
pub fn line_position(&self) -> usize {
|
||||||
self.line_position
|
self.line_position
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn reset(&mut self) {
|
pub fn reset(&mut self) {
|
||||||
self.text_position = 0;
|
self.text_position = 0;
|
||||||
self.line_number = 0;
|
self.line_number = 0;
|
||||||
self.line_position = 0;
|
self.line_position = 0;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn set_text_position(&mut self, n: usize) {
|
pub fn set_text_position(&mut self, n: usize) {
|
||||||
self.text_position = n;
|
self.text_position = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn set_line_number(&mut self, n: usize) {
|
pub fn set_line_number(&mut self, n: usize) {
|
||||||
self.line_number = n;
|
self.line_number = n;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[inline]
|
||||||
pub fn set_line_position(&mut self, n: usize) {
|
pub fn set_line_position(&mut self, n: usize) {
|
||||||
self.line_position = n;
|
self.line_position = n;
|
||||||
}
|
}
|
||||||
@ -51,6 +58,11 @@ impl CaretPosition {
|
|||||||
line_position: (self.line_position as i32 + line_position) as usize,
|
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)]
|
#[cfg(test)]
|
||||||
|
@ -200,6 +200,7 @@ impl ClickHandler for EditorFileToken {
|
|||||||
mod tests {
|
mod tests {
|
||||||
use super::*;
|
use super::*;
|
||||||
use crate::tests::support::build_config;
|
use crate::tests::support::build_config;
|
||||||
|
use crate::tests::support::CanvasMock;
|
||||||
use rider_lexers::Token;
|
use rider_lexers::Token;
|
||||||
use sdl2::pixels::PixelFormatEnum;
|
use sdl2::pixels::PixelFormatEnum;
|
||||||
use sdl2::render::Texture;
|
use sdl2::render::Texture;
|
||||||
@ -207,9 +208,6 @@ mod tests {
|
|||||||
use sdl2::surface::Surface;
|
use sdl2::surface::Surface;
|
||||||
use sdl2::surface::SurfaceContext;
|
use sdl2::surface::SurfaceContext;
|
||||||
use sdl2::ttf::Font;
|
use sdl2::ttf::Font;
|
||||||
use std::fmt::Debug;
|
|
||||||
use std::fmt::Error;
|
|
||||||
use std::fmt::Formatter;
|
|
||||||
use std::rc::Rc;
|
use std::rc::Rc;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
@ -217,83 +215,6 @@ mod tests {
|
|||||||
// models
|
// 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)]
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
struct RendererMock<'l> {
|
struct RendererMock<'l> {
|
||||||
pub config: Arc<RwLock<Config>>,
|
pub config: Arc<RwLock<Config>>,
|
||||||
@ -317,10 +238,16 @@ mod tests {
|
|||||||
|
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
impl<'l> Renderer for RendererMock<'l> {
|
impl<'l> Renderer for RendererMock<'l> {
|
||||||
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
fn load_font(&mut self, _details: FontDetails) -> Rc<Font> {
|
fn load_font(&mut self, _details: FontDetails) -> Rc<Font> {
|
||||||
unimplemented!("load_font")
|
unimplemented!("load_font")
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#[cfg_attr(tarpaulin, skip)]
|
||||||
|
fn load_image(&mut self, _path: String) -> Result<Rc<Texture>, String> {
|
||||||
|
unimplemented!()
|
||||||
|
}
|
||||||
|
|
||||||
fn load_text_tex(
|
fn load_text_tex(
|
||||||
&mut self,
|
&mut self,
|
||||||
_details: &mut TextDetails,
|
_details: &mut TextDetails,
|
||||||
@ -367,6 +294,7 @@ mod tests {
|
|||||||
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
||||||
token.prepare_ui(&mut renderer);
|
token.prepare_ui(&mut renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_keyword_to_color() {
|
fn assert_keyword_to_color() {
|
||||||
let config = build_config();
|
let config = build_config();
|
||||||
@ -378,6 +306,7 @@ mod tests {
|
|||||||
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
||||||
token.prepare_ui(&mut renderer);
|
token.prepare_ui(&mut renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_string_to_color() {
|
fn assert_string_to_color() {
|
||||||
let config = build_config();
|
let config = build_config();
|
||||||
@ -389,6 +318,7 @@ mod tests {
|
|||||||
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
||||||
token.prepare_ui(&mut renderer);
|
token.prepare_ui(&mut renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_identifier_to_color() {
|
fn assert_identifier_to_color() {
|
||||||
let config = build_config();
|
let config = build_config();
|
||||||
@ -400,6 +330,7 @@ mod tests {
|
|||||||
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
||||||
token.prepare_ui(&mut renderer);
|
token.prepare_ui(&mut renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_literal_to_color() {
|
fn assert_literal_to_color() {
|
||||||
let config = build_config();
|
let config = build_config();
|
||||||
@ -411,6 +342,7 @@ mod tests {
|
|||||||
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
||||||
token.prepare_ui(&mut renderer);
|
token.prepare_ui(&mut renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_comment_to_color() {
|
fn assert_comment_to_color() {
|
||||||
let config = build_config();
|
let config = build_config();
|
||||||
@ -422,6 +354,7 @@ mod tests {
|
|||||||
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
||||||
token.prepare_ui(&mut renderer);
|
token.prepare_ui(&mut renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_operator_to_color() {
|
fn assert_operator_to_color() {
|
||||||
let config = build_config();
|
let config = build_config();
|
||||||
@ -433,6 +366,7 @@ mod tests {
|
|||||||
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
let mut token = EditorFileToken::new(&token_type, false, config.clone());
|
||||||
token.prepare_ui(&mut renderer);
|
token.prepare_ui(&mut renderer);
|
||||||
}
|
}
|
||||||
|
|
||||||
#[test]
|
#[test]
|
||||||
fn assert_separator_to_color() {
|
fn assert_separator_to_color() {
|
||||||
let config = build_config();
|
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::app::UpdateResult as UR;
|
||||||
use crate::ui::*;
|
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;
|
use sdl2::rect::Point;
|
||||||
|
use sdl2::rect::Rect;
|
||||||
pub mod file_editor;
|
use std::mem;
|
||||||
|
use std::sync::*;
|
||||||
|
|
||||||
pub trait FileAccess {
|
pub trait FileAccess {
|
||||||
fn has_file(&self) -> bool;
|
fn has_file(&self) -> bool;
|
||||||
@ -33,3 +52,430 @@ pub trait ScrollableView {
|
|||||||
|
|
||||||
fn scroll(&self) -> Point;
|
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 {
|
pub fn source(&self) -> &Rect {
|
||||||
&self.source
|
&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 {
|
match dir_path {
|
||||||
_ if dir_path == self.path => {
|
_ if dir_path == self.path => {
|
||||||
if !self.opened {
|
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()) {
|
let entries: fs::ReadDir = match fs::read_dir(self.path.clone()) {
|
||||||
Ok(d) => d,
|
Ok(d) => d,
|
||||||
_ => return,
|
_ => return,
|
||||||
@ -167,9 +182,10 @@ impl DirectoryView {
|
|||||||
self.directories.sort_by(|a, b| a.name().cmp(&b.name()));
|
self.directories.sort_by(|a, b| a.name().cmp(&b.name()));
|
||||||
}
|
}
|
||||||
|
|
||||||
fn render_icon<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, dest: &mut Rect)
|
fn render_icon<C, R>(&self, canvas: &mut C, renderer: &mut R, dest: &mut Rect)
|
||||||
where
|
where
|
||||||
T: CanvasAccess,
|
C: CanvasAccess,
|
||||||
|
R: Renderer,
|
||||||
{
|
{
|
||||||
let dir_texture_path = {
|
let dir_texture_path = {
|
||||||
let c = self.config.read().unwrap();
|
let c = self.config.read().unwrap();
|
||||||
@ -178,28 +194,25 @@ impl DirectoryView {
|
|||||||
themes_dir.push(path);
|
themes_dir.push(path);
|
||||||
themes_dir.to_str().unwrap().to_owned()
|
themes_dir.to_str().unwrap().to_owned()
|
||||||
};
|
};
|
||||||
let texture = renderer
|
if let Ok(texture) = renderer.load_image(dir_texture_path.clone()) {
|
||||||
.texture_manager()
|
canvas
|
||||||
.load(dir_texture_path.as_str())
|
.render_image(
|
||||||
.unwrap_or_else(|_| panic!("Failed to load directory entry texture"));
|
texture,
|
||||||
|
self.source.clone(),
|
||||||
canvas
|
Rect::new(dest.x(), dest.y(), self.icon_width, self.icon_height),
|
||||||
.render_image(
|
)
|
||||||
texture,
|
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||||
self.source.clone(),
|
}
|
||||||
Rect::new(dest.x(), dest.y(), self.icon_width, self.icon_height),
|
|
||||||
)
|
|
||||||
.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
|
where
|
||||||
T: CanvasAccess,
|
C: CanvasAccess,
|
||||||
|
R: Renderer + CharacterSizeManager,
|
||||||
{
|
{
|
||||||
let mut d = dest.clone();
|
let mut d = dest.clone();
|
||||||
d.set_x(dest.x() + NAME_MARGIN);
|
d.set_x(dest.x() + NAME_MARGIN);
|
||||||
let font_details = build_font_details(self);
|
let font_details = build_font_details(self);
|
||||||
let font = renderer.font_manager().load(&font_details).unwrap();
|
|
||||||
let name = self.name();
|
let name = self.name();
|
||||||
let config = self.config.read().unwrap();
|
let config = self.config.read().unwrap();
|
||||||
let text_color = config.theme().code_highlighting().title.color();
|
let text_color = config.theme().code_highlighting().title.color();
|
||||||
@ -211,23 +224,24 @@ impl DirectoryView {
|
|||||||
text: c.to_string(),
|
text: c.to_string(),
|
||||||
font: font_details.clone(),
|
font: font_details.clone(),
|
||||||
};
|
};
|
||||||
let text_texture = renderer
|
let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone());
|
||||||
.texture_manager()
|
|
||||||
.load_text(&mut text_details, font.clone())
|
|
||||||
.unwrap();
|
|
||||||
d.set_width(size.width());
|
|
||||||
d.set_height(size.height());
|
|
||||||
|
|
||||||
canvas
|
if let Ok(texture) = maybe_texture {
|
||||||
.render_image(text_texture, self.source.clone(), d.clone())
|
d.set_width(size.width());
|
||||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
d.set_height(size.height());
|
||||||
d.set_x(d.x() + size.width() as i32);
|
|
||||||
|
canvas
|
||||||
|
.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
|
where
|
||||||
T: CanvasAccess,
|
C: CanvasAccess,
|
||||||
|
R: Renderer + CharacterSizeManager,
|
||||||
{
|
{
|
||||||
if !self.expanded {
|
if !self.expanded {
|
||||||
return;
|
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');
|
let size = renderer.load_character_size('W');
|
||||||
self.height = size.height();
|
self.height = size.height();
|
||||||
self.icon_height = size.height();
|
self.icon_height = size.height();
|
||||||
@ -277,19 +294,11 @@ impl DirectoryView {
|
|||||||
self.icon_height,
|
self.icon_height,
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ConfigHolder for DirectoryView {
|
pub fn render<R, C>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
|
||||||
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)
|
|
||||||
where
|
where
|
||||||
T: CanvasAccess,
|
R: Renderer + CharacterSizeManager,
|
||||||
|
C: CanvasAccess,
|
||||||
{
|
{
|
||||||
let dest = self.dest();
|
let dest = self.dest();
|
||||||
let move_point = match context {
|
let move_point = match context {
|
||||||
@ -297,12 +306,15 @@ impl DirectoryView {
|
|||||||
_ => Point::new(0, 0),
|
_ => Point::new(0, 0),
|
||||||
};
|
};
|
||||||
let mut dest = move_render_point(move_point, &dest);
|
let mut dest = move_render_point(move_point, &dest);
|
||||||
self.render_icon::<T>(canvas, renderer, &mut dest);
|
self.render_icon::<C, R>(canvas, renderer, &mut dest);
|
||||||
self.render_name::<T>(canvas, renderer, &mut dest.clone());
|
self.render_name::<C, R>(canvas, renderer, &mut dest.clone());
|
||||||
self.render_children::<T>(canvas, renderer, &mut dest);
|
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 {
|
if self.opened {
|
||||||
for dir in self.directories.iter_mut() {
|
for dir in self.directories.iter_mut() {
|
||||||
dir.prepare_ui(renderer);
|
dir.prepare_ui(renderer);
|
||||||
@ -313,10 +325,8 @@ impl DirectoryView {
|
|||||||
}
|
}
|
||||||
self.calculate_size(renderer);
|
self.calculate_size(renderer);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Update for DirectoryView {
|
pub fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
||||||
fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult {
|
|
||||||
if !path::Path::new(&self.path).exists() {
|
if !path::Path::new(&self.path).exists() {
|
||||||
return UpdateResult::RefreshFsTree;
|
return UpdateResult::RefreshFsTree;
|
||||||
}
|
}
|
||||||
@ -325,30 +335,17 @@ impl Update for DirectoryView {
|
|||||||
dir.update(ticks, context);
|
dir.update(ticks, context);
|
||||||
}
|
}
|
||||||
for file in self.files.iter_mut() {
|
for file in self.files.iter_mut() {
|
||||||
file.update(ticks, context);
|
file.update();
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
UpdateResult::NoOp
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderBox for DirectoryView {
|
pub fn render_start_point(&self) -> Point {
|
||||||
fn render_start_point(&self) -> Point {
|
|
||||||
self.pos.clone()
|
self.pos.clone()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dest(&self) -> Rect {
|
pub fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult {
|
||||||
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 {
|
|
||||||
let dest = self.dest();
|
let dest = self.dest();
|
||||||
let move_point = match context {
|
let move_point = match context {
|
||||||
&UpdateContext::ParentPosition(p) => p.clone(),
|
&UpdateContext::ParentPosition(p) => p.clone(),
|
||||||
@ -381,7 +378,7 @@ impl ClickHandler for DirectoryView {
|
|||||||
for file in self.files.iter_mut() {
|
for file in self.files.iter_mut() {
|
||||||
let context = UpdateContext::ParentPosition(p.clone());
|
let context = UpdateContext::ParentPosition(p.clone());
|
||||||
if file.is_left_click_target(&point, &context) {
|
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);
|
p = p + Point::new(0, file.height() as i32 + CHILD_MARGIN);
|
||||||
}
|
}
|
||||||
@ -389,7 +386,7 @@ impl ClickHandler for DirectoryView {
|
|||||||
UpdateResult::NoOp
|
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 dest = self.dest();
|
||||||
let move_point = match context {
|
let move_point = match context {
|
||||||
UpdateContext::ParentPosition(p) => p.clone(),
|
UpdateContext::ParentPosition(p) => p.clone(),
|
||||||
@ -430,3 +427,385 @@ impl ClickHandler for DirectoryView {
|
|||||||
false
|
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::collections::HashMap;
|
||||||
use std::path;
|
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 {
|
pub struct FileEntry {
|
||||||
name_width: u32,
|
name_width: u32,
|
||||||
icon_width: u32,
|
icon_width: u32,
|
||||||
@ -26,8 +31,8 @@ impl FileEntry {
|
|||||||
name_width: 0,
|
name_width: 0,
|
||||||
icon_width: 0,
|
icon_width: 0,
|
||||||
height: 0,
|
height: 0,
|
||||||
dest: Rect::new(0, 0, 16, 16),
|
dest: Rect::new(0, 0, ICON_DEST_WIDTH, ICON_DEST_HEIGHT),
|
||||||
source: Rect::new(0, 0, 64, 64),
|
source: Rect::new(0, 0, ICON_SRC_WIDTH, ICON_SRC_HEIGHT),
|
||||||
config,
|
config,
|
||||||
char_sizes: HashMap::new(),
|
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
|
where
|
||||||
T: CanvasAccess,
|
C: CanvasAccess,
|
||||||
|
R: Renderer,
|
||||||
{
|
{
|
||||||
let dir_texture_path = {
|
let dir_texture_path = {
|
||||||
let c = self.config.read().unwrap();
|
let c = self.config.read().unwrap();
|
||||||
@ -81,27 +87,25 @@ impl FileEntry {
|
|||||||
themes_dir.push(path);
|
themes_dir.push(path);
|
||||||
themes_dir.to_str().unwrap().to_owned()
|
themes_dir.to_str().unwrap().to_owned()
|
||||||
};
|
};
|
||||||
let texture = renderer
|
let maybe_tex = renderer.load_image(dir_texture_path.clone());
|
||||||
.texture_manager()
|
if let Ok(texture) = maybe_tex {
|
||||||
.load(dir_texture_path.as_str())
|
dest.set_width(ICON_DEST_WIDTH);
|
||||||
.unwrap_or_else(|_| panic!("Failed to load directory entry texture"));
|
dest.set_height(ICON_DEST_HEIGHT);
|
||||||
dest.set_width(16);
|
canvas
|
||||||
dest.set_height(16);
|
.render_image(texture, self.source.clone(), dest.clone())
|
||||||
canvas
|
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
||||||
.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
|
where
|
||||||
T: CanvasAccess,
|
C: CanvasAccess,
|
||||||
|
R: Renderer,
|
||||||
{
|
{
|
||||||
let mut d = dest.clone();
|
let mut d = dest.clone();
|
||||||
d.set_x(dest.x() + NAME_MARGIN);
|
d.set_x(dest.x() + NAME_MARGIN);
|
||||||
|
|
||||||
let font_details = build_font_details(self);
|
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();
|
let name = self.name();
|
||||||
|
|
||||||
for c in name.chars() {
|
for c in name.chars() {
|
||||||
@ -115,31 +119,24 @@ impl FileEntry {
|
|||||||
text: c.to_string(),
|
text: c.to_string(),
|
||||||
font: font_details.clone(),
|
font: font_details.clone(),
|
||||||
};
|
};
|
||||||
let text_texture = texture_manager
|
let maybe_texture = renderer.load_text_tex(&mut text_details, font_details.clone());
|
||||||
.load_text(&mut text_details, font.clone())
|
|
||||||
.unwrap();
|
|
||||||
d.set_width(size.width());
|
|
||||||
d.set_height(size.height());
|
|
||||||
|
|
||||||
canvas
|
if let Ok(texture) = maybe_texture {
|
||||||
.render_image(text_texture, self.source.clone(), d.clone())
|
d.set_width(size.width());
|
||||||
.unwrap_or_else(|_| panic!("Failed to draw directory entry texture"));
|
d.set_height(size.height());
|
||||||
d.set_x(d.x() + size.width() as i32)
|
|
||||||
|
canvas
|
||||||
|
.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 {
|
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RenderContext)
|
||||||
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)
|
|
||||||
where
|
where
|
||||||
T: CanvasAccess,
|
C: CanvasAccess,
|
||||||
|
R: Renderer,
|
||||||
{
|
{
|
||||||
let mut dest = match context {
|
let mut dest = match context {
|
||||||
&RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
&RenderContext::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||||
@ -149,48 +146,41 @@ impl FileEntry {
|
|||||||
self.render_name(canvas, renderer, &mut dest.clone());
|
self.render_name(canvas, renderer, &mut dest.clone());
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn prepare_ui(&mut self, renderer: &mut CanvasRenderer) {
|
pub fn prepare_ui<R>(&mut self, renderer: &mut R)
|
||||||
let w_rect = get_text_character_rect('W', renderer).unwrap();
|
where
|
||||||
|
R: Renderer + CharacterSizeManager,
|
||||||
|
{
|
||||||
|
let w_rect = renderer.load_character_size('W');
|
||||||
self.char_sizes.insert('W', w_rect.clone());
|
self.char_sizes.insert('W', w_rect.clone());
|
||||||
self.height = w_rect.height();
|
self.height = w_rect.height();
|
||||||
self.icon_width = w_rect.height();
|
self.icon_width = w_rect.height();
|
||||||
self.name_width = 0;
|
self.name_width = 0;
|
||||||
|
|
||||||
for c in self.name().chars() {
|
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.char_sizes.insert(c, size);
|
||||||
self.name_width += size.width();
|
self.name_width += size.width();
|
||||||
}
|
}
|
||||||
self.dest.set_width(w_rect.height());
|
self.dest.set_width(w_rect.height());
|
||||||
self.dest.set_height(w_rect.height());
|
self.dest.set_height(w_rect.height());
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Update for FileEntry {
|
pub fn update(&mut self) -> UpdateResult {
|
||||||
fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult {
|
|
||||||
if !path::Path::new(&self.path).exists() {
|
if !path::Path::new(&self.path).exists() {
|
||||||
return UpdateResult::RefreshFsTree;
|
return UpdateResult::RefreshFsTree;
|
||||||
}
|
}
|
||||||
UpdateResult::NoOp
|
UpdateResult::NoOp
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderBox for FileEntry {
|
pub fn render_start_point(&self) -> Point {
|
||||||
fn render_start_point(&self) -> Point {
|
|
||||||
self.dest.top_left()
|
self.dest.top_left()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dest(&self) -> Rect {
|
pub fn on_left_click(&mut self) -> UpdateResult {
|
||||||
self.dest.clone()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
impl ClickHandler for FileEntry {
|
|
||||||
fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UpdateResult {
|
|
||||||
UpdateResult::OpenFile(self.path.clone())
|
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(
|
let dest = Rect::new(
|
||||||
self.dest.x(),
|
self.dest.x(),
|
||||||
self.dest.y(),
|
self.dest.y(),
|
||||||
@ -204,3 +194,270 @@ impl ClickHandler for FileEntry {
|
|||||||
rect.contains_point(point.clone())
|
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::*;
|
||||||
use crate::ui::{RenderContext as RC, UpdateContext as UC};
|
use crate::ui::{RenderContext as RC, UpdateContext as UC};
|
||||||
use rider_config::ConfigAccess;
|
use rider_config::ConfigAccess;
|
||||||
|
use rider_config::ConfigHolder;
|
||||||
use sdl2::pixels::Color;
|
use sdl2::pixels::Color;
|
||||||
use sdl2::rect::{Point, Rect};
|
use sdl2::rect::{Point, Rect};
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
@ -56,7 +57,10 @@ impl OpenFile {
|
|||||||
self.root_path.clone()
|
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);
|
self.directory_view.open_directory(dir_path, renderer);
|
||||||
{
|
{
|
||||||
let dest = self.directory_view.dest();
|
let dest = self.directory_view.dest();
|
||||||
@ -73,10 +77,8 @@ impl OpenFile {
|
|||||||
pub fn full_rect(&self) -> &Rect {
|
pub fn full_rect(&self) -> &Rect {
|
||||||
&self.full_dest
|
&self.full_dest
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ScrollableView for OpenFile {
|
pub fn scroll_by(&mut self, x: i32, y: i32) {
|
||||||
fn scroll_by(&mut self, x: i32, y: i32) {
|
|
||||||
let read_config = self.config.read().unwrap();
|
let read_config = self.config.read().unwrap();
|
||||||
|
|
||||||
let value_x = read_config.scroll().speed() * x;
|
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(
|
Point::new(
|
||||||
-self.horizontal_scroll_bar.scroll_value(),
|
-self.horizontal_scroll_bar.scroll_value(),
|
||||||
-self.vertical_scroll_bar.scroll_value(),
|
-self.vertical_scroll_bar.scroll_value(),
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl Update for OpenFile {
|
pub fn update(&mut self, ticks: i32, context: &UC) -> UR {
|
||||||
fn update(&mut self, ticks: i32, context: &UC) -> UR {
|
|
||||||
let (window_width, window_height, color, scroll_width, scroll_margin) = {
|
let (window_width, window_height, color, scroll_width, scroll_margin) = {
|
||||||
let c = self.config.read().unwrap();
|
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);
|
.set_x((window_width / 2) as i32 - (self.dest.width() / 2) as i32);
|
||||||
self.dest
|
self.dest
|
||||||
.set_y((window_height / 2) as i32 - (self.dest.height() / 2) as i32);
|
.set_y((window_height / 2) as i32 - (self.dest.height() / 2) as i32);
|
||||||
|
|
||||||
self.background_color = color;
|
self.background_color = color;
|
||||||
|
|
||||||
// Scroll bars
|
// Scroll bars
|
||||||
@ -143,21 +144,19 @@ impl Update for OpenFile {
|
|||||||
// End
|
// End
|
||||||
UR::NoOp
|
UR::NoOp
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
#[cfg_attr(tarpaulin, skip)]
|
pub fn render<C, R>(&self, canvas: &mut C, renderer: &mut R, context: &RC)
|
||||||
impl OpenFile {
|
|
||||||
pub fn render<T>(&self, canvas: &mut T, renderer: &mut CanvasRenderer, context: &RC)
|
|
||||||
where
|
where
|
||||||
T: CanvasAccess,
|
C: CanvasAccess,
|
||||||
|
R: Renderer + CharacterSizeManager + ConfigHolder,
|
||||||
{
|
{
|
||||||
let dest = match context {
|
let dest = match context {
|
||||||
RC::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
RC::RelativePosition(p) => move_render_point(p.clone(), &self.dest),
|
||||||
_ => self.dest,
|
_ => self.dest.clone(),
|
||||||
};
|
};
|
||||||
|
|
||||||
// Background
|
// Background
|
||||||
// canvas.set_clip_rect(dest.clone());
|
canvas.set_clipping(dest.clone());
|
||||||
canvas
|
canvas
|
||||||
.render_rect(dest, self.background_color)
|
.render_rect(dest, self.background_color)
|
||||||
.unwrap_or_else(|_| panic!("Failed to render open file modal background!"));
|
.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);
|
self.directory_view.prepare_ui(renderer);
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl RenderBox for OpenFile {
|
pub fn render_start_point(&self) -> Point {
|
||||||
fn render_start_point(&self) -> Point {
|
|
||||||
self.dest.top_left()
|
self.dest.top_left()
|
||||||
}
|
}
|
||||||
|
|
||||||
fn dest(&self) -> Rect {
|
pub fn dest(&self) -> Rect {
|
||||||
self.dest.clone()
|
self.dest.clone()
|
||||||
}
|
}
|
||||||
}
|
|
||||||
|
|
||||||
impl ClickHandler for OpenFile {
|
pub fn on_left_click(&mut self, point: &Point, context: &UC) -> UR {
|
||||||
fn on_left_click(&mut self, point: &Point, context: &UC) -> UR {
|
|
||||||
let dest = match context {
|
let dest = match context {
|
||||||
UC::ParentPosition(p) => move_render_point(*p, &self.dest),
|
UC::ParentPosition(p) => move_render_point(*p, &self.dest),
|
||||||
_ => self.dest,
|
_ => self.dest,
|
||||||
@ -221,15 +219,198 @@ impl ClickHandler for OpenFile {
|
|||||||
res
|
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 {
|
let dest = match context {
|
||||||
UC::ParentPosition(p) => move_render_point(p.clone(), &self.dest),
|
UC::ParentPosition(p) => move_render_point(p.clone(), &self.dest),
|
||||||
_ => self.dest.clone(),
|
_ => self.dest.clone(),
|
||||||
};
|
};
|
||||||
let context = UC::ParentPosition(
|
let p =
|
||||||
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll(),
|
dest.top_left() + Point::new(CONTENT_MARGIN_LEFT, CONTENT_MARGIN_TOP) + self.scroll();
|
||||||
);
|
let context = UC::ParentPosition(p);
|
||||||
self.directory_view.is_left_click_target(point, &context);
|
if self.directory_view.is_left_click_target(point, &context) {
|
||||||
true
|
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