summaryrefslogtreecommitdiffstats
path: root/core/src/text
diff options
context:
space:
mode:
Diffstat (limited to 'core/src/text')
-rw-r--r--core/src/text/editor.rs181
-rw-r--r--core/src/text/highlighter.rs88
-rw-r--r--core/src/text/paragraph.rs59
3 files changed, 328 insertions, 0 deletions
diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs
new file mode 100644
index 00000000..f3c6e342
--- /dev/null
+++ b/core/src/text/editor.rs
@@ -0,0 +1,181 @@
+//! Edit text.
+use crate::text::highlighter::{self, Highlighter};
+use crate::text::LineHeight;
+use crate::{Pixels, Point, Rectangle, Size};
+
+use std::sync::Arc;
+
+/// A component that can be used by widgets to edit multi-line text.
+pub trait Editor: Sized + Default {
+ /// The font of the [`Editor`].
+ type Font: Copy + PartialEq + Default;
+
+ /// Creates a new [`Editor`] laid out with the given text.
+ fn with_text(text: &str) -> Self;
+
+ /// Returns the current [`Cursor`] of the [`Editor`].
+ fn cursor(&self) -> Cursor;
+
+ /// Returns the current cursor position of the [`Editor`].
+ ///
+ /// Line and column, respectively.
+ fn cursor_position(&self) -> (usize, usize);
+
+ /// Returns the current selected text of the [`Editor`].
+ fn selection(&self) -> Option<String>;
+
+ /// Returns the text of the given line in the [`Editor`], if it exists.
+ fn line(&self, index: usize) -> Option<&str>;
+
+ /// Returns the amount of lines in the [`Editor`].
+ fn line_count(&self) -> usize;
+
+ /// Performs an [`Action`] on the [`Editor`].
+ fn perform(&mut self, action: Action);
+
+ /// Returns the current boundaries of the [`Editor`].
+ fn 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,
+ new_highlighter: &mut impl Highlighter,
+ );
+
+ /// Runs a text [`Highlighter`] in the [`Editor`].
+ fn highlight<H: Highlighter>(
+ &mut self,
+ font: Self::Font,
+ highlighter: &mut H,
+ format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
+ );
+}
+
+/// An interaction with an [`Editor`].
+#[derive(Debug, Clone, PartialEq)]
+pub enum Action {
+ /// Apply a [`Motion`].
+ Move(Motion),
+ /// Select text with a given [`Motion`].
+ Select(Motion),
+ /// Select the word at the current cursor.
+ SelectWord,
+ /// Select the line at the current cursor.
+ SelectLine,
+ /// Perform an [`Edit`].
+ Edit(Edit),
+ /// Click the [`Editor`] at the given [`Point`].
+ Click(Point),
+ /// Drag the mouse on the [`Editor`] to the given [`Point`].
+ Drag(Point),
+ /// Scroll the [`Editor`] a certain amount of lines.
+ Scroll {
+ /// The amount of lines to scroll.
+ lines: i32,
+ },
+}
+
+impl Action {
+ /// Returns whether the [`Action`] is an editing action.
+ pub fn is_edit(&self) -> bool {
+ matches!(self, Self::Edit(_))
+ }
+}
+
+/// An action that edits text.
+#[derive(Debug, Clone, PartialEq)]
+pub enum Edit {
+ /// Insert the given character.
+ Insert(char),
+ /// Paste the given text.
+ Paste(Arc<String>),
+ /// Break the current line.
+ Enter,
+ /// Delete the previous character.
+ Backspace,
+ /// Delete the next character.
+ Delete,
+}
+
+/// A cursor movement.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub enum Motion {
+ /// Move left.
+ Left,
+ /// Move right.
+ Right,
+ /// Move up.
+ Up,
+ /// Move down.
+ Down,
+ /// Move to the left boundary of a word.
+ WordLeft,
+ /// Move to the right boundary of a word.
+ WordRight,
+ /// Move to the start of the line.
+ Home,
+ /// Move to the end of the line.
+ End,
+ /// Move to the start of the previous window.
+ PageUp,
+ /// Move to the start of the next window.
+ PageDown,
+ /// Move to the start of the text.
+ DocumentStart,
+ /// Move to the end of the text.
+ DocumentEnd,
+}
+
+impl Motion {
+ /// Widens the [`Motion`], if possible.
+ pub fn widen(self) -> Self {
+ match self {
+ Self::Left => Self::WordLeft,
+ Self::Right => Self::WordRight,
+ Self::Home => Self::DocumentStart,
+ Self::End => Self::DocumentEnd,
+ _ => self,
+ }
+ }
+
+ /// Returns the [`Direction`] of the [`Motion`].
+ pub fn direction(&self) -> Direction {
+ match self {
+ Self::Left
+ | Self::Up
+ | Self::WordLeft
+ | Self::Home
+ | Self::PageUp
+ | Self::DocumentStart => Direction::Left,
+ Self::Right
+ | Self::Down
+ | Self::WordRight
+ | Self::End
+ | Self::PageDown
+ | Self::DocumentEnd => Direction::Right,
+ }
+ }
+}
+
+/// A direction in some text.
+#[derive(Debug, Clone, Copy, PartialEq, Eq)]
+pub enum Direction {
+ /// <-
+ Left,
+ /// ->
+ Right,
+}
+
+/// 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/highlighter.rs b/core/src/text/highlighter.rs
new file mode 100644
index 00000000..a0535228
--- /dev/null
+++ b/core/src/text/highlighter.rs
@@ -0,0 +1,88 @@
+//! Highlight text.
+use crate::Color;
+
+use std::ops::Range;
+
+/// A type capable of highlighting text.
+///
+/// A [`Highlighter`] highlights lines in sequence. When a line changes,
+/// it must be notified and the lines after the changed one must be fed
+/// again to the [`Highlighter`].
+pub trait Highlighter: 'static {
+ /// The settings to configure the [`Highlighter`].
+ type Settings: PartialEq + Clone;
+
+ /// The output of the [`Highlighter`].
+ type Highlight;
+
+ /// The highlight iterator type.
+ type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
+ where
+ Self: 'a;
+
+ /// Creates a new [`Highlighter`] from its [`Self::Settings`].
+ fn new(settings: &Self::Settings) -> Self;
+
+ /// Updates the [`Highlighter`] with some new [`Self::Settings`].
+ fn update(&mut self, new_settings: &Self::Settings);
+
+ /// Notifies the [`Highlighter`] that the line at the given index has changed.
+ fn change_line(&mut self, line: usize);
+
+ /// Highlights the given line.
+ ///
+ /// If a line changed prior to this, the first line provided here will be the
+ /// line that changed.
+ fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>;
+
+ /// Returns the current line of the [`Highlighter`].
+ ///
+ /// If `change_line` has been called, this will normally be the least index
+ /// that changed.
+ fn current_line(&self) -> usize;
+}
+
+/// A highlighter that highlights nothing.
+#[derive(Debug, Clone, Copy)]
+pub struct PlainText;
+
+impl Highlighter for PlainText {
+ type Settings = ();
+ type Highlight = ();
+
+ type Iterator<'a> = std::iter::Empty<(Range<usize>, Self::Highlight)>;
+
+ fn new(_settings: &Self::Settings) -> Self {
+ Self
+ }
+
+ fn update(&mut self, _new_settings: &Self::Settings) {}
+
+ fn change_line(&mut self, _line: usize) {}
+
+ fn highlight_line(&mut self, _line: &str) -> Self::Iterator<'_> {
+ std::iter::empty()
+ }
+
+ fn current_line(&self) -> usize {
+ usize::MAX
+ }
+}
+
+/// The format of some text.
+#[derive(Debug, Clone, Copy, PartialEq)]
+pub struct Format<Font> {
+ /// The [`Color`] of the text.
+ pub color: Option<Color>,
+ /// The `Font` of the text.
+ pub font: Option<Font>,
+}
+
+impl<Font> Default for Format<Font> {
+ fn default() -> Self {
+ Self {
+ color: None,
+ font: None,
+ }
+ }
+}
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
+ }
+}