From 7d6a5887fad5a83fe649f3632755d3680977d634 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Adrian=20Wo=C5=BAniak?= Date: Sun, 6 Jan 2019 18:25:20 +0100 Subject: [PATCH] Add scrollbar (#1) * Refactor code, prepare for scrollbar * Move file to file editor, fix rust lexer new line, refactor code, fix caret render position * Fix caret position at new lines * Scroll content --- .gitignore | 1 + src/app/app_state.rs | 136 ++++++--------- src/app/application.rs | 234 +++++++++++++++++++++++++ src/app/caret_manager.rs | 88 +++++----- src/app/file_content_manager.rs | 121 ++++++------- src/app/mod.rs | 219 +---------------------- src/config/creator.rs | 5 + src/config/directories.rs | 8 + src/config/mod.rs | 7 + src/lexer/mod.rs | 2 +- src/lexer/rust_lang.rs | 2 +- src/themes/caret_color.rs | 12 +- src/ui/caret.rs | 113 ++++++------ src/ui/file/editor_file.rs | 113 ++++++++---- src/ui/file/editor_file_section.rs | 94 +++++++--- src/ui/file/editor_file_token.rs | 125 +++++++------ src/ui/file/mod.rs | 4 + src/ui/file_editor.rs | 270 +++++++++++++++++++++++++++++ src/ui/menu_bar.rs | 60 ++++++- src/ui/mod.rs | 45 ++++- src/ui/project_tree/mod.rs | 1 + src/ui/text_character.rs | 157 +++++++++-------- 22 files changed, 1142 insertions(+), 675 deletions(-) create mode 100644 src/app/application.rs create mode 100644 src/ui/file_editor.rs create mode 100644 src/ui/project_tree/mod.rs diff --git a/.gitignore b/.gitignore index 66fb0ca..a626103 100644 --- a/.gitignore +++ b/.gitignore @@ -2,3 +2,4 @@ **/*.rs.bk .idea log +.rider diff --git a/src/app/app_state.rs b/src/app/app_state.rs index d96e6e5..b44b397 100644 --- a/src/app/app_state.rs +++ b/src/app/app_state.rs @@ -1,6 +1,6 @@ use crate::app::caret_manager; use crate::app::file_content_manager; -use crate::app::{UpdateResult, WindowCanvas}; +use crate::app::{UpdateResult, WindowCanvas as WC}; use crate::config::Config; use crate::renderer::Renderer; use crate::ui::caret::Caret; @@ -19,9 +19,8 @@ use std::sync::Arc; pub struct AppState { menu_bar: MenuBar, files: Vec, - current_file: usize, - caret: Caret, config: Rc, + file_editor: FileEditor, } impl AppState { @@ -29,19 +28,29 @@ impl AppState { Self { menu_bar: MenuBar::new(config.clone()), files: vec![], - current_file: 0, - caret: Caret::new(config.clone()), + file_editor: FileEditor::new( + Rect::new( + config.editor_left_margin(), + config.editor_top_margin(), + config.width() - config.editor_left_margin() as u32, + config.height() - config.editor_top_margin() as u32, + ), + config.clone(), + ), config, } } - pub fn open_file(&mut self, file_path: String) { + pub fn open_file(&mut self, file_path: String, renderer: &mut Renderer) { use std::fs::read_to_string; if let Ok(buffer) = read_to_string(&file_path) { - let file = EditorFile::new(file_path.clone(), buffer, self.config.clone()); - self.current_file = self.files.len(); - self.files.push(file); + let mut file = EditorFile::new(file_path.clone(), buffer, self.config.clone()); + file.prepare_ui(renderer); + match self.file_editor.open_file(file) { + Some(old) => self.files.push(old), + _ => (), + } } else { eprintln!("Failed to open file: {}", file_path); }; @@ -51,102 +60,55 @@ impl AppState { &self.config } - pub fn caret(&self) -> &Caret { - &self.caret + pub fn file_editor(&self) -> &FileEditor { + &self.file_editor } - pub fn caret_mut(&mut self) -> &mut Caret { - &mut self.caret - } - - pub fn current_file(&self) -> Option<&EditorFile> { - self.files.get(self.current_file) - } - - pub fn current_file_mut(&mut self) -> Option<&mut EditorFile> { - self.files.get_mut(self.current_file) - } - - fn on_editor_clicked(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult { - let current_file: &mut EditorFile = if let Some(current_file) = self.current_file_mut() { - current_file - } else { - return UpdateResult::NoOp; - }; - if !current_file.is_left_click_target(point) { - return UpdateResult::NoOp; - } - video_subsystem.text_input().start(); - match current_file.on_left_click(point) { - UpdateResult::MoveCaret(rect, position) => { - self.caret - .move_caret(position, Point::new(rect.x(), rect.y())); - } - _ => (), - }; - - UpdateResult::NoOp - } - - pub fn move_caret(&mut self, dir: MoveDirection) { - match dir { - MoveDirection::Left => {} - MoveDirection::Right => caret_manager::move_caret_right(self), - MoveDirection::Up => {} - MoveDirection::Down => {} - } - } - - pub fn delete_front(&mut self) { - file_content_manager::delete_front(self); - } - - pub fn delete_back(&mut self) { - file_content_manager::delete_back(self); - } - - pub fn insert_text(&mut self, text: String, renderer: &mut Renderer) { - file_content_manager::insert_text(self, text, renderer); - } - - pub fn insert_new_line(&mut self, renderer: &mut Renderer) { - file_content_manager::insert_new_line(self, renderer); - } - - pub fn replace_current_file(&mut self, file: EditorFile) { - self.files[self.current_file] = file; + pub fn file_editor_mut(&mut self) -> &mut FileEditor { + &mut self.file_editor } } impl Render for AppState { - fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - self.menu_bar.render(canvas, renderer); - if let Some(file) = self.current_file_mut() { - file.render(canvas, renderer); - } - self.caret.render(canvas, renderer); - UpdateResult::NoOp + fn render(&self, canvas: &mut WC, renderer: &mut Renderer, _parent: Parent) -> UpdateResult { + self.file_editor.render(canvas, renderer, None); + self.menu_bar.render(canvas, renderer, None) + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { + self.menu_bar.prepare_ui(renderer); + self.file_editor.prepare_ui(renderer); } } impl Update for AppState { - fn update(&mut self, ticks: i32) -> UpdateResult { - self.menu_bar.update(ticks); - if let Some(file) = self.files.get_mut(self.current_file) { - file.update(ticks); - } - self.caret.update(ticks); + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult { + self.menu_bar.update(ticks, context); + self.file_editor.update(ticks, context); UpdateResult::NoOp } } impl AppState { pub fn on_left_click(&mut self, point: &Point, video_subsystem: &mut VS) -> UpdateResult { - if self.menu_bar.is_left_click_target(point) { + if self + .menu_bar + .is_left_click_target(point, &UpdateContext::Nothing) + { video_subsystem.text_input().stop(); - return self.menu_bar.on_left_click(point); + return self.menu_bar.on_left_click(point, &UpdateContext::Nothing); + } else { + if !self + .file_editor + .is_left_click_target(point, &UpdateContext::Nothing) + { + return UpdateResult::NoOp; + } else { + video_subsystem.text_input().start(); + self.file_editor + .on_left_click(point, &UpdateContext::Nothing); + } } - self.on_editor_clicked(point, video_subsystem); UpdateResult::NoOp } diff --git a/src/app/application.rs b/src/app/application.rs new file mode 100644 index 0000000..e844c2e --- /dev/null +++ b/src/app/application.rs @@ -0,0 +1,234 @@ +pub use crate::app::app_state::AppState; +pub use crate::config::Config; +pub use crate::renderer::Renderer; +use crate::themes::*; +use crate::ui::caret::{CaretPosition, MoveDirection}; +use crate::ui::*; + +use std::rc::Rc; +use std::thread::sleep; +use std::time::Duration; + +use sdl2::event::Event; +use sdl2::hint; +use sdl2::keyboard::{Keycode, Mod}; +use sdl2::mouse::*; +use sdl2::pixels::{Color, PixelFormatEnum}; +use sdl2::rect::{Point, Rect}; +use sdl2::render::Canvas; +use sdl2::rwops::RWops; +use sdl2::surface::Surface; +use sdl2::ttf::Sdl2TtfContext; +use sdl2::video::Window; +use sdl2::EventPump; +use sdl2::{Sdl, TimerSubsystem, VideoSubsystem}; + +pub type WindowCanvas = Canvas; + +#[derive(PartialEq, Clone, Debug)] +pub enum UpdateResult { + NoOp, + Stop, + RefreshPositions, + MouseLeftClicked(Point), + MoveCaret(Rect, CaretPosition), + DeleteFront, + DeleteBack, + Input(String), + InsertNewLine, + MoveCaretLeft, + MoveCaretRight, + MoveCaretUp, + MoveCaretDown, + Scroll { x: i32, y: i32 }, +} + +pub enum Task { + OpenFile { file_path: String }, +} + +pub struct Application { + config: Rc, + clear_color: Color, + sdl_context: Sdl, + canvas: WindowCanvas, + video_subsystem: VideoSubsystem, + tasks: Vec, +} + +impl Application { + pub fn new() -> Self { + let config = Rc::new(Config::new()); + let sdl_context = sdl2::init().unwrap(); + + hint::set("SDL_GL_MULTISAMPLEBUFFERS", "1"); + hint::set("SDL_GL_MULTISAMPLESAMPLES", "8"); + hint::set("SDL_GL_ACCELERATED_VISUAL", "1"); + hint::set("SDL_HINT_RENDER_SCALE_QUALITY", "2"); + hint::set("SDL_HINT_VIDEO_ALLOW_SCREENSAVER", "1"); + + let video_subsystem = sdl_context.video().unwrap(); + + let mut window: Window = video_subsystem + .window("Rider", config.width(), config.height()) + .position_centered() + .resizable() + .opengl() + .build() + .unwrap(); + let icon_bytes = include_bytes!("../../assets/gear-64x64.bmp").clone(); + let mut rw = RWops::from_bytes(&icon_bytes).unwrap(); + let mut icon = Surface::load_bmp_rw(&mut rw).unwrap(); + window.set_icon(&mut icon); + + let canvas = window.into_canvas().accelerated().build().unwrap(); + + Self { + sdl_context, + video_subsystem, + canvas, + tasks: vec![], + clear_color: config.theme().background().into(), + config, + } + } + + pub fn init(&mut self) { + self.clear(); + } + + pub fn run(&mut self) { + let mut timer: TimerSubsystem = self.sdl_context.timer().unwrap(); + let mut event_pump = self.sdl_context.event_pump().unwrap(); + let font_context = sdl2::ttf::init().unwrap(); + let texture_creator = self.canvas.texture_creator(); + let sleep_time = Duration::new(0, 1_000_000_000u32 / 60); + let mut app_state = AppState::new(self.config.clone()); + let mut renderer = Renderer::new(self.config.clone(), &font_context, &texture_creator); + app_state.prepare_ui(&mut renderer); + + 'running: loop { + match self.handle_events(&mut event_pump) { + UpdateResult::Stop => break 'running, + UpdateResult::RefreshPositions => (), + UpdateResult::NoOp => (), + UpdateResult::MoveCaret(_, _pos) => (), + UpdateResult::MouseLeftClicked(point) => { + app_state.on_left_click(&point, &mut self.video_subsystem); + } + UpdateResult::DeleteFront => { + app_state.file_editor_mut().delete_front(&mut renderer); + } + UpdateResult::DeleteBack => { + app_state.file_editor_mut().delete_back(&mut renderer); + } + UpdateResult::Input(text) => { + app_state.file_editor_mut().insert_text(text, &mut renderer); + } + UpdateResult::InsertNewLine => { + app_state.file_editor_mut().insert_new_line(&mut renderer); + } + UpdateResult::MoveCaretLeft => { + app_state.file_editor_mut().move_caret(MoveDirection::Left); + } + UpdateResult::MoveCaretRight => { + app_state.file_editor_mut().move_caret(MoveDirection::Right); + } + UpdateResult::MoveCaretUp => { + app_state.file_editor_mut().move_caret(MoveDirection::Up); + } + UpdateResult::MoveCaretDown => { + app_state.file_editor_mut().move_caret(MoveDirection::Down); + } + UpdateResult::Scroll { x, y } => { + app_state.file_editor_mut().scroll_to(x, y); + } + } + for task in self.tasks.iter() { + match task { + Task::OpenFile { file_path } => { + use crate::ui::file::editor_file::*; + app_state.open_file(file_path.clone(), &mut renderer); + } + } + } + self.tasks.clear(); + + self.clear(); + + app_state.update(timer.ticks() as i32, &UpdateContext::Nothing); + app_state.render(&mut self.canvas, &mut renderer, None); + + self.present(); + sleep(sleep_time); + } + } + + pub fn open_file(&mut self, file_path: String) { + self.tasks.push(Task::OpenFile { file_path }); + } + + fn present(&mut self) { + self.canvas.present(); + } + + fn clear(&mut self) { + self.canvas.set_draw_color(self.clear_color.clone()); + self.canvas.clear(); + } + + fn handle_events(&mut self, event_pump: &mut EventPump) -> UpdateResult { + for event in event_pump.poll_iter() { + match event { + Event::Quit { .. } => return UpdateResult::Stop, + Event::MouseButtonUp { + mouse_btn, x, y, .. + } => match mouse_btn { + MouseButton::Left => return UpdateResult::MouseLeftClicked(Point::new(x, y)), + _ => (), + }, + Event::KeyDown { keycode, .. } => { + let keycode = if keycode.is_some() { + keycode.unwrap() + } else { + return UpdateResult::NoOp; + }; + match keycode { + Keycode::Backspace => return UpdateResult::DeleteFront, + Keycode::Delete => return UpdateResult::DeleteBack, + Keycode::KpEnter | Keycode::Return => return UpdateResult::InsertNewLine, + Keycode::Left => return UpdateResult::MoveCaretLeft, + Keycode::Right => return UpdateResult::MoveCaretRight, + Keycode::Up => return UpdateResult::MoveCaretUp, + Keycode::Down => return UpdateResult::MoveCaretDown, + _ => UpdateResult::NoOp, + }; + } + Event::TextInput { text, .. } => { + return UpdateResult::Input(text); + } + Event::MouseWheel { + direction, x, y, .. + } => { + match direction { + MouseWheelDirection::Normal => { + return UpdateResult::Scroll { x, y }; + } + MouseWheelDirection::Flipped => { + return UpdateResult::Scroll { x, y: -y }; + } + _ => { + // ignore + } + }; + } + _ => (), + } + } + UpdateResult::NoOp + } + + pub fn config(&self) -> &Rc { + &self.config + } +} diff --git a/src/app/caret_manager.rs b/src/app/caret_manager.rs index 25243e3..2058ae7 100644 --- a/src/app/caret_manager.rs +++ b/src/app/caret_manager.rs @@ -1,54 +1,64 @@ use crate::app::AppState; -use crate::ui::file::editor_file::EditorFile; -use crate::ui::text_character::TextCharacter; +use crate::ui::*; use sdl2::rect::{Point, Rect}; -pub fn move_caret_right(app_state: &mut AppState) { - let file: &EditorFile = match app_state.current_file() { +pub fn move_caret_right(file_editor: &mut FileEditor) { + let file: &EditorFile = match file_editor.file() { None => return, Some(f) => f, }; - let line = match file.get_character_at(app_state.caret().text_position()) { - Some(t) if t.is_new_line() => app_state.caret().line_number().clone() + 1, - Some(_) => app_state.caret().line_number().clone(), - None => 0, - }; - - let characters: Vec<&TextCharacter> = match file.get_line(&line) { - None => return, - Some(characters) => characters, - }; - let mut idx = 0; - for (i, c) in characters.iter().enumerate() { - if c.position() == app_state.caret().text_position() { - idx = i + 1; - break; - } - } - let text_character: &TextCharacter = match characters.get(idx) { + let c: TextCharacter = match file.get_character_at(file_editor.caret().text_position() + 1) { Some(text_character) => text_character, - None => return, + None => return, // EOF }; - let line = line - app_state.caret().line_number(); - let pos = app_state.caret().position().moved(1, line as i32, 0); - let mut d: Rect = text_character.dest().clone(); - if text_character.is_new_line() && idx > 0 { - let prev = match characters.get(idx - 1) { - Some(c) => c, - _ => return, - }; - d = prev.dest().clone(); - d.set_x(d.x() + d.width() as i32); - } - app_state + let caret_rect = file_editor.caret().dest().clone(); + let pos = file_editor.caret().position(); + let (d, p): (Rect, CaretPosition) = match ( + c.is_last_in_line(), + c.is_new_line(), + c.dest().y() == caret_rect.y(), + ) { + (true, true, false) => { + let prev: TextCharacter = if c.position() != 0 { + file.get_character_at(c.position() - 1).unwrap_or(c.clone()) + } else { + c.clone() + }; + let mut dest = prev.dest().clone(); + dest.set_x(dest.x() + dest.width() as i32); + (dest, pos.moved(1, 0, 0)) + } + (false, true, false) => { + let prev: TextCharacter = if c.position() != 0 { + file.get_character_at(c.position() - 1).unwrap_or(c.clone()) + } else { + c.clone() + }; + let mut dest = prev.dest().clone(); + if !prev.is_new_line() { + dest.set_x(dest.x() + dest.width() as i32); + } + (dest, pos.moved(1, 0, 0)) + } + (true, false, false) => { + // move after character, stay on current line + (c.dest().clone(), pos.moved(1, 0, 0)) + } + (true, false, true) => { + // move to new line + (c.dest().clone(), pos.moved(1, 0, 0)) + } + _ => (c.dest().clone(), pos.moved(1, 0, 0)), + }; + file_editor .caret_mut() - .move_caret(pos, Point::new(d.x(), d.y())); + .move_caret(p, Point::new(d.x(), d.y())); } -pub fn move_caret_left(app_state: &mut AppState) { - let _file: &EditorFile = match app_state.current_file() { +pub fn move_caret_left(file_editor: &mut FileEditor) { + let _file: &EditorFile = match file_editor.file() { None => return, Some(f) => f, }; - let _line = app_state.caret().line_number(); + let _line = file_editor.caret().line_number(); } diff --git a/src/app/file_content_manager.rs b/src/app/file_content_manager.rs index bda7049..024c2b6 100644 --- a/src/app/file_content_manager.rs +++ b/src/app/file_content_manager.rs @@ -1,36 +1,22 @@ -use crate::app::AppState; +use crate::app::*; use crate::renderer::Renderer; -use crate::ui::caret::Caret; -use crate::ui::caret::CaretPosition; -use crate::ui::file::editor_file::EditorFile; -use crate::ui::get_text_character_rect; -use crate::ui::text_character::TextCharacter; -use sdl2::rect::Point; -use sdl2::rect::Rect; +use crate::ui::*; +use sdl2::rect::*; -fn get_character_at(app_state: &mut AppState, index: usize) -> Option<&TextCharacter> { - match app_state.current_file() { - None => return None, - Some(f) => f, - } - .get_character_at(index) +fn current_file_path(file_editor: &mut FileEditor) -> String { + file_editor + .file() + .map_or_else(|| String::new(), |f| f.path()) } -fn current_file_path(app_state: &mut AppState) -> String { - match app_state.current_file() { - Some(f) => f.path(), - _ => String::new(), - } -} - -pub fn delete_front(app_state: &mut AppState) { - let mut buffer: String = if let Some(file) = app_state.current_file() { +pub fn delete_front(file_editor: &mut FileEditor, renderer: &mut Renderer) { + let mut buffer: String = if let Some(file) = file_editor.file() { file } else { return; } .buffer(); - let position: CaretPosition = app_state.caret().position().clone(); + let position: CaretPosition = file_editor.caret().position().clone(); if position.text_position() == 0 { return; } @@ -44,100 +30,107 @@ pub fn delete_front(app_state: &mut AppState) { _ if position.text_position() > 0 => position.moved(-1, 0, 0), _ => position.moved(0, 0, 0), }; - - let move_to = match get_character_at(app_state, position.text_position()) { - Some(character) => { + let move_to = file_editor + .file() + .and_then(|f| f.get_character_at(file_editor.caret().text_position())) + .and_then(|character| { let dest: &Rect = character.dest(); Some((position, Point::new(dest.x(), dest.y()))) - } - _ => None, - }; + }); match move_to { - Some((position, point)) => app_state.caret_mut().move_caret(position, point), - None => app_state.caret_mut().reset_caret(), + Some((position, point)) => file_editor.caret_mut().move_caret(position, point), + None => file_editor.caret_mut().reset_caret(), }; - let new_file = EditorFile::new( - current_file_path(app_state), + let mut new_file = EditorFile::new( + current_file_path(file_editor), buffer, - app_state.config().clone(), + file_editor.config().clone(), ); - app_state.replace_current_file(new_file); + new_file.prepare_ui(renderer); + file_editor.replace_current_file(new_file); } -pub fn delete_back(app_state: &mut AppState) { - let file: &EditorFile = if let Some(file) = app_state.current_file() { +pub fn delete_back(file_editor: &mut FileEditor, renderer: &mut Renderer) { + let file: &EditorFile = if let Some(file) = file_editor.file() { file } else { return; }; let mut buffer: String = file.buffer(); - let position: usize = app_state.caret().text_position(); + let position: usize = file_editor.caret().text_position(); if position >= buffer.len() { return; } buffer.remove(position); - let new_file = EditorFile::new(file.path(), buffer, app_state.config().clone()); - app_state.replace_current_file(new_file); + let mut new_file = EditorFile::new(file.path(), buffer, file_editor.config().clone()); + new_file.prepare_ui(renderer); + file_editor.replace_current_file(new_file); } -pub fn insert_text(app_state: &mut AppState, text: String, renderer: &mut Renderer) { - let mut buffer: String = if let Some(file) = app_state.current_file() { - file - } else { +pub fn insert_text(file_editor: &mut FileEditor, text: String, renderer: &mut Renderer) { + let mut buffer: String = file_editor.file().map_or(String::new(), |f| f.buffer()); + if buffer.is_empty() { return; } - .buffer(); - let current = match get_character_at(app_state, app_state.caret().text_position()) { + + let current = match file_editor + .file() + .and_then(|file| file.get_character_at(file_editor.caret().text_position())) + { Some(c) => c, _ => return, }; let mut pos = Point::new(current.dest().x(), current.dest().y()); - let mut position: CaretPosition = app_state.caret().position().clone(); + let mut position: CaretPosition = file_editor.caret().position().clone(); for character in text.chars() { buffer.insert(position.text_position(), character); if let Some(rect) = get_text_character_rect(character, renderer) { pos = pos + Point::new(rect.width() as i32, 0); position = position.moved(1, 0, 0); - app_state.caret_mut().move_caret(position, pos.clone()); + file_editor.caret_mut().move_caret(position, pos.clone()); } } - let new_file = EditorFile::new( - current_file_path(app_state), + let mut new_file = EditorFile::new( + file_editor.file().map_or(String::new(), |f| f.path()), buffer, - app_state.config().clone(), + file_editor.config().clone(), ); - - app_state.replace_current_file(new_file); + new_file.prepare_ui(renderer); + file_editor.replace_current_file(new_file); } -pub fn insert_new_line(app_state: &mut AppState, renderer: &mut Renderer) { - let mut buffer: String = if let Some(file) = app_state.current_file() { +pub fn insert_new_line(file_editor: &mut FileEditor, renderer: &mut Renderer) { + let mut buffer: String = if let Some(file) = file_editor.file() { file } else { return; } .buffer(); - let current = match get_character_at(app_state, app_state.caret().text_position()) { + let current = match file_editor + .file() + .and_then(|file| file.get_character_at(file_editor.caret().text_position())) + { Some(c) => c, _ => return, }; let mut pos = Point::new(current.dest().x(), current.dest().y()); - let mut position: CaretPosition = app_state.caret().position().clone(); + let mut position: CaretPosition = file_editor.caret().position().clone(); buffer.insert(position.text_position(), '\n'); if let Some(rect) = get_text_character_rect('\n', renderer) { pos = Point::new( - app_state.config().editor_left_margin(), + file_editor.config().editor_left_margin(), pos.y() + rect.height() as i32, ); position = position.moved(0, 1, 0); - app_state.caret_mut().move_caret(position, pos.clone()); + file_editor.caret_mut().move_caret(position, pos.clone()); } - let new_file = EditorFile::new( - current_file_path(app_state), + let mut new_file = EditorFile::new( + current_file_path(file_editor), buffer, - app_state.config().clone(), + file_editor.config().clone(), ); - app_state.replace_current_file(new_file); + new_file.prepare_ui(renderer); + file_editor.replace_current_file(new_file); } diff --git a/src/app/mod.rs b/src/app/mod.rs index db903c0..c4a9e64 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -1,218 +1,9 @@ -pub use crate::app::app_state::AppState; -pub use crate::config::Config; -pub use crate::renderer::Renderer; -use crate::themes::*; -use crate::ui::caret::{CaretPosition, MoveDirection}; -use crate::ui::*; - -use std::rc::Rc; -use std::thread::sleep; -use std::time::Duration; - -use sdl2::event::Event; -use sdl2::hint; -use sdl2::keyboard::{Keycode, Mod}; -use sdl2::mouse::MouseButton; -use sdl2::pixels::{Color, PixelFormatEnum}; -use sdl2::rect::{Point, Rect}; -use sdl2::render::Canvas; -use sdl2::rwops::RWops; -use sdl2::surface::Surface; -use sdl2::ttf::Sdl2TtfContext; -use sdl2::video::Window; -use sdl2::EventPump; -use sdl2::{Sdl, TimerSubsystem, VideoSubsystem}; - pub mod app_state; +pub mod application; pub mod caret_manager; pub mod file_content_manager; -pub type WindowCanvas = Canvas; - -#[derive(PartialEq, Clone, Debug)] -pub enum UpdateResult { - NoOp, - Stop, - RefreshPositions, - MouseLeftClicked(Point), - MoveCaret(Rect, CaretPosition), - DeleteFront, - DeleteBack, - Input(String), - InsertNewLine, - MoveCaretLeft, - MoveCaretRight, - MoveCaretUp, - MoveCaretDown, -} - -pub enum Task { - OpenFile { file_path: String }, -} - -pub struct Application { - config: Rc, - clear_color: Color, - sdl_context: Sdl, - canvas: WindowCanvas, - video_subsystem: VideoSubsystem, - tasks: Vec, -} - -impl Application { - pub fn new() -> Self { - let config = Rc::new(Config::new()); - let sdl_context = sdl2::init().unwrap(); - - hint::set("SDL_GL_MULTISAMPLEBUFFERS", "1"); - hint::set("SDL_GL_MULTISAMPLESAMPLES", "8"); - hint::set("SDL_GL_ACCELERATED_VISUAL", "1"); - hint::set("SDL_HINT_RENDER_SCALE_QUALITY", "2"); - hint::set("SDL_HINT_VIDEO_ALLOW_SCREENSAVER", "1"); - - let video_subsystem = sdl_context.video().unwrap(); - - let mut window: Window = video_subsystem - .window("Rider", config.width(), config.height()) - .position_centered() - .opengl() - .build() - .unwrap(); - let icon_bytes = include_bytes!("../../assets/gear-64x64.bmp").clone(); - let mut rw = RWops::from_bytes(&icon_bytes).unwrap(); - let mut icon = Surface::load_bmp_rw(&mut rw).unwrap(); - window.set_icon(&mut icon); - - let canvas = window.into_canvas().accelerated().build().unwrap(); - - Self { - sdl_context, - video_subsystem, - canvas, - tasks: vec![], - clear_color: config.theme().background().into(), - config, - } - } - - pub fn init(&mut self) { - self.clear(); - } - - pub fn run(&mut self) { - let mut timer: TimerSubsystem = self.sdl_context.timer().unwrap(); - let mut event_pump = self.sdl_context.event_pump().unwrap(); - let font_context = sdl2::ttf::init().unwrap(); - let texture_creator = self.canvas.texture_creator(); - let sleep_time = Duration::new(0, 1_000_000_000u32 / 60); - let mut app_state = AppState::new(self.config.clone()); - let mut renderer = Renderer::new(self.config.clone(), &font_context, &texture_creator); - - 'running: loop { - match self.handle_events(&mut event_pump) { - UpdateResult::Stop => break 'running, - UpdateResult::RefreshPositions => (), - UpdateResult::NoOp => (), - UpdateResult::MoveCaret(_, _pos) => (), - UpdateResult::MouseLeftClicked(point) => { - app_state.on_left_click(&point, &mut self.video_subsystem); - } - UpdateResult::DeleteFront => { - app_state.delete_front(); - } - UpdateResult::DeleteBack => { - app_state.delete_back(); - } - UpdateResult::Input(text) => { - app_state.insert_text(text, &mut renderer); - } - UpdateResult::InsertNewLine => { - app_state.insert_new_line(&mut renderer); - } - UpdateResult::MoveCaretLeft => { - app_state.move_caret(MoveDirection::Left); - } - UpdateResult::MoveCaretRight => { - app_state.move_caret(MoveDirection::Right); - } - UpdateResult::MoveCaretUp => { - app_state.move_caret(MoveDirection::Up); - } - UpdateResult::MoveCaretDown => { - app_state.move_caret(MoveDirection::Down); - } - } - for task in self.tasks.iter() { - match task { - Task::OpenFile { file_path } => { - use crate::ui::file::editor_file::*; - app_state.open_file(file_path.clone()); - } - } - } - self.tasks.clear(); - - self.clear(); - - app_state.update(timer.ticks() as i32); - app_state.render(&mut self.canvas, &mut renderer); - - self.present(); - sleep(sleep_time); - } - } - - pub fn open_file(&mut self, file_path: String) { - self.tasks.push(Task::OpenFile { file_path }); - } - - fn present(&mut self) { - self.canvas.present(); - } - - fn clear(&mut self) { - self.canvas.set_draw_color(self.clear_color.clone()); - self.canvas.clear(); - } - - fn handle_events(&mut self, event_pump: &mut EventPump) -> UpdateResult { - for event in event_pump.poll_iter() { - match event { - Event::Quit { .. } => return UpdateResult::Stop, - Event::MouseButtonUp { - mouse_btn, x, y, .. - } => match mouse_btn { - MouseButton::Left => return UpdateResult::MouseLeftClicked(Point::new(x, y)), - _ => (), - }, - Event::KeyDown { keycode, .. } => { - let keycode = if keycode.is_some() { - keycode.unwrap() - } else { - return UpdateResult::NoOp; - }; - match keycode { - Keycode::Backspace => return UpdateResult::DeleteFront, - Keycode::Delete => return UpdateResult::DeleteBack, - Keycode::KpEnter | Keycode::Return => return UpdateResult::InsertNewLine, - Keycode::Left => return UpdateResult::MoveCaretLeft, - Keycode::Right => return UpdateResult::MoveCaretRight, - Keycode::Up => return UpdateResult::MoveCaretUp, - Keycode::Down => return UpdateResult::MoveCaretDown, - _ => UpdateResult::NoOp, - }; - } - Event::TextInput { text, .. } => { - println!("text input: {}", text); - return UpdateResult::Input(text); - } - _ => (), - } - } - UpdateResult::NoOp - } - - pub fn config(&self) -> &Rc { - &self.config - } -} +pub use crate::app::app_state::*; +pub use crate::app::application::*; +pub use crate::app::caret_manager::*; +pub use crate::app::file_content_manager::*; diff --git a/src/config/creator.rs b/src/config/creator.rs index 7c048a6..ba038af 100644 --- a/src/config/creator.rs +++ b/src/config/creator.rs @@ -19,6 +19,11 @@ pub fn create() { if !log_dir().exists() { fs::create_dir_all(&log_dir()).unwrap_or_else(|_| panic!("Cannot create log directory")); } + + if !project_dir().exists() { + fs::create_dir_all(&project_dir()) + .unwrap_or_else(|_| panic!("Cannot create project directory")); + } } fn write_default_fonts() { diff --git a/src/config/directories.rs b/src/config/directories.rs index 508bc38..bf39ab2 100644 --- a/src/config/directories.rs +++ b/src/config/directories.rs @@ -26,3 +26,11 @@ pub fn config_dir() -> PathBuf { config_dir.push("rider"); config_dir } + +pub fn project_dir() -> PathBuf { + let runtime = dirs::runtime_dir().unwrap(); + + let mut project_dir = runtime.clone(); + project_dir.push(".rider"); + project_dir +} diff --git a/src/config/mod.rs b/src/config/mod.rs index c1206cc..318da89 100644 --- a/src/config/mod.rs +++ b/src/config/mod.rs @@ -56,6 +56,7 @@ impl EditorConfig { pub struct Config { width: u32, height: u32, + scroll_speed: i32, menu_height: u16, editor_config: EditorConfig, theme: Theme, @@ -74,12 +75,18 @@ impl Config { Self { width: 1024, height: 860, + scroll_speed: 10, menu_height: 60, theme: Theme::load(editor_config.current_theme().clone()), editor_config, extensions_mapping, } } + + pub fn scroll_speed(&self) -> i32 { + self.scroll_speed + } + pub fn width(&self) -> u32 { self.width } diff --git a/src/lexer/mod.rs b/src/lexer/mod.rs index c1a11b5..b3a5377 100644 --- a/src/lexer/mod.rs +++ b/src/lexer/mod.rs @@ -3,7 +3,7 @@ use std::ops::Deref; pub mod plain; pub mod rust_lang; -#[derive(Debug, Clone)] +#[derive(Debug, Clone, Copy)] pub enum Language { PlainText, Rust, diff --git a/src/lexer/rust_lang.rs b/src/lexer/rust_lang.rs index 00d38ba..4d1e52b 100644 --- a/src/lexer/rust_lang.rs +++ b/src/lexer/rust_lang.rs @@ -7,7 +7,7 @@ pub mod lexer { lexer! { fn next_token(text: 'a) -> (TokenType, &'a str); - r"( +|\t+|\n+)" => (TokenType::Whitespace { + r"( +|\t+|\n)" => (TokenType::Whitespace { token: Token::new(text.to_string(), 0, 0, 0, 0) }, text), diff --git a/src/themes/caret_color.rs b/src/themes/caret_color.rs index a93ee08..a2965ed 100644 --- a/src/themes/caret_color.rs +++ b/src/themes/caret_color.rs @@ -10,16 +10,8 @@ pub struct CaretColor { impl Default for CaretColor { fn default() -> Self { Self { - bright: ThemeConfig::new( - SerdeColor::new(0, 0, 0, 0), - false, - false, - ), - blur: ThemeConfig::new( - SerdeColor::new(0, 0, 0, 0), - false, - false, - ), + bright: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), + blur: ThemeConfig::new(SerdeColor::new(0, 0, 0, 0), false, false), } } } diff --git a/src/ui/caret.rs b/src/ui/caret.rs index cd54920..7749dc7 100644 --- a/src/ui/caret.rs +++ b/src/ui/caret.rs @@ -1,4 +1,4 @@ -use crate::app::{UpdateResult, WindowCanvas}; +use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::config::Config; use crate::renderer::Renderer; use crate::ui::text_character::TextCharacter; @@ -10,7 +10,7 @@ use std::ops::Deref; use std::rc::Rc; #[derive(Clone, Debug, PartialEq)] -enum CaretState { +pub enum CaretState { Bright, Blur, } @@ -95,25 +95,6 @@ pub struct CaretRenderPosition { reset_position: Rect, } -impl CaretRenderPosition { - pub fn dest(&self) -> &Rect { - &self.dest - } - - pub fn reset_position(&self) -> &Rect { - &self.reset_position - } - - pub fn reset(&mut self) { - self.dest = self.reset_position.clone() - } - - pub fn move_to(&mut self, p: &Point) { - self.dest.set_x(p.x()); - self.dest.set_y(p.y()); - } -} - #[derive(Clone, Debug, PartialEq)] pub struct CaretColor { bright: Color, @@ -140,7 +121,7 @@ pub struct Caret { blink_delay: u8, state: CaretState, position: CaretPosition, - render_position: CaretRenderPosition, + dest: Rect, colors: CaretColor, } @@ -151,20 +132,7 @@ impl Caret { Self { state: CaretState::Bright, blink_delay: 0, - render_position: CaretRenderPosition { - dest: Rect::new( - config.editor_left_margin(), - config.editor_top_margin(), - 4, - 0, - ), - reset_position: Rect::new( - config.editor_left_margin(), - config.editor_top_margin(), - 4, - 0, - ), - }, + dest: Rect::new(0, 0, 6, 0), colors: CaretColor { bright, blur }, pending: true, position: CaretPosition { @@ -172,7 +140,6 @@ impl Caret { line_number: 0, line_position: 0, }, - // config, } } @@ -185,13 +152,19 @@ impl Caret { } pub fn reset_caret(&mut self) { - self.render_position.reset(); + self.dest.set_x(0); + self.dest.set_y(0); self.position.reset(); } pub fn move_caret(&mut self, position: CaretPosition, pos: Point) { self.position = position; - self.render_position.move_to(&pos); + self.dest.set_x(pos.x()); + self.dest.set_y(pos.y()); + } + + pub fn dest(&self) -> &Rect { + &self.dest } pub fn position(&self) -> &CaretPosition { @@ -208,52 +181,66 @@ impl Deref for Caret { } impl Render for Caret { - fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - if self.pending { - if let Some(rect) = get_text_character_rect('W', renderer) { - let mut dest = self.render_position.dest().clone(); - dest.set_height(rect.height()); - let reset_position = dest.clone(); - self.render_position = CaretRenderPosition { - dest, - reset_position, - }; - } - self.pending = false; - } - let dest = self.render_position.dest(); + fn render(&self, canvas: &mut WC, _renderer: &mut Renderer, parent: Parent) -> UR { + let dest = match parent { + Some(parent) => move_render_point(parent.render_start_point(), self.dest()), + None => self.dest().clone(), + }; let start = Point::new(dest.x(), dest.y()); let end = Point::new(dest.x(), dest.y() + dest.height() as i32); let color = match self.state { CaretState::Bright => self.colors.bright(), CaretState::Blur => self.colors.blur(), - }; - canvas.set_draw_color(color.clone()); + } + .clone(); + canvas.set_draw_color(color); canvas .draw_line(start, end) .unwrap_or_else(|_| panic!("Failed to draw a caret")); - UpdateResult::NoOp + UR::NoOp + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { + if !self.pending { + return; + } + + if let Some(rect) = get_text_character_rect('W', renderer) { + self.dest.set_height(rect.height()); + } + self.pending = false; } } impl Update for Caret { - fn update(&mut self, _ticks: i32) -> UpdateResult { + fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR { self.blink_delay += 1; if self.blink_delay >= 30 { self.blink_delay = 0; self.toggle_state(); } - UpdateResult::NoOp + UR::NoOp } } impl ClickHandler for Caret { - fn on_left_click(&mut self, _point: &Point) -> UpdateResult { - // self.move_caret(Point::new(self.position.x(), self.position.y())); - UpdateResult::NoOp + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { + UR::NoOp } - fn is_left_click_target(&self, point: &Point) -> bool { - is_in_rect(point, &self.render_position.dest()) + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + is_in_rect( + point, + &match context { + &UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()), + _ => self.dest().clone(), + }, + ) + } +} + +impl RenderBox for Caret { + fn render_start_point(&self) -> Point { + self.dest().top_left() } } diff --git a/src/ui/file/editor_file.rs b/src/ui/file/editor_file.rs index a9b2db0..88f8761 100644 --- a/src/ui/file/editor_file.rs +++ b/src/ui/file/editor_file.rs @@ -1,32 +1,32 @@ use sdl2::rect::{Point, Rect}; use std::rc::Rc; +use std::sync::*; -use crate::app::{UpdateResult, WindowCanvas}; +use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::config::Config; use crate::renderer::Renderer; use crate::ui::file::editor_file_section::EditorFileSection; use crate::ui::text_character::TextCharacter; use crate::ui::*; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct EditorFile { path: String, sections: Vec, render_position: Rect, buffer: String, config: Rc, + line_height: u32, } impl EditorFile { pub fn new(path: String, buffer: String, config: Rc) -> Self { use std::path::Path; - - let p = Path::new(&path); - let ext = match p.extension() { - Some(s) => s.to_str().unwrap_or("txt"), - None => "txt", - } - .to_string(); + let ext = Path::new(&path) + .extension() + .and_then(|p| p.to_str()) + .map_or("txt", |s| s) + .to_string(); let sections = vec![EditorFileSection::new(buffer.clone(), ext, config.clone())]; let x = config.editor_left_margin(); let y = config.editor_top_margin(); @@ -37,6 +37,7 @@ impl EditorFile { render_position: Rect::new(x, y, 0, 0), buffer, config, + line_height: 0, } } @@ -48,10 +49,23 @@ impl EditorFile { self.path.clone() } - pub fn get_character_at(&self, index: usize) -> Option<&TextCharacter> { + pub fn sections(&self) -> &Vec { + &self.sections + } + + pub fn line_height(&self) -> u32 { + self.line_height + } + + pub fn render_position(&self) -> &Rect { + &self.render_position + } + + pub fn get_character_at(&self, index: usize) -> Option { for section in self.sections.iter() { - if let Some(text_character) = section.get_character_at(index) { - return Some(text_character); + let character = section.get_character_at(index); + if character.is_some() { + return character; } } None @@ -59,7 +73,6 @@ impl EditorFile { pub fn get_line(&self, line: &usize) -> Option> { let mut vec: Vec<&TextCharacter> = vec![]; - for section in self.sections.iter() { match section.get_line(line) { Some(v) => vec.append(&mut v.clone()), @@ -74,8 +87,23 @@ impl EditorFile { } } + pub fn get_last_at_line(&self, line: usize) -> Option { + let mut current = None; + for section in self.sections.iter() { + let c = section.get_last_at_line(line); + if c.is_some() { + current = c; + } + } + current + } + + pub fn get_section_at_mut(&mut self, index: usize) -> Option<&mut EditorFileSection> { + self.sections.get_mut(index) + } + fn refresh_characters_position(&mut self) { - let mut current: Rect = self.render_position.clone(); + let mut current: Rect = Rect::new(0, 0, 0, 0); for section in self.sections.iter_mut() { section.update_positions(&mut current); } @@ -83,47 +111,66 @@ impl EditorFile { } impl Render for EditorFile { - fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - let mut res = UpdateResult::NoOp; + fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR { + for section in self.sections.iter() { + section.render(canvas, renderer, parent); + } + UR::NoOp + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { for section in self.sections.iter_mut() { - res = section.render(canvas, renderer); + section.prepare_ui(renderer); } - if res == UpdateResult::RefreshPositions { - self.refresh_characters_position(); - for section in self.sections.iter_mut() { - section.render(canvas, renderer); - } + if let Some(r) = get_text_character_rect('W', renderer) { + self.line_height = r.height(); } - UpdateResult::NoOp + self.refresh_characters_position(); } } impl Update for EditorFile { - fn update(&mut self, ticks: i32) -> UpdateResult { - let mut result = UpdateResult::NoOp; + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { + let mut result = UR::NoOp; for section in self.sections.iter_mut() { - result = section.update(ticks); + result = section.update(ticks, context); } result } } impl ClickHandler for EditorFile { - fn on_left_click(&mut self, point: &Point) -> UpdateResult { - for section in self.sections.iter_mut() { - if section.is_left_click_target(point) { - return section.on_left_click(point); + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { + let mut index = -1; + for (i, section) in self.sections.iter().enumerate() { + if section.is_left_click_target(point, context) { + index = i as i32; + break; } } - UpdateResult::NoOp + if index >= 0 { + let context = UpdateContext::ParentPosition(self.render_start_point()); + return self + .get_section_at_mut(index as usize) + .unwrap() + .on_left_click(point, &context); + } + UR::NoOp } - fn is_left_click_target(&self, point: &Point) -> bool { + fn is_left_click_target(&self, point: &Point, _context: &UpdateContext) -> bool { + let context = UpdateContext::ParentPosition(self.render_start_point()); for section in self.sections.iter() { - if section.is_left_click_target(point) { + if section.is_left_click_target(point, &context) { return true; } } false } } + +impl RenderBox for EditorFile { + fn render_start_point(&self) -> Point { + self.render_position.top_left() + } +} diff --git a/src/ui/file/editor_file_section.rs b/src/ui/file/editor_file_section.rs index 0ef5f20..79bddf2 100644 --- a/src/ui/file/editor_file_section.rs +++ b/src/ui/file/editor_file_section.rs @@ -1,7 +1,8 @@ use sdl2::rect::{Point, Rect}; +use std::cell::Cell; use std::rc::Rc; -use crate::app::{UpdateResult, WindowCanvas}; +use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::config::Config; use crate::lexer::Language; use crate::renderer::Renderer; @@ -9,7 +10,7 @@ use crate::ui::file::editor_file_token::EditorFileToken; use crate::ui::text_character::TextCharacter; use crate::ui::*; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct EditorFileSection { tokens: Vec, language: Language, @@ -27,9 +28,19 @@ impl EditorFileSection { let lexer_tokens = lexer::parse(buffer.clone(), language); let mut tokens: Vec = vec![]; - for token_type in lexer_tokens { - let token = EditorFileToken::new(token_type, config.clone()); - tokens.push(token.clone()); + let mut iterator = lexer_tokens.iter().peekable(); + loop { + let token_type = match iterator.next() { + Some(t) => t, + _ => break, + }; + let next = iterator.peek(); + let token = EditorFileToken::new( + token_type, + next.map_or(true, |t| t.is_new_line()), + config.clone(), + ); + tokens.push(token); } let language = Language::PlainText; Self { @@ -45,10 +56,11 @@ impl EditorFileSection { } } - pub fn get_character_at(&self, index: usize) -> Option<&TextCharacter> { + pub fn get_character_at(&self, index: usize) -> Option { for token in self.tokens.iter() { - if let Some(text_character) = token.get_character_at(index) { - return Some(text_character); + let character = token.get_character_at(index); + if character.is_some() { + return character; } } None @@ -68,46 +80,72 @@ impl EditorFileSection { Some(vec) } } + + pub fn get_last_at_line(&self, line: usize) -> Option { + let mut current: Option = None; + for token in self.tokens.iter() { + if !token.is_last_in_line() { + continue; + } + let c = token.get_last_at_line(line); + if c.is_some() { + current = c; + } + } + current + } } impl Render for EditorFileSection { - fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - let mut res = UpdateResult::NoOp; - for character in self.tokens.iter_mut() { - let r = character.render(canvas, renderer); - if res == UpdateResult::NoOp { - res = r; - } + fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR { + for token in self.tokens.iter() { + token.render(canvas, renderer, parent); + } + UR::NoOp + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { + for token in self.tokens.iter_mut() { + token.prepare_ui(renderer); } - res } } impl Update for EditorFileSection { - fn update(&mut self, ticks: i32) -> UpdateResult { - let mut result = UpdateResult::NoOp; - for file_char in self.tokens.iter_mut() { - result = file_char.update(ticks) + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { + let mut result = UR::NoOp; + for token in self.tokens.iter_mut() { + result = token.update(ticks, context) } result } } impl ClickHandler for EditorFileSection { - fn on_left_click(&mut self, point: &Point) -> UpdateResult { + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { for token in self.tokens.iter_mut() { - if token.is_left_click_target(point) { - return token.on_left_click(point); + if token.is_left_click_target(point, context) { + return token.on_left_click(point, context); } } - UpdateResult::NoOp + UR::NoOp } - fn is_left_click_target(&self, point: &Point) -> bool { - for token in self.tokens.iter() { - if token.is_left_click_target(point) { - return true; + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + let mut i = 0; + loop { + if i == self.tokens.len() { + break; } + match self.tokens.get(i) { + Some(token) => { + if token.is_left_click_target(point, context) { + return true; + } + } + None => break, + } + i += 1; } false } diff --git a/src/ui/file/editor_file_token.rs b/src/ui/file/editor_file_token.rs index aaf33db..eae4e77 100644 --- a/src/ui/file/editor_file_token.rs +++ b/src/ui/file/editor_file_token.rs @@ -1,9 +1,8 @@ -use crate::app::{UpdateResult, WindowCanvas}; +use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::config::Config; use crate::lexer::TokenType; use crate::renderer::managers::{FontDetails, TextDetails}; use crate::renderer::Renderer; -use crate::ui::text_character::*; use crate::ui::*; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; @@ -11,32 +10,55 @@ use sdl2::render::Texture; use sdl2::ttf::Font; use std::rc::Rc; -#[derive(Clone)] +impl TokenType { + pub fn to_color(&self, config: &Rc) -> Color { + let config = config.theme().code_highlighting(); + match self { + &TokenType::Whitespace { .. } => config.whitespace().color().into(), + &TokenType::Keyword { .. } => config.keyword().color().into(), + &TokenType::String { .. } => config.string().color().into(), + &TokenType::Number { .. } => config.number().color().into(), + &TokenType::Identifier { .. } => config.identifier().color().into(), + &TokenType::Literal { .. } => config.literal().color().into(), + &TokenType::Comment { .. } => config.comment().color().into(), + &TokenType::Operator { .. } => config.operator().color().into(), + &TokenType::Separator { .. } => config.separator().color().into(), + } + } +} + +#[derive(Clone, Debug)] pub struct EditorFileToken { + last_in_line: bool, characters: Vec, - token_type: TokenType, + token_type: Rc, config: Rc, } impl EditorFileToken { - pub fn new(token_type: TokenType, config: Rc) -> Self { + pub fn new(token_type: &TokenType, last_in_line: bool, config: Rc) -> Self { Self { + last_in_line, characters: vec![], - token_type, + token_type: Rc::new(token_type.clone()), config, } } + pub fn is_last_in_line(&self) -> bool { + self.last_in_line + } + pub fn update_position(&mut self, current: &mut Rect) { for text_character in self.characters.iter_mut() { text_character.update_position(current); } } - pub fn get_character_at(&self, index: usize) -> Option<&TextCharacter> { + pub fn get_character_at(&self, index: usize) -> Option { for character in self.characters.iter() { if character.position() == index { - return Some(&character); + return Some(character.clone()); } } None @@ -70,32 +92,17 @@ impl EditorFileToken { } } - fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult { - let config = renderer.config().theme().code_highlighting(); - let color: Color = match self.token_type { - TokenType::Whitespace { .. } => config.whitespace().color().into(), - TokenType::Keyword { .. } => config.keyword().color().into(), - TokenType::String { .. } => config.string().color().into(), - TokenType::Number { .. } => config.number().color().into(), - TokenType::Identifier { .. } => config.identifier().color().into(), - TokenType::Literal { .. } => config.literal().color().into(), - TokenType::Comment { .. } => config.comment().color().into(), - TokenType::Operator { .. } => config.operator().color().into(), - TokenType::Separator { .. } => config.separator().color().into(), - }; - for (index, c) in self.token_type.text().chars().enumerate() { - let mut text_character = TextCharacter::new( - c.clone(), - self.token_type.start() + index, - self.token_type.line(), - color, - self.config.clone(), - ); - text_character.update_view(renderer); - self.characters.push(text_character); + pub fn get_last_at_line(&self, line: usize) -> Option { + let mut current: Option<&TextCharacter> = None; + for text_character in self.characters.iter() { + if !text_character.is_last_in_line() { + continue; + } + if text_character.line() == line { + current = Some(text_character); + } } - - UpdateResult::RefreshPositions + current.map(|c| c.clone()) } } @@ -104,42 +111,60 @@ impl Render for EditorFileToken { * Must first create targets so even if new line appear renderer will know * where move render starting point */ - fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - if self.characters.is_empty() { - return self.update_view(renderer); - } + fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR { if self.token_type.is_new_line() { - return UpdateResult::NoOp; + return UR::NoOp; } - for text_character in self.characters.iter_mut() { - text_character.render(canvas, renderer); + for text_character in self.characters.iter() { + text_character.render(canvas, renderer, parent); + } + UR::NoOp + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { + if !self.characters.is_empty() { + return; + } + let color: Color = self.token_type.to_color(renderer.config()); + let chars: Vec = self.token_type.text().chars().collect(); + for (index, c) in chars.iter().enumerate() { + let last_in_line = self.last_in_line && index + 1 == chars.len(); + let mut text_character: TextCharacter = TextCharacter::new( + c.clone(), + self.token_type.start() + index, + self.token_type.line(), + last_in_line, + color, + self.config.clone(), + ); + text_character.prepare_ui(renderer); + self.characters.push(text_character); } - UpdateResult::NoOp } } impl Update for EditorFileToken { - fn update(&mut self, ticks: i32) -> UpdateResult { + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { for text_character in self.characters.iter_mut() { - text_character.update(ticks); + text_character.update(ticks, context); } - UpdateResult::NoOp + UR::NoOp } } impl ClickHandler for EditorFileToken { - fn on_left_click(&mut self, point: &Point) -> UpdateResult { + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UR { for text_character in self.characters.iter_mut() { - if text_character.is_left_click_target(point) { - return text_character.on_left_click(point); + if text_character.is_left_click_target(point, context) { + return text_character.on_left_click(point, context); } } - UpdateResult::NoOp + UR::NoOp } - fn is_left_click_target(&self, point: &Point) -> bool { + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { for text_character in self.characters.iter() { - if text_character.is_left_click_target(point) { + if text_character.is_left_click_target(point, context) { return true; } } diff --git a/src/ui/file/mod.rs b/src/ui/file/mod.rs index 7ddf5d7..28b1c2b 100644 --- a/src/ui/file/mod.rs +++ b/src/ui/file/mod.rs @@ -1,3 +1,7 @@ pub mod editor_file; pub mod editor_file_section; pub mod editor_file_token; + +pub use crate::ui::file::editor_file::*; +pub use crate::ui::file::editor_file_section::*; +pub use crate::ui::file::editor_file_token::*; diff --git a/src/ui/file_editor.rs b/src/ui/file_editor.rs new file mode 100644 index 0000000..f30531e --- /dev/null +++ b/src/ui/file_editor.rs @@ -0,0 +1,270 @@ +use sdl2::rect::*; +use std::borrow::*; +use std::mem; +use std::rc::Rc; + +use crate::app::*; +use crate::app::{UpdateResult as UR, WindowCanvas as WS}; +use crate::ui::*; + +pub struct FileEditor { + dest: Rect, + scroll: Point, + caret: Caret, + file: Option, + config: Rc, +} + +impl FileEditor { + pub fn new(dest: Rect, config: Rc) -> Self { + Self { + dest, + scroll: Point::new(0, 0), + caret: Caret::new(config.clone()), + file: None, + config, + } + } + + pub fn config(&self) -> &Rc { + &self.config + } + + pub fn caret(&self) -> &Caret { + &self.caret + } + + pub fn caret_mut(&mut self) -> &mut Caret { + &mut self.caret + } + + pub fn has_file(&self) -> bool { + self.file.is_some() + } + + pub fn drop_file(&mut self) -> Option { + if self.has_file() { + let mut file = None; + mem::swap(&mut self.file, &mut file); + file + } else { + None + } + } + + pub fn open_file(&mut self, file: EditorFile) -> Option { + let mut file = Some(file); + mem::swap(&mut self.file, &mut file); + file + } + + pub fn file(&self) -> Option<&EditorFile> { + self.file.as_ref() + } + + pub fn file_mut(&mut self) -> Option<&mut EditorFile> { + self.file.as_mut() + } + + pub fn move_caret(&mut self, dir: MoveDirection) { + match dir { + MoveDirection::Left => {} + MoveDirection::Right => caret_manager::move_caret_right(self), + MoveDirection::Up => {} + MoveDirection::Down => {} + } + } + + pub fn delete_front(&mut self, renderer: &mut Renderer) { + file_content_manager::delete_front(self, renderer); + } + + pub fn delete_back(&mut self, renderer: &mut Renderer) { + file_content_manager::delete_back(self, renderer); + } + + pub fn insert_text(&mut self, text: String, renderer: &mut Renderer) { + file_content_manager::insert_text(self, text, renderer); + } + + pub fn insert_new_line(&mut self, renderer: &mut Renderer) { + file_content_manager::insert_new_line(self, renderer); + } + + pub fn replace_current_file(&mut self, file: EditorFile) { + self.open_file(file); + } + + pub fn scroll_to(&mut self, x: i32, y: i32) { + self.scroll = self.scroll + + Point::new( + self.config.scroll_speed() * x, + self.config.scroll_speed() * y, + ); + } + + 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) + } + + 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 Render for FileEditor { + fn render(&self, canvas: &mut WS, renderer: &mut Renderer, _parent: Parent) -> UR { + match self.file() { + Some(file) => file.render(canvas, renderer, Some(self)), + _ => UR::NoOp, + }; + self.caret.render(canvas, renderer, Some(self)) + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { + self.caret.prepare_ui(renderer); + } +} + +impl Update for FileEditor { + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UR { + self.caret.update(ticks, context); + 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 + } +} + +#[cfg(test)] +mod tests { + use crate::app::*; + use crate::ui::*; + use sdl2::rect::*; + use sdl2::*; + use std::borrow::*; + use std::rc::*; + + #[test] + fn replace_file() { + let config = Rc::new(Config::new()); + let mut editor = FileEditor::new(Rect::new(0, 0, 100, 100), config.clone()); + 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()); + } + + #[test] + fn add_text() { + let config = Rc::new(Config::new()); + let sdl_context = sdl2::init().unwrap(); + let video_subsystem = sdl_context.video().unwrap(); + let window = video_subsystem + .window("Test", 1, 1) + .borderless() + .opengl() + .build() + .unwrap(); + let canvas = window.into_canvas().accelerated().build().unwrap(); + let font_context = sdl2::ttf::init().unwrap(); + let texture_creator = canvas.texture_creator(); + let mut renderer = Renderer::new(config.clone(), &font_context, &texture_creator); + + let mut editor = FileEditor::new(Rect::new(0, 0, 100, 100), config.clone()); + let mut file = EditorFile::new("./foo.txt".to_string(), "foo".to_string(), config.clone()); + file.prepare_ui(&mut renderer); + assert_eq!(editor.open_file(file).is_none(), true); + assert_eq!(editor.caret().position().text_position(), 0); + assert_eq!(editor.file().is_some(), true); + assert_eq!(editor.file().unwrap().sections().len(), 1); + assert_eq!(editor.file().unwrap().get_character_at(0).is_some(), true); + + editor.insert_text("z".to_string(), &mut renderer); + assert_eq!(editor.caret().position().text_position(), 1); + assert_eq!(editor.file().is_some(), true); + assert_eq!(editor.file().unwrap().buffer(), "zfoo".to_string()); + } +} diff --git a/src/ui/menu_bar.rs b/src/ui/menu_bar.rs index cfe6589..049627f 100644 --- a/src/ui/menu_bar.rs +++ b/src/ui/menu_bar.rs @@ -7,17 +7,21 @@ use sdl2::rect::{Point, Rect}; use std::rc::Rc; pub struct MenuBar { + border_color: Color, background_color: Color, dest: Rect, config: Rc, + pending: bool, } impl MenuBar { pub fn new(config: Rc) -> Self { Self { - background_color: Color::RGB(10, 10, 10), + border_color: Color::RGB(10, 10, 10), + background_color: config.theme().background().into(), dest: Rect::new(0, 0, 0, 0), config, + pending: true, } } @@ -31,28 +35,66 @@ impl MenuBar { } impl Render for MenuBar { - fn render(&mut self, canvas: &mut WindowCanvas, _renderer: &mut Renderer) -> UpdateResult { + fn render( + &self, + canvas: &mut WindowCanvas, + _renderer: &mut Renderer, + parent: Parent, + ) -> UpdateResult { + canvas.set_draw_color(self.background_color.clone()); + canvas + .fill_rect(match parent { + None => self.dest.clone(), + Some(parent) => move_render_point(parent.render_start_point(), self.dest()), + }) + .unwrap_or_else(|_| panic!("Failed to draw main menu background")); + + canvas.set_draw_color(self.border_color.clone()); + canvas + .draw_rect(match parent { + None => self.dest.clone(), + Some(parent) => move_render_point(parent.render_start_point(), self.dest()), + }) + .unwrap_or_else(|_| panic!("Failed to draw main menu background")); + + UpdateResult::NoOp + } + + fn prepare_ui(&mut self, _renderer: &mut Renderer) { + if !self.pending { + return; + } let width = self.config.width(); let height = self.config.menu_height() as u32; self.dest = Rect::new(0, 0, width, height); - canvas.set_draw_color(self.background_color.clone()); - canvas.draw_rect(self.dest.clone()).unwrap(); - UpdateResult::NoOp + self.pending = false; } } impl Update for MenuBar { - fn update(&mut self, _ticks: i32) -> UpdateResult { + fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UpdateResult { UpdateResult::NoOp } } impl ClickHandler for MenuBar { - fn on_left_click(&mut self, _point: &Point) -> UpdateResult { + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UpdateResult { unimplemented!() } - fn is_left_click_target(&self, point: &Point) -> bool { - is_in_rect(point, self.dest()) + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + is_in_rect( + point, + &match context { + &UpdateContext::ParentPosition(p) => move_render_point(p, self.dest()), + _ => self.dest().clone(), + }, + ) + } +} + +impl RenderBox for MenuBar { + fn render_start_point(&self) -> Point { + self.dest.top_left() } } diff --git a/src/ui/mod.rs b/src/ui/mod.rs index 512eb5a..8018419 100644 --- a/src/ui/mod.rs +++ b/src/ui/mod.rs @@ -7,11 +7,30 @@ use crate::renderer::Renderer; pub mod caret; pub mod file; +pub mod file_editor; pub mod menu_bar; +pub mod project_tree; pub mod text_character; +pub use crate::ui::caret::*; +pub use crate::ui::file::*; +pub use crate::ui::file_editor::*; +pub use crate::ui::menu_bar::*; +pub use crate::ui::project_tree::*; +pub use crate::ui::text_character::*; + +pub type Parent<'l> = Option<&'l RenderBox>; +pub type ParentMut<'l> = Option<&'l mut RenderBox>; + +pub enum UpdateContext<'l> { + Nothing, + ParentPosition(Point), + CurrentFile(&'l mut EditorFile), +} + +#[inline] pub fn is_in_rect(point: &Point, rect: &Rect) -> bool { - let start = Point::new(rect.x(), rect.y()); + let start = rect.top_left(); let end = Point::new( rect.x() + (rect.width() as i32), rect.y() + (rect.height() as i32), @@ -35,16 +54,32 @@ pub fn get_text_character_rect(c: char, renderer: &mut Renderer) -> Option } } +#[inline] +pub fn move_render_point(p: Point, d: &Rect) -> Rect { + Rect::new(d.x() + p.x(), d.y() + p.y(), d.width(), d.height()) +} + pub trait Render { - fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult; + fn render( + &self, + canvas: &mut WindowCanvas, + renderer: &mut Renderer, + parent: Parent, + ) -> UpdateResult; + + fn prepare_ui(&mut self, renderer: &mut Renderer); } pub trait Update { - fn update(&mut self, ticks: i32) -> UpdateResult; + fn update(&mut self, ticks: i32, context: &UpdateContext) -> UpdateResult; } pub trait ClickHandler { - fn on_left_click(&mut self, point: &Point) -> UpdateResult; + fn on_left_click(&mut self, point: &Point, context: &UpdateContext) -> UpdateResult; - fn is_left_click_target(&self, point: &Point) -> bool; + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool; +} + +pub trait RenderBox { + fn render_start_point(&self) -> Point; } diff --git a/src/ui/project_tree/mod.rs b/src/ui/project_tree/mod.rs new file mode 100644 index 0000000..8b13789 --- /dev/null +++ b/src/ui/project_tree/mod.rs @@ -0,0 +1 @@ + diff --git a/src/ui/text_character.rs b/src/ui/text_character.rs index 3b7f936..4204f36 100644 --- a/src/ui/text_character.rs +++ b/src/ui/text_character.rs @@ -1,4 +1,4 @@ -use crate::app::{UpdateResult, WindowCanvas}; +use crate::app::{UpdateResult as UR, WindowCanvas as WC}; use crate::config::Config; use crate::lexer::TokenType; use crate::renderer::managers::{FontDetails, TextDetails}; @@ -12,12 +12,12 @@ use sdl2::render::Texture; use sdl2::ttf::Font; use std::rc::Rc; -#[derive(Clone)] +#[derive(Clone, Debug)] pub struct TextCharacter { - pending: bool, text_character: char, position: usize, line: usize, + last_in_line: bool, source: Rect, dest: Rect, color: Color, @@ -29,14 +29,15 @@ impl TextCharacter { text_character: char, position: usize, line: usize, + last_in_line: bool, color: Color, config: Rc, ) -> Self { Self { - pending: true, text_character, position, line, + last_in_line, source: Rect::new(0, 0, 0, 0), dest: Rect::new(0, 0, 0, 0), color, @@ -44,6 +45,10 @@ impl TextCharacter { } } + pub fn is_last_in_line(&self) -> bool { + self.last_in_line + } + pub fn dest(&self) -> &Rect { &self.dest } @@ -59,8 +64,10 @@ impl TextCharacter { pub fn update_position(&mut self, current: &mut Rect) { if self.is_new_line() { let y = self.source.height() as i32; - current.set_x(self.config.editor_left_margin()); + current.set_x(0); current.set_y(current.y() + y); + self.dest.set_x(current.x()); + self.dest.set_y(current.y()); } else { self.dest.set_x(current.x()); self.dest.set_y(current.y()); @@ -70,7 +77,62 @@ impl TextCharacter { } } - pub fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult { + #[inline] + pub fn is_new_line(&self) -> bool { + self.text_character == '\n' + } + + pub fn position(&self) -> usize { + self.position + } + + pub fn line(&self) -> usize { + self.line + } + + pub fn text_character(&self) -> char { + self.text_character.clone() + } +} + +impl Render for TextCharacter { + /** + * Must first create targets so even if new line appear renderer will know + * where move render starting point + */ + fn render(&self, canvas: &mut WC, renderer: &mut Renderer, parent: Parent) -> UR { + if self.is_new_line() { + return UR::NoOp; + } + + let config = renderer.config().editor_config(); + let font_details = + FontDetails::new(config.font_path().as_str(), config.character_size().clone()); + let font = renderer + .font_manager() + .load(&font_details) + .unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details)); + + let c = self.text_character.clone(); + let mut details = TextDetails { + text: c.to_string(), + color: self.color.clone(), + font: font_details.clone(), + }; + let dest = match parent { + None => self.dest.clone(), + Some(parent) => move_render_point(parent.render_start_point(), self.dest()), + }; + if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) { + renderer.render_texture(canvas, &texture, &self.source, &dest); + } + // let c = Color::RGB(255, 0, 0); + // canvas.set_draw_color(c); + // canvas.draw_rect(dest.clone()).unwrap(); + UR::NoOp + } + + fn prepare_ui(&mut self, renderer: &mut Renderer) { let config = renderer.config().editor_config(); let font_details = FontDetails::new(config.font_path().as_str(), config.character_size().clone()); @@ -96,83 +158,36 @@ impl TextCharacter { .texture_manager() .load_text(&mut details, &font) .unwrap_or_else(|_| panic!("Could not create texture for {:?}", self.text_character)); - - self.pending = false; - UpdateResult::RefreshPositions - } - - #[inline] - pub fn is_new_line(&self) -> bool { - self.text_character == '\n' - } - - #[inline] - pub fn is_pending(&self) -> bool { - self.pending - } - - pub fn position(&self) -> usize { - self.position - } - - pub fn line(&self) -> usize { - self.line - } - - pub fn text_character(&self) -> char { - self.text_character.clone() - } -} - -impl Render for TextCharacter { - /** - * Must first create targets so even if new line appear renderer will know - * where move render starting point - */ - fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { - if self.is_pending() { - return self.update_view(renderer); - } - if self.is_new_line() { - return UpdateResult::NoOp; - } - - let config = renderer.config().editor_config(); - let font_details = - FontDetails::new(config.font_path().as_str(), config.character_size().clone()); - let font = renderer - .font_manager() - .load(&font_details) - .unwrap_or_else(|_| panic!("Could not load font for {:?}", font_details)); - - let c = self.text_character.clone(); - let mut details = TextDetails { - text: c.to_string(), - color: self.color.clone(), - font: font_details.clone(), - }; - if let Ok(texture) = renderer.texture_manager().load_text(&mut details, &font) { - renderer.render_texture(canvas, &texture, &self.source, &self.dest); - } - UpdateResult::NoOp } } impl Update for TextCharacter { - fn update(&mut self, _ticks: i32) -> UpdateResult { - UpdateResult::NoOp + fn update(&mut self, _ticks: i32, _context: &UpdateContext) -> UR { + UR::NoOp } } impl ClickHandler for TextCharacter { - fn on_left_click(&mut self, _point: &Point) -> UpdateResult { - UpdateResult::MoveCaret( + fn on_left_click(&mut self, _point: &Point, _context: &UpdateContext) -> UR { + UR::MoveCaret( self.dest().clone(), CaretPosition::new(self.position(), self.line(), 0), ) } - fn is_left_click_target(&self, point: &Point) -> bool { - is_in_rect(point, self.dest()) + fn is_left_click_target(&self, point: &Point, context: &UpdateContext) -> bool { + is_in_rect( + point, + &match context { + &UpdateContext::ParentPosition(p) => move_render_point(p.clone(), self.dest()), + _ => self.dest().clone(), + }, + ) + } +} + +impl RenderBox for TextCharacter { + fn render_start_point(&self) -> Point { + self.dest.top_left() } }