Draft Editor API and TextEditor widget

This commit is contained in:
Héctor Ramón Jiménez 2023-09-12 14:51:00 +02:00
parent 346af3f8b0
commit 6448429103
No known key found for this signature in database
GPG key ID: 7CC46565708259A7
25 changed files with 1384 additions and 92 deletions

View file

@ -2,7 +2,7 @@
use crate::{Length, Padding, Size};
/// A set of size constraints for layouting.
#[derive(Debug, Clone, Copy)]
#[derive(Debug, Clone, Copy, PartialEq)]
pub struct Limits {
min: Size,
max: Size,

View file

@ -12,7 +12,7 @@
#![forbid(unsafe_code, rust_2018_idioms)]
#![deny(
missing_debug_implementations,
missing_docs,
// missing_docs,
unused_results,
clippy::extra_unused_lifetimes,
clippy::from_over_into,

View file

@ -43,6 +43,7 @@ impl Renderer for Null {
impl text::Renderer for Null {
type Font = Font;
type Paragraph = ();
type Editor = ();
const ICON_FONT: Font = Font::DEFAULT;
const CHECKMARK_ICON: char = '0';
@ -66,6 +67,14 @@ impl text::Renderer for Null {
) {
}
fn fill_editor(
&mut self,
_editor: &Self::Editor,
_position: Point,
_color: Color,
) {
}
fn fill_text(
&mut self,
_paragraph: Text<'_, Self::Font>,
@ -106,3 +115,32 @@ impl text::Paragraph for () {
None
}
}
impl text::Editor for () {
type Font = Font;
fn with_text(_text: &str) -> Self {}
fn cursor(&self) -> text::editor::Cursor {
text::editor::Cursor::Caret(Point::ORIGIN)
}
fn perform(&mut self, _action: text::editor::Action) {}
fn bounds(&self) -> Size {
Size::ZERO
}
fn min_bounds(&self) -> Size {
Size::ZERO
}
fn update(
&mut self,
_new_bounds: Size,
_new_font: Self::Font,
_new_size: Pixels,
_new_line_height: text::LineHeight,
) {
}
}

View file

@ -1,4 +1,11 @@
//! Draw and interact with text.
mod paragraph;
pub mod editor;
pub use editor::Editor;
pub use paragraph::Paragraph;
use crate::alignment;
use crate::{Color, Pixels, Point, Size};
@ -126,6 +133,31 @@ impl Hit {
}
}
/// The difference detected in some text.
///
/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
/// [`Text`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Difference {
/// No difference.
///
/// The text can be reused as it is!
None,
/// A bounds difference.
///
/// This normally means a relayout is necessary, but the shape of the text can
/// be reused.
Bounds,
/// A shape difference.
///
/// The contents, alignment, sizes, fonts, or any other essential attributes
/// of the shape of the text have changed. A complete reshape and relayout of
/// the text is necessary.
Shape,
}
/// A renderer capable of measuring and drawing [`Text`].
pub trait Renderer: crate::Renderer {
/// The font type used.
@ -134,6 +166,9 @@ pub trait Renderer: crate::Renderer {
/// The [`Paragraph`] of this [`Renderer`].
type Paragraph: Paragraph<Font = Self::Font> + 'static;
/// The [`Editor`] of this [`Renderer`].
type Editor: Editor<Font = Self::Font> + 'static;
/// The icon font of the backend.
const ICON_FONT: Self::Font;
@ -165,6 +200,13 @@ pub trait Renderer: crate::Renderer {
color: Color,
);
fn fill_editor(
&mut self,
editor: &Self::Editor,
position: Point,
color: Color,
);
/// Draws the given [`Text`] at the given position and with the given
/// [`Color`].
fn fill_text(
@ -174,84 +216,3 @@ pub trait Renderer: crate::Renderer {
color: Color,
);
}
/// 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
}
}
/// The difference detected in some text.
///
/// You will obtain a [`Difference`] when you [`compare`] a [`Paragraph`] with some
/// [`Text`].
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
pub enum Difference {
/// No difference.
///
/// The text can be reused as it is!
None,
/// A bounds difference.
///
/// This normally means a relayout is necessary, but the shape of the text can
/// be reused.
Bounds,
/// A shape difference.
///
/// The contents, alignment, sizes, fonts, or any other essential attributes
/// of the shape of the text have changed. A complete reshape and relayout of
/// the text is necessary.
Shape,
}

68
core/src/text/editor.rs Normal file
View file

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

View file

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