diff --git a/resources/prefs.yaml b/resources/prefs.yaml index cf472b3..cb86cf0 100644 --- a/resources/prefs.yaml +++ b/resources/prefs.yaml @@ -1,11 +1,11 @@ --- recent_folders: - - /Users/Dorian/Desktop/COurs ENSAM 1A/langues - /Users/Dorian/Desktop + - /Users/Dorian/Desktop/COurs ENSAM 1A/langues - /Users/Dorian/Desktop/COurs ENSAM 1A - /Users/Dorian/Desktop/Code/text-editor - /Users/Dorian/Dropbox recent_files: + - /Users/Dorian/Desktop/test.drn - /Users/Dorian/Desktop/COurs ENSAM 1A/langues/anglais-questions.txt - /Users/Dorian/Desktop/nathan.drn - - /Users/Dorian/Desktop/test.drn diff --git a/src/editor.rs b/src/editor.rs index cc7895d..d5338a8 100644 --- a/src/editor.rs +++ b/src/editor.rs @@ -1,4 +1,5 @@ use std::{cmp, env, fs}; +use std::any::TypeId; use std::cell::RefCell; use std::rc::Rc; use std::path::{Path, PathBuf}; @@ -23,12 +24,14 @@ use crate::cursor::{Cursor, CURSOR_OFFSET_X}; use crate::camera::Camera; use crate::contextual_menu::{ContextualMenu, MenuItem}; use crate::{Animation, EditorEvent, FocusElement, MenuId}; +use crate::style_range::StyleRange; use crate::menu_actions::MenuAction; use crate::font::Font; use crate::line::Line; use crate::range::Range; use crate::selection::Selection; use crate::editable::Editable; +use crate::range_trait::RangeTrait; use crate::stats::Stats; pub const EDITOR_PADDING: f32 = 10.; @@ -47,12 +50,11 @@ pub struct Editor { pub filepath: Option, pub event_sender: Option>, pub selection: Selection, - pub underline_buffer: Vec, - pub bold_buffer: Vec, + pub style_buffer: Vec, // a buffer that keeps track of every style in the document pub menu: ContextualMenu, pub cached_prefs: Option, pub stats: Stats, - pub should_edit_file: bool, // so the input does not trigger file specific events + pub should_edit_file: bool, // so the input internal editor does not trigger file specific events } impl Editor { @@ -72,8 +74,7 @@ impl Editor { modifiers: ModifiersState::default(), filepath: Option::None, event_sender: Option::None, - underline_buffer: vec![], - bold_buffer: vec![], + style_buffer: vec![], menu: ContextualMenu::new(system_font), cached_prefs: Option::None, offset, @@ -156,7 +157,7 @@ impl Editable for Editor { VirtualKeyCode::Delete => { self.move_cursor_relative(1, 0); self.delete_char(); }, VirtualKeyCode::Return => if self.modifiers.alt() { self.toggle_ai_contextual_menu() } else { self.new_line() }, VirtualKeyCode::Escape => self.menu.close(), - VirtualKeyCode::Tab => if self.modifiers.alt() { self.menu.open() }, + VirtualKeyCode::Tab => if self.modifiers.alt() { self.menu.open() } else { self.add_text(" ") }, _ => { return; }, } self.update_text_layout(); @@ -390,7 +391,7 @@ impl Editor { self.event_sender.as_ref().unwrap().send_event(event).unwrap(); } - pub fn set_dirty(&mut self, dirty: bool) { + pub fn set_dirty(&mut self, dirty: bool) { // Sert the editor in a "unsave" state --> display a star in the title bar let path = self.filepath.clone().unwrap_or(String::from("")); self.send_event(EditorEvent::SetDirty(path, dirty)); // Set the editor dirty } @@ -634,35 +635,37 @@ impl Editor { } /// Add a range to a buffer according to the underline/bold rules - fn add_range_to_buffer(range: Range, buffer: &mut Vec) { + fn add_range_to_buffer(range_like: T, buffer: &mut Vec) { + // switch on the type of the generic parameter to determine wether it's a simple Range or a StyledRange + let range = range_like.get_range(); if !range.is_valid() { return; } let len = buffer.len(); for mut i in 0 .. len { assert!(len >= 1); i = len - 1 - i; - let buffer_range = buffer.get_mut(i).unwrap(); - if range == *buffer_range { buffer.remove(i); return; } + let buffer_range = buffer.get_mut(i).unwrap().get_range(); + if range == buffer_range { buffer.remove(i); return; } else if range.include(buffer_range) { buffer.remove(i); } - else if buffer_range.include(&range) { + else if buffer_range.include(range) { assert!(buffer_range.is_valid()); - let before = Range::new(buffer_range.get_real_start().unwrap(), range.get_real_start().unwrap()); - let after = Range::new(range.get_real_end().unwrap(), buffer_range.get_real_end().unwrap()); + let before = T::new(buffer_range.get_real_start().unwrap(), range.get_real_start().unwrap()); + let after = T::new(range.get_real_end().unwrap(), buffer_range.get_real_end().unwrap()); if before.is_valid() { buffer.push(before); } if after.is_valid() { buffer.push(after); } buffer.remove(i); return; } } - buffer.push(range); + buffer.push(range_like); // to push the proper struct and not only the range } pub fn underline(&mut self) { - Self::add_range_to_buffer(self.selection.get_range(), &mut self.underline_buffer); + Self::add_range_to_buffer(StyleRange::new_underline(self.selection.get_range()), &mut self.style_buffer); self.set_dirty(true); } pub fn bold(&mut self) { - Self::add_range_to_buffer(self.selection.get_range(), &mut self.bold_buffer); + Self::add_range_to_buffer(StyleRange::new_bold(self.selection.get_range()), &mut self.style_buffer); self.set_dirty(true); } @@ -907,7 +910,10 @@ impl Editor { let mut encode = String::new(); // Encode underline encode.push_str("#u: "); - let underline_ranges = self.underline_buffer + let bold_buffer: Vec = self.style_buffer.iter().filter(|sr| sr.bold == true).map(|sr| sr.range).collect(); + let underline_buffer: Vec = self.style_buffer.iter().filter(|sr| sr.underline == true).map(|sr| sr.range).collect(); + + let underline_ranges = underline_buffer .iter() .map(|r| r.get_id() + ",") .filter(|id| id != "Invalid range") @@ -916,7 +922,7 @@ impl Editor { encode.push_str("\n"); // Encode bold encode.push_str("#b: "); - let bold_ranges = self.bold_buffer + let bold_ranges = bold_buffer .iter() .map(|r| r.get_id() + ",") .filter(|id| id != "Invalid range") @@ -958,8 +964,7 @@ impl Editor { pub fn load_txt_file(&mut self, filepath: &str) { let valid_filepath = fs::canonicalize(filepath).expect("Invalid filepath"); self.lines = vec![Line::new(Rc::clone(&self.font))]; - self.underline_buffer = vec![]; - self.bold_buffer = vec![]; + self.style_buffer = vec![]; self.selection.reset(); self.filepath = Some(filepath.into()); let file_content = fs::read_to_string(&valid_filepath).expect(&format!("Unable to load file to {}", filepath)); @@ -988,10 +993,15 @@ impl Editor { self.lines = vec![Line::new(Rc::clone(&self.font))]; self.selection.reset(); self.filepath = Some(filepath.into()); + self.style_buffer = vec![]; let file_content = fs::read_to_string(&valid_filepath).expect(&format!("Unable to load file to {}", filepath)); let content_lines = file_content.split('\n').collect(); - self.underline_buffer = Range::get_ranges_from_drn_line("#u:", &content_lines); - self.bold_buffer = Range::get_ranges_from_drn_line("#b:", &content_lines); + // Handle style + let underline_buffer = Range::get_ranges_from_drn_line("#u:", &content_lines); + let bold_buffer = Range::get_ranges_from_drn_line("#b:", &content_lines); + for range in underline_buffer { self.style_buffer.push(StyleRange::new_underline(range)) } + for range in bold_buffer { self.style_buffer.push(StyleRange::new_bold(range)) } + // Handle text for (i, line) in content_lines[2..].iter().enumerate() { if i < self.lines.len() { self.lines.push(Line::new(Rc::clone(&self.font))); @@ -1038,7 +1048,7 @@ impl Editor { pub fn update_text_layout(&mut self) { let mut difference = 0; for (i, line) in (&mut self.lines).iter_mut().enumerate() { - let diff = line.update_text_layout(); + let diff = line.update_text_layout(&self.style_buffer); if i as u32 == self.cursor.y { difference = diff; } } self.font.borrow_mut().style_changed = false; @@ -1087,7 +1097,8 @@ impl Editor { let line_camera = Camera::from_with_offset(&self.camera, Vector2::new(-line_offset, 0.)); // draw underline - for range in &mut self.underline_buffer { + let mut underline_buffer: Vec = self.style_buffer.iter_mut().filter(|sr| sr.underline == true).map(|sr| sr.range).collect(); + for range in &mut underline_buffer { assert!(range.is_valid()); let line = &self.lines[range.start.unwrap().y as usize]; let line_offset = line.alignment_offset; diff --git a/src/font.rs b/src/font.rs index dc8b922..d0a4799 100644 --- a/src/font.rs +++ b/src/font.rs @@ -58,7 +58,7 @@ impl Font { pub fn layout_text(&self, text: &str, text_layout_options: TextOptions) -> Rc { let escaped_text = self.format(text) - .replace('\t', " ")// Just for rendering + .replace('\t', " ")// Just for rendering .replace(" " ,"\u{a0}"); // Just for rendering self.s2d_font.layout_text(&escaped_text, 2.0 * self.size as f32, text_layout_options) } diff --git a/src/line.rs b/src/line.rs index 98cb8e9..e02c7b1 100644 --- a/src/line.rs +++ b/src/line.rs @@ -5,8 +5,10 @@ use speedy2d::color::Color; use speedy2d::dimen::Vector2; use speedy2d::font::{FormattedTextBlock, TextAlignment, TextOptions}; use speedy2d::Graphics2D; +use crate::style_range::StyleRange; use crate::font::Font; +use crate::range::Range; const INITIAL_LINE_CAPACITY: usize = 1024; @@ -93,7 +95,7 @@ impl Line { (start_index, end_index) } - pub fn update_text_layout(&mut self) -> i32 { // return the difference of length + pub fn update_text_layout(&mut self, style_buffer: &Vec) -> i32 { // return the difference of length let string = self.get_text(); let font = self.font.borrow(); let font_formatted_string = font.format(&string); diff --git a/src/main.rs b/src/main.rs index d06d897..f936597 100644 --- a/src/main.rs +++ b/src/main.rs @@ -18,6 +18,10 @@ mod menu_actions; mod stats; mod open_ai_wrapper; mod loader; +mod style_range; +mod range_trait; + + // Uncomment to load TESL parser // mod tesl; diff --git a/src/range.rs b/src/range.rs index f339302..a9d85a3 100644 --- a/src/range.rs +++ b/src/range.rs @@ -10,6 +10,7 @@ use speedy2d::shape::Rectangle; use crate::camera::Camera; use crate::font::Font; use crate::line::Line; +use crate::range_trait::RangeTrait; pub fn get_line_length(i: u32, lines: &[Line]) -> u32 { if i + 1 > lines.len() as u32 { return 0; } // Prevent overflow @@ -56,28 +57,30 @@ pub fn vector_min(v1: Vector2, v2: Vector2) -> Vector2 { if vector_max(v1, v2) == v2 { v1 } else { v2 } } -impl Range { - pub fn new(start: Vector2, end: Vector2) -> Self { +impl RangeTrait for Range { + fn new(start: Vector2, end: Vector2) -> Self { Self { start: Some(start), end: Some(end), } } - pub fn start(&mut self, position: Vector2) { + fn get_range(&self) -> &Range { &self } + + fn start(&mut self, position: Vector2) { self.start = Some(Vector2::new(position.x, position.y)); } - pub fn end(&mut self, position: Vector2) { + fn end(&mut self, position: Vector2) { self.end = Some(Vector2::new(position.x, position.y)); } - pub fn reset(&mut self) { + fn reset(&mut self) { self.start = Option::None; self.end = Option::None; } - pub fn add(&mut self, other: Range) { + fn add(&mut self, other: Range) { if !other.is_valid() { return; } if !self.is_valid() { self.start(other.start.unwrap()); @@ -90,34 +93,34 @@ impl Range { self.end(end); } - pub fn include(&self, other: &Range) -> bool { + fn include(&self, other: &Range) -> bool { if !self.is_valid() || !other.is_valid() { return false; } vector_min(self.start.unwrap(), other.start.unwrap()) == self.start.unwrap() && vector_max(self.end.unwrap(), other.end.unwrap()) == self.end.unwrap() } - pub fn is_valid(&self) -> bool { + fn is_valid(&self) -> bool { self.start.is_some() && self.end.is_some() && self.start != self.end } - pub fn get_id(&self) -> String { + fn get_id(&self) -> String { if !self.is_valid() { return "Invalid range".to_string() } let start = self.start.unwrap(); let end = self.end.unwrap(); format!("{}-{}-{}-{}", start.x, start.y, end.x, end.y) } - pub fn get_real_start(&self) -> Option> { + fn get_real_start(&self) -> Option> { if !self.is_valid() { return Option::None; } Some(vector_min(self.start.unwrap(), self.end.unwrap())) } - pub fn get_real_end(&self) -> Option> { + fn get_real_end(&self) -> Option> { if !self.is_valid() { return Option::None; } Some(vector_max(self.start.unwrap(), self.end.unwrap())) } - pub fn get_ranges_from_drn_line(pattern: &str, lines: &Vec<&str>) -> Vec { + fn get_ranges_from_drn_line(pattern: &str, lines: &Vec<&str>) -> Vec { let mut result = vec![]; let mut line = lines .iter() @@ -138,7 +141,7 @@ impl Range { result } - pub fn get_lines_index(&mut self, lines: &[Line]) -> Vec<(u32, u32)> { + fn get_lines_index(&mut self, lines: &[Line]) -> Vec<(u32, u32)> { // relative index of selection starting in the self.start.y index if !self.is_valid() { return vec![]; } let start = self.get_real_start().unwrap(); @@ -154,7 +157,7 @@ impl Range { result } - pub fn _render(&mut self, font: Rc>, lines: &[Line], camera: &Camera, graphics: &mut Graphics2D) { + fn _render(&mut self, font: Rc>, lines: &[Line], camera: &Camera, graphics: &mut Graphics2D) { if !self.is_valid() { return; } let font_width = font.borrow().char_width; let font_height = font.borrow().char_height; @@ -173,5 +176,4 @@ impl Range { ) } } - } \ No newline at end of file diff --git a/src/range_trait.rs b/src/range_trait.rs new file mode 100644 index 0000000..a9d5b46 --- /dev/null +++ b/src/range_trait.rs @@ -0,0 +1,38 @@ +use std::cell::RefCell; +use std::rc::Rc; +use speedy2d::dimen::Vector2; +use speedy2d::Graphics2D; +use crate::camera::Camera; +use crate::font::Font; +use crate::line::Line; +use crate::range::Range; + +pub trait RangeTrait { + fn new(start: Vector2, end: Vector2) -> Self; + + fn get_range(&self) -> &Range; + + fn start(&mut self, position: Vector2); + + fn end(&mut self, position: Vector2); + + fn reset(&mut self); + + fn add(&mut self, other: Self); + + fn include(&self, other: &Self) -> bool; + + fn is_valid(&self) -> bool; + + fn get_id(&self) -> String; + + fn get_real_start(&self) -> Option>; + + fn get_real_end(&self) -> Option>; + + fn get_ranges_from_drn_line(pattern: &str, lines: &Vec<&str>) -> Vec; + + fn get_lines_index(&mut self, lines: &[Line]) -> Vec<(u32, u32)>; + + fn _render(&mut self, font: Rc>, lines: &[Line], camera: &Camera, graphics: &mut Graphics2D); +} \ No newline at end of file diff --git a/src/selection.rs b/src/selection.rs index 5f8e9e9..b7d61a7 100644 --- a/src/selection.rs +++ b/src/selection.rs @@ -13,6 +13,7 @@ use crate::camera::Camera; use crate::font::Font; use crate::line::Line; use crate::range::{get_line_length, Range}; +use crate::range_trait::RangeTrait; const ANIMATION_DURATION: f32 = 100.; // ms diff --git a/src/style_range.rs b/src/style_range.rs new file mode 100644 index 0000000..8926068 --- /dev/null +++ b/src/style_range.rs @@ -0,0 +1,160 @@ +use std::cell::RefCell; +use std::fmt::{Debug, Formatter}; +use std::rc::Rc; +use speedy2d::color::Color; +use speedy2d::dimen::Vector2; +use speedy2d::Graphics2D; +use crate::camera::Camera; +use crate::font::Font; +use crate::line::Line; +use crate::range::Range; +use crate::range_trait::RangeTrait; + +#[derive(Clone, Copy)] +pub struct StyleRange { + pub color: Color, + pub bold: bool, + pub underline: bool, + pub strikethrough: bool, // barré + pub range: Range, +} + +impl Default for StyleRange { + fn default() -> Self { + Self { + color: Color::BLACK, + bold: false, + underline: false, + strikethrough: false, + range: Range::default() + } + } +} + +impl Debug for StyleRange { + fn fmt(&self, f: &mut Formatter<'_>) -> std::fmt::Result { + let start_text = if let Some(start) = self.range.start { start.x.to_string() + "," + &start.y.to_string() } else { "None".to_owned() } ; + let end_text = if let Some(end) = self.range.end { end.x.to_string() + "," + &end.y.to_string() } else { "None".to_owned() } ; + write!(f, "Range : {} - {}", start_text, end_text) + } +} + +impl PartialEq for StyleRange { + fn eq(&self, other: &Self) -> bool { + self.range.eq(&other.range) + } +} + +impl StyleRange { + fn new_with_parameters(start: Vector2, end: Vector2, color: Color, bold: bool, underline: bool, strikethrough: bool) -> Self { + Self { + range: Range::new(start, end), + color, + bold, + underline, + strikethrough, + } + } + + pub fn new_colored(range: Range, color: Color) -> Self { + Self { + range, + color, + bold: false, + underline: false, + strikethrough: false, + } + } + + pub fn new_bold(range: Range) -> Self { + Self { + range, + color: Color::BLACK, + bold: true, + underline: false, + strikethrough: false, + } + } + + pub fn new_underline(range: Range) -> Self { + Self { + range, + color: Color::BLACK, + bold: false, + underline: true, + strikethrough: false, + } + } + + pub fn new_strikethrough(range: Range) -> Self { + Self { + range, + color: Color::BLACK, + bold: false, + underline: false, + strikethrough: true, + } + } +} + +impl RangeTrait for StyleRange { + fn new(start: Vector2, end: Vector2,) -> Self { + Self { + range: Range::new(start, end), + color: Color::BLACK, + bold: false, + underline: false, + strikethrough: false, + } + } + + fn get_range(&self) -> &Range { &self.range } + + fn start(&mut self, position: Vector2) { + self.range.start(position) + } + + fn end(&mut self, position: Vector2) { + self.range.end(position) + } + + fn reset(&mut self) { + self.range.reset() + } + + fn add(&mut self, other: StyleRange) { + self.range.add(other.range) + } + + fn include(&self, other: &StyleRange) -> bool { + self.range.include(&other.range) + } + + fn is_valid(&self) -> bool { + self.range.is_valid() + } + + fn get_id(&self) -> String { + self.range.get_id() + } + + fn get_real_start(&self) -> Option> { + self.range.get_real_start() + } + + fn get_real_end(&self) -> Option> { + self.range.get_real_end() + } + + fn get_ranges_from_drn_line(pattern: &str, lines: &Vec<&str>) -> Vec { + Range::get_ranges_from_drn_line(pattern, lines) + } + + fn get_lines_index(&mut self, lines: &[Line]) -> Vec<(u32, u32)> { + self.range.get_lines_index(lines) + } + + fn _render(&mut self, font: Rc>, lines: &[Line], camera: &Camera, graphics: &mut Graphics2D) { + self.range._render(font, lines, camera, graphics) + } +} \ No newline at end of file