summaryrefslogblamecommitdiffstats
path: root/graphics/src/text/editor.rs
blob: 53f63fea867984ab3a0ea39056e755bd9f1047d6 (plain) (tree)






































































































































































































































































































































                                                                                
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,
        }
    }
}