diff --git a/src/app/app_state.rs b/src/app/app_state.rs index 9c94622..6be1207 100644 --- a/src/app/app_state.rs +++ b/src/app/app_state.rs @@ -5,6 +5,8 @@ use crate::ui::caret::Caret; use crate::ui::file::editor_file::EditorFile; use crate::ui::file::*; use crate::ui::menu_bar::MenuBar; +use crate::ui::caret::{CaretPosition, MoveDirection}; +use crate::ui::text_character::TextCharacter; use crate::ui::*; use sdl2::rect::{Point, Rect}; use sdl2::VideoSubsystem; @@ -52,15 +54,29 @@ impl AppState { }; let mut buffer: String = file.buffer(); let caret: &mut Caret = &mut self.caret; - let position: usize = caret.text_position(); - if position == 0 { + let position: CaretPosition = caret.position().clone(); + if position.text_position() == 0 { return; } - buffer.remove(position - 1); - match file.get_character_at(position - 1) { + let c: char = buffer.chars().collect::>()[position.text_position() - 1]; + buffer.remove(position.text_position() - 1); + let position = match c { + '\n' => CaretPosition::new( + position.text_position() - 1, + position.line_number() - 1, + 0, + ), + _ => CaretPosition::new( + position.text_position() - 1, + position.line_number(), + position.line_position(), + ) + }; + + match file.get_character_at(position.text_position()) { Some(character) => { let dest: &Rect = character.dest(); - caret.move_caret(position - 1, Point::new(dest.x(), dest.y())); + caret.move_caret(position, Point::new(dest.x(), dest.y())); } _ => { caret.reset_caret(); @@ -101,12 +117,16 @@ impl AppState { _ => return, }; let mut pos = Point::new(current.dest().x(), current.dest().y()); - let mut position: usize = caret.text_position(); + let mut position: CaretPosition = caret.position().clone(); for character in text.chars() { - buffer.insert(position, character); + 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 += 1; + position = CaretPosition::new( + position.text_position() + 1, + position.line_number(), + position.line_position(), + ); caret.move_caret(position, pos.clone()); } } @@ -115,9 +135,42 @@ impl AppState { self.files[self.current_file] = new_file; } -// fn current_file(&self) -> Option<&EditorFile> { -// self.files.get(self.current_file) -// } + pub fn insert_new_line(&mut self, renderer: &mut Renderer) { + let file: &mut EditorFile = if let Some(file) = self.files.get_mut(self.current_file) { + file + } else { + return; + }; + let mut buffer: String = file.buffer(); + let caret: &mut Caret = &mut self.caret; + + let current = match file.get_character_at(caret.text_position()) { + Some(c) => c, + _ => return, + }; + let mut pos = Point::new(current.dest().x(), current.dest().y()); + let mut position: CaretPosition = caret.position().clone(); + buffer.insert(position.text_position(), '\n'); + if let Some(rect) = get_text_character_rect('\n', renderer) { + pos = Point::new( + self.config.editor_left_margin(), + pos.y() + rect.height() as i32, + ); + position = CaretPosition::new( + position.text_position(), + position.line_number() + 1, + 0, + ); + caret.move_caret(position, pos.clone()); + } + + let new_file = EditorFile::new(file.path(), buffer, self.config.clone()); + self.files[self.current_file] = new_file; + } + + fn current_file(&self) -> Option<&EditorFile> { + self.files.get(self.current_file) + } fn current_file_mut(&mut self) -> Option<&mut EditorFile> { self.files.get_mut(self.current_file) @@ -147,6 +200,54 @@ impl AppState { UpdateResult::NoOp } + + pub fn move_caret(&mut self, dir: MoveDirection) { + match dir { + MoveDirection::Left => {} + MoveDirection::Right => + self.move_caret_right(), + MoveDirection::Up => {} + MoveDirection::Down => {} + } + } + + fn move_caret_right(&mut self) { + let file: &EditorFile = match self.current_file() { + None => return, + Some(f) => f, + }; + let line = self.caret.line_number().clone(); + 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() == self.caret.text_position() { + idx = i + 1; + break; + } + }; + let text_character: &TextCharacter = match characters.get(idx) { + Some(text_character) => text_character, + None => return, + }; + let line = text_character.line() - self.caret.line_number(); + let pos = self.caret + .position() + .moved(1, line, 0); + let mut d: Rect = text_character.dest().clone(); + if text_character.is_new_line() { + let prev = match characters.get(idx - 1) { + Some(c) => c, + _ => return, + }; + d = prev.dest().clone(); + d.set_x(d.x() + d.width() as i32); + } + self.caret.move_caret(pos, Point::new(d.x(), d.y())); + } } impl Render for AppState { diff --git a/src/app/mod.rs b/src/app/mod.rs index cc9b1ef..d2f9543 100644 --- a/src/app/mod.rs +++ b/src/app/mod.rs @@ -3,6 +3,7 @@ use crate::config::Config; use crate::renderer::Renderer; use crate::themes::*; use crate::ui::*; +use crate::ui::caret::{CaretPosition,MoveDirection}; use std::rc::Rc; use std::thread::sleep; @@ -30,10 +31,15 @@ pub enum UpdateResult { Stop, RefreshPositions, MouseLeftClicked(Point), - MoveCaret(Rect, usize), + MoveCaret(Rect, CaretPosition), DeleteFront, DeleteBack, Input(String), + InsertNewLine, + MoveCaretLeft, + MoveCaretRight, + MoveCaretUp, + MoveCaretDown, } pub enum Task { @@ -109,9 +115,23 @@ impl Application { app_state.delete_back(); } UpdateResult::Input(text) => { - println!("text 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 { @@ -165,6 +185,11 @@ impl Application { 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, }; } diff --git a/src/ui/caret.rs b/src/ui/caret.rs index e278624..a938393 100644 --- a/src/ui/caret.rs +++ b/src/ui/caret.rs @@ -7,6 +7,7 @@ use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; use sdl2::render::Texture; use std::rc::Rc; +use std::ops::Deref; #[derive(Clone, Debug, PartialEq)] enum CaretState { @@ -15,42 +16,159 @@ enum CaretState { } #[derive(Clone, Debug, PartialEq)] -pub struct Caret { - pending: bool, +pub enum MoveDirection { + Left, + Right, + Up, + Down +} + +//#[derive(Clone, Debug, PartialEq)] +//pub enum CaretLocation { +// FirstLineFirstCharacter, +// FirstLine(usize), // with character location +// LastLineFirstCharacter, +// LastLine(usize), // with character location +// FirstCharacter(usize),// with line number +// LastCharacter(usize), // with line number +// Other(usize, usize), // with line number and character number +//} + +#[derive(Clone, Copy, Debug, PartialEq)] +pub struct CaretPosition { text_position: usize, - blink_delay: u8, -// config: Rc, - state: CaretState, + line_number: usize, + line_position: usize, +} + +impl CaretPosition { + pub fn new(text_position: usize, line_number: usize, line_position: usize,) -> Self { + Self { + text_position, + line_number, + line_position + } + } + + pub fn text_position(&self) -> usize { + self.text_position.clone() + } + + pub fn line_number(&self) -> usize { + self.line_number.clone() + } + + pub fn line_position(&self) -> usize { + self.line_position.clone() + } + + pub fn reset(&mut self) { + self.text_position = 0; + self.line_number = 0; + self.line_position = 0; + } + + pub fn set_text_position(&mut self, n: usize) { + self.text_position = n; + } + + pub fn set_line_number(&mut self, n: usize) { + self.line_number = n; + } + + pub fn set_line_position(&mut self, n: usize) { + self.line_position = n; + } + + pub fn moved(&self, text_position: usize, line_number: usize, line_position: usize) -> Self { + Self { + text_position: self.text_position + text_position, + line_number: self.line_number + line_number, + line_position: self.line_position + line_position + } + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct CaretRenderPosition { dest: Rect, reset_position: Rect, - bright_character_color: Color, - blur_character_color: Color, +} + +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, + blur: Color, +} + +impl CaretColor { + pub fn bright(&self) -> &Color { + &self.bright + } + + pub fn blur(&self) -> &Color { + &self.blur + } +} + +#[derive(Clone, Debug, PartialEq)] +pub struct Caret { + pending: bool, + blink_delay: u8, + state: CaretState, + position: CaretPosition, + render_position: CaretRenderPosition, + colors: CaretColor, } impl Caret { pub fn new(config: Rc) -> Self { - let bright_character_color = config.theme().caret().bright().color().into(); - let blur_character_color = config.theme().caret().blur().color().into(); + let bright = config.theme().caret().bright().color().into(); + let blur = config.theme().caret().blur().color().into(); Self { state: CaretState::Bright, blink_delay: 0, - 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, - ), - bright_character_color, - blur_character_color, + 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, + ), + }, + colors: CaretColor { bright, blur }, pending: true, - text_position: 0, -// config, + position: CaretPosition { + text_position: 0, + line_number: 0, + line_position: 0, + }, + // config, } } @@ -63,44 +181,48 @@ impl Caret { } pub fn reset_caret(&mut self) { - self.dest = self.reset_position.clone(); - self.text_position = 0; + self.render_position.reset(); + self.position.reset(); } - pub fn move_caret(&mut self, position: usize, pos: Point) { - self.text_position = position; - self.dest.set_x(pos.x()); - self.dest.set_y(pos.y()); + pub fn move_caret(&mut self, position: CaretPosition, pos: Point) { + self.position = position; + self.render_position.move_to(&pos); } - pub fn text_position(&self) -> usize { - self.text_position + pub fn position(&self) -> &CaretPosition { + &self.position + } +} + +impl Deref for Caret { + type Target = CaretPosition; + + fn deref(&self) -> &::Target { + self.position() } } impl Render for Caret { fn render(&mut self, canvas: &mut WindowCanvas, renderer: &mut Renderer) -> UpdateResult { if self.pending { - use crate::renderer::managers::FontDetails; - let config = renderer.config().clone(); - let font = renderer - .font_manager() - .load(&FontDetails { - path: config.editor_config().font_path().clone(), - size: config.editor_config().character_size(), - }) - .unwrap_or_else(|_| panic!("Unable to load font")); - if let Ok((_, h)) = font.size_of_char('W') { - self.dest.set_height(h); - self.reset_position = self.dest.clone(); + 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 start = Point::new(self.dest.x(), self.dest.y()); - let end = Point::new(self.dest.x(), self.dest.y() + self.dest.height() as i32); + let dest = self.render_position.dest(); + 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.bright_character_color, - CaretState::Blur => &self.blur_character_color, + CaretState::Bright => self.colors.bright(), + CaretState::Blur => self.colors.blur(), }; canvas.set_draw_color(color.clone()); canvas @@ -128,6 +250,6 @@ impl ClickHandler for Caret { } fn is_left_click_target(&self, point: &Point) -> bool { - is_in_rect(point, &self.dest) + is_in_rect(point, &self.render_position.dest()) } } diff --git a/src/ui/file/editor_file.rs b/src/ui/file/editor_file.rs index c9c789e..ddf7472 100644 --- a/src/ui/file/editor_file.rs +++ b/src/ui/file/editor_file.rs @@ -48,6 +48,23 @@ impl EditorFile { None } + 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()), + _ => (), + } + } + + if vec.is_empty() { + None + } else { + Some(vec) + } + } + fn refresh_characters_position(&mut self) { let mut current: Rect = self.render_position.clone(); for section in self.sections.iter_mut() { diff --git a/src/ui/file/editor_file_section.rs b/src/ui/file/editor_file_section.rs index f770da3..c7c0071 100644 --- a/src/ui/file/editor_file_section.rs +++ b/src/ui/file/editor_file_section.rs @@ -48,6 +48,21 @@ impl EditorFileSection { } None } + + pub fn get_line(&self, line: &usize) -> Option> { + let mut vec: Vec<&TextCharacter> = vec![]; + for token in self.tokens.iter() { + match token.get_line(line) { + Some(v) => vec.append(&mut v.clone()), + _ => (), + }; + } + if vec.is_empty() { + None + } else { + Some(vec) + } + } } impl Render for EditorFileSection { diff --git a/src/ui/file/editor_file_token.rs b/src/ui/file/editor_file_token.rs index dd10b0e..36dc030 100644 --- a/src/ui/file/editor_file_token.rs +++ b/src/ui/file/editor_file_token.rs @@ -42,6 +42,33 @@ impl EditorFileToken { None } + pub fn get_line(&self, line: &usize) -> Option> { + let mut vec: Vec<&TextCharacter> = vec![]; + for c in self.characters.iter() { + let _tmp = (line.clone(), c.line().clone(), self.token_type.is_new_line(), c.text_character()); + match (line.clone(), c.line().clone(), self.token_type.is_new_line()) { + (0, 0, true) => { + vec.push(c); + }, + (a, b, true) if (a + 1) == b => { + vec.push(c); + }, + (a, b, true) if a != (b + 1) => + (), + (a, b, false) if a == b => { + vec.push(c); + } + _t => + (), + } + } + if vec.is_empty() { + None + } else { + Some(vec) + } + } + fn update_view(&mut self, renderer: &mut Renderer) -> UpdateResult { let config = renderer.config().theme().code_highlighting(); let color: Color = match self.token_type { diff --git a/src/ui/text_character.rs b/src/ui/text_character.rs index fb8b582..dbbdb05 100644 --- a/src/ui/text_character.rs +++ b/src/ui/text_character.rs @@ -1,10 +1,10 @@ use crate::app::{UpdateResult, WindowCanvas}; use crate::config::Config; use crate::lexer::TokenType; -use crate::renderer::managers::FontDetails; -use crate::renderer::managers::TextDetails; +use crate::renderer::managers::{TextDetails, FontDetails}; use crate::renderer::Renderer; use crate::ui::*; +use crate::ui::caret::CaretPosition; use sdl2::pixels::Color; use sdl2::rect::{Point, Rect}; @@ -79,7 +79,11 @@ impl TextCharacter { .load(&font_details) .unwrap_or_else(|_| panic!("Font not found {:?}", font_details)); - if let Some(rect) = get_text_character_rect(self.text_character.clone(), renderer) { + let c = match self.text_character { + '\n' => 'W', + c => c, + }; + if let Some(rect) = get_text_character_rect(c, renderer) { self.source = rect.clone(); self.dest = rect.clone(); } @@ -98,7 +102,7 @@ impl TextCharacter { } #[inline] - fn is_new_line(&self) -> bool { + pub fn is_new_line(&self) -> bool { self.text_character == '\n' } @@ -110,6 +114,14 @@ impl TextCharacter { 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 { @@ -154,7 +166,14 @@ impl Update for TextCharacter { impl ClickHandler for TextCharacter { fn on_left_click(&mut self, _point: &Point) -> UpdateResult { - UpdateResult::MoveCaret(self.dest().clone(), self.position()) + UpdateResult::MoveCaret( + self.dest().clone(), + CaretPosition::new( + self.position(), + self.line(), + 0, + ), + ) } fn is_left_click_target(&self, point: &Point) -> bool {