use crate::core::text::editor::{self, Action, Cursor}; use crate::core::text::LineHeight; use crate::core::{Font, Pixels, Point, Size}; use crate::text; use cosmic_text::Edit; use std::fmt; use std::sync::{self, Arc}; #[derive(Debug, PartialEq)] pub struct Editor(Option>); struct Internal { editor: cosmic_text::Editor, font: Font, bounds: Size, min_bounds: Size, version: text::Version, } impl Editor { pub fn new() -> Self { Self::default() } pub fn buffer(&self) -> &cosmic_text::Buffer { &self.internal().editor.buffer() } pub fn downgrade(&self) -> Weak { let editor = self.internal(); Weak { raw: Arc::downgrade(editor), bounds: editor.bounds, } } fn internal(&self) -> &Arc { self.0 .as_ref() .expect("editor should always be initialized") } } impl editor::Editor for Editor { type Font = Font; fn with_text(text: &str) -> Self { let mut buffer = cosmic_text::Buffer::new_empty(cosmic_text::Metrics { font_size: 1.0, line_height: 1.0, }); buffer.set_text( text::font_system() .write() .expect("Write font system") .raw(), text, cosmic_text::Attrs::new(), cosmic_text::Shaping::Advanced, ); Editor(Some(Arc::new(Internal { editor: cosmic_text::Editor::new(buffer), ..Default::default() }))) } fn cursor(&self) -> editor::Cursor { let internal = self.internal(); match internal.editor.select_opt() { Some(selection) => { // TODO Cursor::Selection(vec![]) } None => { let cursor = internal.editor.cursor(); let buffer = internal.editor.buffer(); let lines_before_cursor: usize = buffer .lines .iter() .take(cursor.line) .map(|line| { line.layout_opt() .as_ref() .expect("Line layout should be cached") .len() }) .sum(); let line = buffer .lines .get(cursor.line) .expect("Cursor line should be present"); let layout = line .layout_opt() .as_ref() .expect("Line layout should be cached"); let mut lines = layout.iter().enumerate(); let (subline, offset) = lines .find_map(|(i, line)| { let start = line .glyphs .first() .map(|glyph| glyph.start) .unwrap_or(0); let end = line .glyphs .last() .map(|glyph| glyph.end) .unwrap_or(0); let is_cursor_after_start = start <= cursor.index; let is_cursor_before_end = match cursor.affinity { cosmic_text::Affinity::Before => { cursor.index <= end } cosmic_text::Affinity::After => cursor.index < end, }; if is_cursor_after_start && is_cursor_before_end { let offset = line .glyphs .iter() .take_while(|glyph| cursor.index > glyph.start) .map(|glyph| glyph.w) .sum(); Some((i, offset)) } else { None } }) .unwrap_or((0, 0.0)); let line_height = buffer.metrics().line_height; let scroll_offset = buffer.scroll() as f32 * line_height; Cursor::Caret(Point::new( offset, (lines_before_cursor + subline) as f32 * line_height - scroll_offset, )) } } } fn perform(&mut self, action: Action) { let mut font_system = text::font_system().write().expect("Write font system"); let editor = self.0.take().expect("Editor should always be initialized"); // TODO: Handle multiple strong references somehow let mut internal = Arc::try_unwrap(editor) .expect("Editor cannot have multiple strong references"); let editor = &mut internal.editor; let mut act = |action| editor.action(font_system.raw(), action); match action { Action::MoveLeft => act(cosmic_text::Action::Left), Action::MoveRight => act(cosmic_text::Action::Right), Action::MoveUp => act(cosmic_text::Action::Up), Action::MoveDown => act(cosmic_text::Action::Down), Action::Insert(c) => act(cosmic_text::Action::Insert(c)), Action::Backspace => act(cosmic_text::Action::Backspace), Action::Delete => act(cosmic_text::Action::Delete), Action::Click(position) => act(cosmic_text::Action::Click { x: position.x as i32, y: position.y as i32, }), Action::Drag(position) => act(cosmic_text::Action::Drag { x: position.x as i32, y: position.y as i32, }), _ => todo!(), } editor.shape_as_needed(font_system.raw()); self.0 = Some(Arc::new(internal)); } fn bounds(&self) -> Size { self.internal().bounds } fn min_bounds(&self) -> Size { self.internal().min_bounds } fn update( &mut self, new_bounds: Size, new_font: Font, new_size: Pixels, new_line_height: LineHeight, ) { let editor = self.0.take().expect("editor should always be initialized"); let mut internal = Arc::try_unwrap(editor) .expect("Editor cannot have multiple strong references"); let mut font_system = text::font_system().write().expect("Write font system"); let mut changed = false; if new_font != internal.font { for line in internal.editor.buffer_mut().lines.iter_mut() { let _ = line.set_attrs_list(cosmic_text::AttrsList::new( text::to_attributes(new_font), )); } changed = true; } let metrics = internal.editor.buffer().metrics(); let new_line_height = new_line_height.to_absolute(new_size); if new_size.0 != metrics.font_size || new_line_height.0 != metrics.line_height { internal.editor.buffer_mut().set_metrics( font_system.raw(), cosmic_text::Metrics::new(new_size.0, new_line_height.0), ); changed = true; } if new_bounds != internal.bounds { internal.editor.buffer_mut().set_size( font_system.raw(), new_bounds.width, new_bounds.height, ); internal.bounds = new_bounds; changed = true; } if changed { internal.min_bounds = text::measure(&internal.editor.buffer()); } self.0 = Some(Arc::new(internal)); } } impl Default for Editor { fn default() -> Self { Self(Some(Arc::new(Internal::default()))) } } impl PartialEq for Internal { fn eq(&self, other: &Self) -> bool { self.font == other.font && self.bounds == other.bounds && self.min_bounds == other.min_bounds && self.editor.buffer().metrics() == other.editor.buffer().metrics() } } impl Default for Internal { fn default() -> Self { Self { editor: cosmic_text::Editor::new(cosmic_text::Buffer::new_empty( cosmic_text::Metrics { font_size: 1.0, line_height: 1.0, }, )), font: Font::default(), bounds: Size::ZERO, min_bounds: Size::ZERO, version: text::Version::default(), } } } impl fmt::Debug for Internal { fn fmt(&self, f: &mut fmt::Formatter<'_>) -> fmt::Result { f.debug_struct("Internal") .field("font", &self.font) .field("bounds", &self.bounds) .field("min_bounds", &self.min_bounds) .finish() } } #[derive(Debug, Clone)] pub struct Weak { raw: sync::Weak, pub bounds: Size, } impl Weak { pub fn upgrade(&self) -> Option { self.raw.upgrade().map(Some).map(Editor) } } impl PartialEq for Weak { fn eq(&self, other: &Self) -> bool { match (self.raw.upgrade(), other.raw.upgrade()) { (Some(p1), Some(p2)) => p1 == p2, _ => false, } } }