From 6448429103c9c82b90040ac5a5a097bdded23f82 Mon Sep 17 00:00:00 2001
From: Héctor Ramón Jiménez <hector@hecrj.dev>
Date: Tue, 12 Sep 2023 14:51:00 +0200
Subject: Draft `Editor` API and `TextEditor` widget

---
 core/src/text/editor.rs    | 68 ++++++++++++++++++++++++++++++++++++++++++++++
 core/src/text/paragraph.rs | 59 ++++++++++++++++++++++++++++++++++++++++
 2 files changed, 127 insertions(+)
 create mode 100644 core/src/text/editor.rs
 create mode 100644 core/src/text/paragraph.rs

(limited to 'core/src/text')

diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs
new file mode 100644
index 00000000..a4fd0ec1
--- /dev/null
+++ b/core/src/text/editor.rs
@@ -0,0 +1,68 @@
+use crate::text::LineHeight;
+use crate::{Pixels, Point, Rectangle, Size};
+
+pub trait Editor: Sized + Default {
+    type Font: Copy + PartialEq + Default;
+
+    /// Creates a new [`Editor`] laid out with the given text.
+    fn with_text(text: &str) -> Self;
+
+    fn cursor(&self) -> Cursor;
+
+    fn perform(&mut self, action: Action);
+
+    /// Returns the current boundaries of the [`Editor`].
+    fn bounds(&self) -> Size;
+
+    /// Returns the minimum boundaries that can fit the contents of the
+    /// [`Editor`].
+    fn min_bounds(&self) -> Size;
+
+    /// Updates the [`Editor`] with some new attributes.
+    fn update(
+        &mut self,
+        new_bounds: Size,
+        new_font: Self::Font,
+        new_size: Pixels,
+        new_line_height: LineHeight,
+    );
+
+    /// Returns the minimum width that can fit the contents of the [`Editor`].
+    fn min_width(&self) -> f32 {
+        self.min_bounds().width
+    }
+
+    /// Returns the minimum height that can fit the contents of the [`Editor`].
+    fn min_height(&self) -> f32 {
+        self.min_bounds().height
+    }
+}
+
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Action {
+    MoveLeft,
+    MoveRight,
+    MoveUp,
+    MoveDown,
+    MoveLeftWord,
+    MoveRightWord,
+    MoveHome,
+    MoveEnd,
+    SelectWord,
+    SelectLine,
+    Insert(char),
+    Backspace,
+    Delete,
+    Click(Point),
+    Drag(Point),
+}
+
+/// The cursor of an [`Editor`].
+#[derive(Debug, Clone)]
+pub enum Cursor {
+    /// Cursor without a selection
+    Caret(Point),
+
+    /// Cursor selecting a range of text
+    Selection(Vec<Rectangle>),
+}
diff --git a/core/src/text/paragraph.rs b/core/src/text/paragraph.rs
new file mode 100644
index 00000000..de1fb74d
--- /dev/null
+++ b/core/src/text/paragraph.rs
@@ -0,0 +1,59 @@
+use crate::alignment;
+use crate::text::{Difference, Hit, Text};
+use crate::{Point, Size};
+
+/// A text paragraph.
+pub trait Paragraph: Sized + Default {
+    /// The font of this [`Paragraph`].
+    type Font: Copy + PartialEq;
+
+    /// Creates a new [`Paragraph`] laid out with the given [`Text`].
+    fn with_text(text: Text<'_, Self::Font>) -> Self;
+
+    /// Lays out the [`Paragraph`] with some new boundaries.
+    fn resize(&mut self, new_bounds: Size);
+
+    /// Compares the [`Paragraph`] with some desired [`Text`] and returns the
+    /// [`Difference`].
+    fn compare(&self, text: Text<'_, Self::Font>) -> Difference;
+
+    /// Returns the horizontal alignment of the [`Paragraph`].
+    fn horizontal_alignment(&self) -> alignment::Horizontal;
+
+    /// Returns the vertical alignment of the [`Paragraph`].
+    fn vertical_alignment(&self) -> alignment::Vertical;
+
+    /// Returns the minimum boundaries that can fit the contents of the
+    /// [`Paragraph`].
+    fn min_bounds(&self) -> Size;
+
+    /// Tests whether the provided point is within the boundaries of the
+    /// [`Paragraph`], returning information about the nearest character.
+    fn hit_test(&self, point: Point) -> Option<Hit>;
+
+    /// Returns the distance to the given grapheme index in the [`Paragraph`].
+    fn grapheme_position(&self, line: usize, index: usize) -> Option<Point>;
+
+    /// Updates the [`Paragraph`] to match the given [`Text`], if needed.
+    fn update(&mut self, text: Text<'_, Self::Font>) {
+        match self.compare(text) {
+            Difference::None => {}
+            Difference::Bounds => {
+                self.resize(text.bounds);
+            }
+            Difference::Shape => {
+                *self = Self::with_text(text);
+            }
+        }
+    }
+
+    /// Returns the minimum width that can fit the contents of the [`Paragraph`].
+    fn min_width(&self) -> f32 {
+        self.min_bounds().width
+    }
+
+    /// Returns the minimum height that can fit the contents of the [`Paragraph`].
+    fn min_height(&self) -> f32 {
+        self.min_bounds().height
+    }
+}
-- 
cgit