diff options
| author | 2023-09-12 14:51:00 +0200 | |
|---|---|---|
| committer | 2023-09-12 14:51:00 +0200 | |
| commit | 6448429103c9c82b90040ac5a5a097bdded23f82 (patch) | |
| tree | 79582bde4a7d6df71df0abefe35146b06452409f /graphics | |
| parent | 346af3f8b0baa418fd37b878bc2930ff0bd57cc0 (diff) | |
| download | iced-6448429103c9c82b90040ac5a5a097bdded23f82.tar.gz iced-6448429103c9c82b90040ac5a5a097bdded23f82.tar.bz2 iced-6448429103c9c82b90040ac5a5a097bdded23f82.zip  | |
Draft `Editor` API and `TextEditor` widget
Diffstat (limited to 'graphics')
| -rw-r--r-- | graphics/src/damage.rs | 7 | ||||
| -rw-r--r-- | graphics/src/primitive.rs | 10 | ||||
| -rw-r--r-- | graphics/src/renderer.rs | 14 | ||||
| -rw-r--r-- | graphics/src/text.rs | 2 | ||||
| -rw-r--r-- | graphics/src/text/editor.rs | 327 | 
5 files changed, 360 insertions, 0 deletions
diff --git a/graphics/src/damage.rs b/graphics/src/damage.rs index 3276c2d4..595cc274 100644 --- a/graphics/src/damage.rs +++ b/graphics/src/damage.rs @@ -66,6 +66,13 @@ impl<T: Damage> Damage for Primitive<T> {                  bounds.expand(1.5)              } +            Self::Editor { +                editor, position, .. +            } => { +                let bounds = Rectangle::new(*position, editor.bounds); + +                bounds.expand(1.5) +            }              Self::Quad { bounds, .. }              | Self::Image { bounds, .. }              | Self::Svg { bounds, .. } => bounds.expand(1.0), diff --git a/graphics/src/primitive.rs b/graphics/src/primitive.rs index 8a97e6e7..ce0b734b 100644 --- a/graphics/src/primitive.rs +++ b/graphics/src/primitive.rs @@ -4,6 +4,7 @@ use crate::core::image;  use crate::core::svg;  use crate::core::text;  use crate::core::{Background, Color, Font, Pixels, Point, Rectangle, Vector}; +use crate::text::editor;  use crate::text::paragraph;  use std::sync::Arc; @@ -41,6 +42,15 @@ pub enum Primitive<T> {          /// The color of the paragraph.          color: Color,      }, +    /// An editor primitive +    Editor { +        /// The [`editor::Weak`] reference. +        editor: editor::Weak, +        /// The position of the paragraph. +        position: Point, +        /// The color of the paragraph. +        color: Color, +    },      /// A quad primitive      Quad {          /// The bounds of the quad diff --git a/graphics/src/renderer.rs b/graphics/src/renderer.rs index c5033d36..9b699183 100644 --- a/graphics/src/renderer.rs +++ b/graphics/src/renderer.rs @@ -141,6 +141,7 @@ where  {      type Font = Font;      type Paragraph = text::Paragraph; +    type Editor = text::Editor;      const ICON_FONT: Font = Font::with_name("Iced-Icons");      const CHECKMARK_ICON: char = '\u{f00c}'; @@ -171,6 +172,19 @@ where          });      } +    fn fill_editor( +        &mut self, +        editor: &Self::Editor, +        position: Point, +        color: Color, +    ) { +        self.primitives.push(Primitive::Editor { +            editor: editor.downgrade(), +            position, +            color, +        }); +    } +      fn fill_text(          &mut self,          text: Text<'_, Self::Font>, diff --git a/graphics/src/text.rs b/graphics/src/text.rs index f5ccaf52..280e4f01 100644 --- a/graphics/src/text.rs +++ b/graphics/src/text.rs @@ -1,7 +1,9 @@  pub mod cache; +pub mod editor;  pub mod paragraph;  pub use cache::Cache; +pub use editor::Editor;  pub use paragraph::Paragraph;  pub use cosmic_text; diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs new file mode 100644 index 00000000..53f63fea --- /dev/null +++ b/graphics/src/text/editor.rs @@ -0,0 +1,327 @@ +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<Arc<Internal>>); + +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<Internal> { +        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<Internal>, +    pub bounds: Size, +} + +impl Weak { +    pub fn upgrade(&self) -> Option<Editor> { +        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, +        } +    } +}  | 
