Write documentation for the new text APIs
This commit is contained in:
parent
6582387579
commit
625cd745f3
16 changed files with 185 additions and 22 deletions
|
|
@ -12,7 +12,7 @@
|
||||||
#![forbid(unsafe_code, rust_2018_idioms)]
|
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
// missing_docs,
|
missing_docs,
|
||||||
unused_results,
|
unused_results,
|
||||||
rustdoc::broken_intra_doc_links
|
rustdoc::broken_intra_doc_links
|
||||||
)]
|
)]
|
||||||
|
|
|
||||||
|
|
@ -61,6 +61,7 @@ impl Click {
|
||||||
self.kind
|
self.kind
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the position of the [`Click`].
|
||||||
pub fn position(&self) -> Point {
|
pub fn position(&self) -> Point {
|
||||||
self.position
|
self.position
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -204,6 +204,8 @@ pub trait Renderer: crate::Renderer {
|
||||||
color: Color,
|
color: Color,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Draws the given [`Editor`] at the given position and with the given
|
||||||
|
/// [`Color`].
|
||||||
fn fill_editor(
|
fn fill_editor(
|
||||||
&mut self,
|
&mut self,
|
||||||
editor: &Self::Editor,
|
editor: &Self::Editor,
|
||||||
|
|
|
||||||
|
|
@ -1,25 +1,36 @@
|
||||||
|
//! Edit text.
|
||||||
use crate::text::highlighter::{self, Highlighter};
|
use crate::text::highlighter::{self, Highlighter};
|
||||||
use crate::text::LineHeight;
|
use crate::text::LineHeight;
|
||||||
use crate::{Pixels, Point, Rectangle, Size};
|
use crate::{Pixels, Point, Rectangle, Size};
|
||||||
|
|
||||||
use std::sync::Arc;
|
use std::sync::Arc;
|
||||||
|
|
||||||
|
/// A component that can be used by widgets to edit multi-line text.
|
||||||
pub trait Editor: Sized + Default {
|
pub trait Editor: Sized + Default {
|
||||||
|
/// The [`Font`] of the [`Editor`].
|
||||||
type Font: Copy + PartialEq + Default;
|
type Font: Copy + PartialEq + Default;
|
||||||
|
|
||||||
/// Creates a new [`Editor`] laid out with the given text.
|
/// Creates a new [`Editor`] laid out with the given text.
|
||||||
fn with_text(text: &str) -> Self;
|
fn with_text(text: &str) -> Self;
|
||||||
|
|
||||||
|
/// Returns the current [`Cursor`] of the [`Editor`].
|
||||||
fn cursor(&self) -> Cursor;
|
fn cursor(&self) -> Cursor;
|
||||||
|
|
||||||
|
/// Returns the current cursor position of the [`Editor`].
|
||||||
|
///
|
||||||
|
/// Line and column, respectively.
|
||||||
fn cursor_position(&self) -> (usize, usize);
|
fn cursor_position(&self) -> (usize, usize);
|
||||||
|
|
||||||
|
/// Returns the current selected text of the [`Editor`].
|
||||||
fn selection(&self) -> Option<String>;
|
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>;
|
fn line(&self, index: usize) -> Option<&str>;
|
||||||
|
|
||||||
|
/// Returns the amount of lines in the [`Editor`].
|
||||||
fn line_count(&self) -> usize;
|
fn line_count(&self) -> usize;
|
||||||
|
|
||||||
|
/// Performs an [`Action`] on the [`Editor`].
|
||||||
fn perform(&mut self, action: Action);
|
fn perform(&mut self, action: Action);
|
||||||
|
|
||||||
/// Returns the current boundaries of the [`Editor`].
|
/// Returns the current boundaries of the [`Editor`].
|
||||||
|
|
@ -35,6 +46,7 @@ pub trait Editor: Sized + Default {
|
||||||
new_highlighter: &mut impl Highlighter,
|
new_highlighter: &mut impl Highlighter,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
/// Runs a text [`Highlighter`] in the [`Editor`].
|
||||||
fn highlight<H: Highlighter>(
|
fn highlight<H: Highlighter>(
|
||||||
&mut self,
|
&mut self,
|
||||||
font: Self::Font,
|
font: Self::Font,
|
||||||
|
|
@ -43,50 +55,83 @@ pub trait Editor: Sized + Default {
|
||||||
);
|
);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An interaction with an [`Editor`].
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Action {
|
pub enum Action {
|
||||||
|
/// Apply a [`Motion`].
|
||||||
Move(Motion),
|
Move(Motion),
|
||||||
|
/// Select text with a given [`Motion`].
|
||||||
Select(Motion),
|
Select(Motion),
|
||||||
|
/// Select the word at the current cursor.
|
||||||
SelectWord,
|
SelectWord,
|
||||||
|
/// Select the line at the current cursor.
|
||||||
SelectLine,
|
SelectLine,
|
||||||
|
/// Perform an [`Edit`].
|
||||||
Edit(Edit),
|
Edit(Edit),
|
||||||
|
/// Click the [`Editor`] at the given [`Point`].
|
||||||
Click(Point),
|
Click(Point),
|
||||||
|
/// Drag the mouse on the [`Editor`] to the given [`Point`].
|
||||||
Drag(Point),
|
Drag(Point),
|
||||||
Scroll { lines: i32 },
|
/// Scroll the [`Editor`] a certain amount of lines.
|
||||||
|
Scroll {
|
||||||
|
/// The amount of lines to scroll.
|
||||||
|
lines: i32,
|
||||||
|
},
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Action {
|
impl Action {
|
||||||
|
/// Returns whether the [`Action`] is an editing action.
|
||||||
pub fn is_edit(&self) -> bool {
|
pub fn is_edit(&self) -> bool {
|
||||||
matches!(self, Self::Edit(_))
|
matches!(self, Self::Edit(_))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// An action that edits text.
|
||||||
#[derive(Debug, Clone, PartialEq)]
|
#[derive(Debug, Clone, PartialEq)]
|
||||||
pub enum Edit {
|
pub enum Edit {
|
||||||
|
/// Insert the given character.
|
||||||
Insert(char),
|
Insert(char),
|
||||||
|
/// Paste the given text.
|
||||||
Paste(Arc<String>),
|
Paste(Arc<String>),
|
||||||
|
/// Break the current line.
|
||||||
Enter,
|
Enter,
|
||||||
|
/// Delete the previous character.
|
||||||
Backspace,
|
Backspace,
|
||||||
|
/// Delete the next character.
|
||||||
Delete,
|
Delete,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cursor movement.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub enum Motion {
|
pub enum Motion {
|
||||||
|
/// Move left.
|
||||||
Left,
|
Left,
|
||||||
|
/// Move right.
|
||||||
Right,
|
Right,
|
||||||
|
/// Move up.
|
||||||
Up,
|
Up,
|
||||||
|
/// Move down.
|
||||||
Down,
|
Down,
|
||||||
|
/// Move to the left boundary of a word.
|
||||||
WordLeft,
|
WordLeft,
|
||||||
|
/// Move to the right boundary of a word.
|
||||||
WordRight,
|
WordRight,
|
||||||
|
/// Move to the start of the line.
|
||||||
Home,
|
Home,
|
||||||
|
/// Move to the end of the line.
|
||||||
End,
|
End,
|
||||||
|
/// Move to the start of the previous window.
|
||||||
PageUp,
|
PageUp,
|
||||||
|
/// Move to the start of the next window.
|
||||||
PageDown,
|
PageDown,
|
||||||
|
/// Move to the start of the text.
|
||||||
DocumentStart,
|
DocumentStart,
|
||||||
|
/// Move to the end of the text.
|
||||||
DocumentEnd,
|
DocumentEnd,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Motion {
|
impl Motion {
|
||||||
|
/// Widens the [`Motion`], if possible.
|
||||||
pub fn widen(self) -> Self {
|
pub fn widen(self) -> Self {
|
||||||
match self {
|
match self {
|
||||||
Self::Left => Self::WordLeft,
|
Self::Left => Self::WordLeft,
|
||||||
|
|
@ -97,6 +142,7 @@ impl Motion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the [`Direction`] of the [`Motion`].
|
||||||
pub fn direction(&self) -> Direction {
|
pub fn direction(&self) -> Direction {
|
||||||
match self {
|
match self {
|
||||||
Self::Left
|
Self::Left
|
||||||
|
|
@ -115,9 +161,12 @@ impl Motion {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A direction in some text.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq)]
|
||||||
pub enum Direction {
|
pub enum Direction {
|
||||||
|
/// <-
|
||||||
Left,
|
Left,
|
||||||
|
/// ->
|
||||||
Right,
|
Right,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,31 +1,48 @@
|
||||||
|
//! Highlight text.
|
||||||
use crate::Color;
|
use crate::Color;
|
||||||
|
|
||||||
use std::ops::Range;
|
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 {
|
pub trait Highlighter: 'static {
|
||||||
|
/// The settings to configure the [`Highlighter`].
|
||||||
type Settings: PartialEq + Clone;
|
type Settings: PartialEq + Clone;
|
||||||
|
|
||||||
|
/// The output of the [`Highlighter`].
|
||||||
type Highlight;
|
type Highlight;
|
||||||
|
|
||||||
|
/// The highlight iterator type.
|
||||||
type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
|
type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
|
||||||
where
|
where
|
||||||
Self: 'a;
|
Self: 'a;
|
||||||
|
|
||||||
|
/// Creates a new [`Highlighter`] from its [`Self::Settings`].
|
||||||
fn new(settings: &Self::Settings) -> Self;
|
fn new(settings: &Self::Settings) -> Self;
|
||||||
|
|
||||||
|
/// Updates the [`Highlighter`] with some new [`Self::Settings`].
|
||||||
fn update(&mut self, new_settings: &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);
|
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<'_>;
|
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;
|
fn current_line(&self) -> usize;
|
||||||
}
|
}
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
/// A highlighter that highlights nothing.
|
||||||
pub struct Style {
|
|
||||||
pub color: Color,
|
|
||||||
}
|
|
||||||
|
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct PlainText;
|
pub struct PlainText;
|
||||||
|
|
||||||
|
|
@ -52,9 +69,12 @@ impl Highlighter for PlainText {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The format of some text.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq)]
|
#[derive(Debug, Clone, Copy, PartialEq)]
|
||||||
pub struct Format<Font> {
|
pub struct Format<Font> {
|
||||||
|
/// The [`Color`] of the text.
|
||||||
pub color: Option<Color>,
|
pub color: Option<Color>,
|
||||||
|
/// The `Font` of the text.
|
||||||
pub font: Option<Font>,
|
pub font: Option<Font>,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -34,7 +34,7 @@ struct Editor {
|
||||||
|
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
enum Message {
|
enum Message {
|
||||||
Edit(text_editor::Action),
|
ActionPerformed(text_editor::Action),
|
||||||
ThemeSelected(highlighter::Theme),
|
ThemeSelected(highlighter::Theme),
|
||||||
NewFile,
|
NewFile,
|
||||||
OpenFile,
|
OpenFile,
|
||||||
|
|
@ -68,10 +68,10 @@ impl Application for Editor {
|
||||||
|
|
||||||
fn update(&mut self, message: Message) -> Command<Message> {
|
fn update(&mut self, message: Message) -> Command<Message> {
|
||||||
match message {
|
match message {
|
||||||
Message::Edit(action) => {
|
Message::ActionPerformed(action) => {
|
||||||
self.is_dirty = self.is_dirty || action.is_edit();
|
self.is_dirty = self.is_dirty || action.is_edit();
|
||||||
|
|
||||||
self.content.edit(action);
|
self.content.perform(action);
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
}
|
}
|
||||||
|
|
@ -103,7 +103,7 @@ impl Application for Editor {
|
||||||
|
|
||||||
if let Ok((path, contents)) = result {
|
if let Ok((path, contents)) = result {
|
||||||
self.file = Some(path);
|
self.file = Some(path);
|
||||||
self.content = text_editor::Content::with(&contents);
|
self.content = text_editor::Content::with_text(&contents);
|
||||||
}
|
}
|
||||||
|
|
||||||
Command::none()
|
Command::none()
|
||||||
|
|
@ -191,7 +191,7 @@ impl Application for Editor {
|
||||||
column![
|
column![
|
||||||
controls,
|
controls,
|
||||||
text_editor(&self.content)
|
text_editor(&self.content)
|
||||||
.on_edit(Message::Edit)
|
.on_action(Message::ActionPerformed)
|
||||||
.highlight::<Highlighter>(
|
.highlight::<Highlighter>(
|
||||||
highlighter::Settings {
|
highlighter::Settings {
|
||||||
theme: self.theme,
|
theme: self.theme,
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
#![forbid(rust_2018_idioms)]
|
#![forbid(rust_2018_idioms)]
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
//missing_docs,
|
missing_docs,
|
||||||
unsafe_code,
|
unsafe_code,
|
||||||
unused_results,
|
unused_results,
|
||||||
rustdoc::broken_intra_doc_links
|
rustdoc::broken_intra_doc_links
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Draw text.
|
||||||
pub mod cache;
|
pub mod cache;
|
||||||
pub mod editor;
|
pub mod editor;
|
||||||
pub mod paragraph;
|
pub mod paragraph;
|
||||||
|
|
@ -17,6 +18,7 @@ use once_cell::sync::OnceCell;
|
||||||
use std::borrow::Cow;
|
use std::borrow::Cow;
|
||||||
use std::sync::{Arc, RwLock};
|
use std::sync::{Arc, RwLock};
|
||||||
|
|
||||||
|
/// Returns the global [`FontSystem`].
|
||||||
pub fn font_system() -> &'static RwLock<FontSystem> {
|
pub fn font_system() -> &'static RwLock<FontSystem> {
|
||||||
static FONT_SYSTEM: OnceCell<RwLock<FontSystem>> = OnceCell::new();
|
static FONT_SYSTEM: OnceCell<RwLock<FontSystem>> = OnceCell::new();
|
||||||
|
|
||||||
|
|
@ -32,6 +34,7 @@ pub fn font_system() -> &'static RwLock<FontSystem> {
|
||||||
})
|
})
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A set of system fonts.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct FontSystem {
|
pub struct FontSystem {
|
||||||
raw: cosmic_text::FontSystem,
|
raw: cosmic_text::FontSystem,
|
||||||
|
|
@ -39,10 +42,12 @@ pub struct FontSystem {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl FontSystem {
|
impl FontSystem {
|
||||||
|
/// Returns the raw [`cosmic_text::FontSystem`].
|
||||||
pub fn raw(&mut self) -> &mut cosmic_text::FontSystem {
|
pub fn raw(&mut self) -> &mut cosmic_text::FontSystem {
|
||||||
&mut self.raw
|
&mut self.raw
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Loads a font from its bytes.
|
||||||
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
pub fn load_font(&mut self, bytes: Cow<'static, [u8]>) {
|
||||||
let _ = self.raw.db_mut().load_font_source(
|
let _ = self.raw.db_mut().load_font_source(
|
||||||
cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
|
cosmic_text::fontdb::Source::Binary(Arc::new(bytes.into_owned())),
|
||||||
|
|
@ -51,14 +56,19 @@ impl FontSystem {
|
||||||
self.version = Version(self.version.0 + 1);
|
self.version = Version(self.version.0 + 1);
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current [`Version`] of the [`FontSystem`].
|
||||||
|
///
|
||||||
|
/// Loading a font will increase the version of a [`FontSystem`].
|
||||||
pub fn version(&self) -> Version {
|
pub fn version(&self) -> Version {
|
||||||
self.version
|
self.version
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A version number.
|
||||||
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
#[derive(Debug, Clone, Copy, PartialEq, Eq, PartialOrd, Ord, Hash, Default)]
|
||||||
pub struct Version(u32);
|
pub struct Version(u32);
|
||||||
|
|
||||||
|
/// Measures the dimensions of the given [`cosmic_text::Buffer`].
|
||||||
pub fn measure(buffer: &cosmic_text::Buffer) -> Size {
|
pub fn measure(buffer: &cosmic_text::Buffer) -> Size {
|
||||||
let (width, total_lines) = buffer
|
let (width, total_lines) = buffer
|
||||||
.layout_runs()
|
.layout_runs()
|
||||||
|
|
@ -69,6 +79,7 @@ pub fn measure(buffer: &cosmic_text::Buffer) -> Size {
|
||||||
Size::new(width, total_lines as f32 * buffer.metrics().line_height)
|
Size::new(width, total_lines as f32 * buffer.metrics().line_height)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the attributes of the given [`Font`].
|
||||||
pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> {
|
pub fn to_attributes(font: Font) -> cosmic_text::Attrs<'static> {
|
||||||
cosmic_text::Attrs::new()
|
cosmic_text::Attrs::new()
|
||||||
.family(to_family(font.family))
|
.family(to_family(font.family))
|
||||||
|
|
@ -124,6 +135,7 @@ fn to_style(style: font::Style) -> cosmic_text::Style {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts some [`Shaping`] strategy to a [`cosmic_text::Shaping`] strategy.
|
||||||
pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
|
pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
|
||||||
match shaping {
|
match shaping {
|
||||||
Shaping::Basic => cosmic_text::Shaping::Basic,
|
Shaping::Basic => cosmic_text::Shaping::Basic,
|
||||||
|
|
@ -131,6 +143,7 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Converts some [`Color`] to a [`cosmic_text::Color`].
|
||||||
pub fn to_color(color: Color) -> cosmic_text::Color {
|
pub fn to_color(color: Color) -> cosmic_text::Color {
|
||||||
let [r, g, b, a] = color::pack(color).components();
|
let [r, g, b, a] = color::pack(color).components();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Cache text.
|
||||||
use crate::core::{Font, Size};
|
use crate::core::{Font, Size};
|
||||||
use crate::text;
|
use crate::text;
|
||||||
|
|
||||||
|
|
@ -5,6 +6,7 @@ use rustc_hash::{FxHashMap, FxHashSet};
|
||||||
use std::collections::hash_map;
|
use std::collections::hash_map;
|
||||||
use std::hash::{BuildHasher, Hash, Hasher};
|
use std::hash::{BuildHasher, Hash, Hasher};
|
||||||
|
|
||||||
|
/// A store of recently used sections of text.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
#[derive(Default)]
|
#[derive(Default)]
|
||||||
pub struct Cache {
|
pub struct Cache {
|
||||||
|
|
@ -21,14 +23,17 @@ type HashBuilder = twox_hash::RandomXxHashBuilder64;
|
||||||
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
type HashBuilder = std::hash::BuildHasherDefault<twox_hash::XxHash64>;
|
||||||
|
|
||||||
impl Cache {
|
impl Cache {
|
||||||
|
/// Creates a new empty [`Cache`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Gets the text [`Entry`] with the given [`KeyHash`].
|
||||||
pub fn get(&self, key: &KeyHash) -> Option<&Entry> {
|
pub fn get(&self, key: &KeyHash) -> Option<&Entry> {
|
||||||
self.entries.get(key)
|
self.entries.get(key)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Allocates a text [`Entry`] if it is not already present in the [`Cache`].
|
||||||
pub fn allocate(
|
pub fn allocate(
|
||||||
&mut self,
|
&mut self,
|
||||||
font_system: &mut cosmic_text::FontSystem,
|
font_system: &mut cosmic_text::FontSystem,
|
||||||
|
|
@ -88,6 +93,9 @@ impl Cache {
|
||||||
(hash, self.entries.get_mut(&hash).unwrap())
|
(hash, self.entries.get_mut(&hash).unwrap())
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Trims the [`Cache`].
|
||||||
|
///
|
||||||
|
/// This will clear the sections of text that have not been used since the last `trim`.
|
||||||
pub fn trim(&mut self) {
|
pub fn trim(&mut self) {
|
||||||
self.entries
|
self.entries
|
||||||
.retain(|key, _| self.recently_used.contains(key));
|
.retain(|key, _| self.recently_used.contains(key));
|
||||||
|
|
@ -99,13 +107,20 @@ impl Cache {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A cache key representing a section of text.
|
||||||
#[derive(Debug, Clone, Copy)]
|
#[derive(Debug, Clone, Copy)]
|
||||||
pub struct Key<'a> {
|
pub struct Key<'a> {
|
||||||
|
/// The content of the text.
|
||||||
pub content: &'a str,
|
pub content: &'a str,
|
||||||
|
/// The size of the text.
|
||||||
pub size: f32,
|
pub size: f32,
|
||||||
|
/// The line height of the text.
|
||||||
pub line_height: f32,
|
pub line_height: f32,
|
||||||
|
/// The [`Font`] of the text.
|
||||||
pub font: Font,
|
pub font: Font,
|
||||||
|
/// The bounds of the text.
|
||||||
pub bounds: Size,
|
pub bounds: Size,
|
||||||
|
/// The shaping strategy of the text.
|
||||||
pub shaping: text::Shaping,
|
pub shaping: text::Shaping,
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
@ -123,10 +138,14 @@ impl Key<'_> {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The hash of a [`Key`].
|
||||||
pub type KeyHash = u64;
|
pub type KeyHash = u64;
|
||||||
|
|
||||||
|
/// A cache entry.
|
||||||
#[allow(missing_debug_implementations)]
|
#[allow(missing_debug_implementations)]
|
||||||
pub struct Entry {
|
pub struct Entry {
|
||||||
|
/// The buffer of text, ready for drawing.
|
||||||
pub buffer: cosmic_text::Buffer,
|
pub buffer: cosmic_text::Buffer,
|
||||||
|
/// The minimum bounds of the text.
|
||||||
pub min_bounds: Size,
|
pub min_bounds: Size,
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Draw and edit text.
|
||||||
use crate::core::text::editor::{
|
use crate::core::text::editor::{
|
||||||
self, Action, Cursor, Direction, Edit, Motion,
|
self, Action, Cursor, Direction, Edit, Motion,
|
||||||
};
|
};
|
||||||
|
|
@ -11,6 +12,7 @@ use cosmic_text::Edit as _;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::{self, Arc};
|
use std::sync::{self, Arc};
|
||||||
|
|
||||||
|
/// A multi-line text editor.
|
||||||
#[derive(Debug, PartialEq)]
|
#[derive(Debug, PartialEq)]
|
||||||
pub struct Editor(Option<Arc<Internal>>);
|
pub struct Editor(Option<Arc<Internal>>);
|
||||||
|
|
||||||
|
|
@ -23,14 +25,21 @@ struct Internal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Editor {
|
impl Editor {
|
||||||
|
/// Creates a new empty [`Editor`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the buffer of the [`Editor`].
|
||||||
pub fn buffer(&self) -> &cosmic_text::Buffer {
|
pub fn buffer(&self) -> &cosmic_text::Buffer {
|
||||||
self.internal().editor.buffer()
|
self.internal().editor.buffer()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Weak`] reference to the [`Editor`].
|
||||||
|
///
|
||||||
|
/// This is useful to avoid cloning the [`Editor`] when
|
||||||
|
/// referential guarantees are unnecessary. For instance,
|
||||||
|
/// when creating a rendering tree.
|
||||||
pub fn downgrade(&self) -> Weak {
|
pub fn downgrade(&self) -> Weak {
|
||||||
let editor = self.internal();
|
let editor = self.internal();
|
||||||
|
|
||||||
|
|
@ -662,13 +671,16 @@ impl fmt::Debug for Internal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A weak reference to an [`Editor`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Weak {
|
pub struct Weak {
|
||||||
raw: sync::Weak<Internal>,
|
raw: sync::Weak<Internal>,
|
||||||
|
/// The bounds of the [`Editor`].
|
||||||
pub bounds: Size,
|
pub bounds: Size,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Weak {
|
impl Weak {
|
||||||
|
/// Tries to update the reference into an [`Editor`].
|
||||||
pub fn upgrade(&self) -> Option<Editor> {
|
pub fn upgrade(&self) -> Option<Editor> {
|
||||||
self.raw.upgrade().map(Some).map(Editor)
|
self.raw.upgrade().map(Some).map(Editor)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Draw paragraphs.
|
||||||
use crate::core;
|
use crate::core;
|
||||||
use crate::core::alignment;
|
use crate::core::alignment;
|
||||||
use crate::core::text::{Hit, LineHeight, Shaping, Text};
|
use crate::core::text::{Hit, LineHeight, Shaping, Text};
|
||||||
|
|
@ -7,6 +8,7 @@ use crate::text;
|
||||||
use std::fmt;
|
use std::fmt;
|
||||||
use std::sync::{self, Arc};
|
use std::sync::{self, Arc};
|
||||||
|
|
||||||
|
/// A bunch of text.
|
||||||
#[derive(Clone, PartialEq)]
|
#[derive(Clone, PartialEq)]
|
||||||
pub struct Paragraph(Option<Arc<Internal>>);
|
pub struct Paragraph(Option<Arc<Internal>>);
|
||||||
|
|
||||||
|
|
@ -23,14 +25,21 @@ struct Internal {
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Paragraph {
|
impl Paragraph {
|
||||||
|
/// Creates a new empty [`Paragraph`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::default()
|
Self::default()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the buffer of the [`Paragraph`].
|
||||||
pub fn buffer(&self) -> &cosmic_text::Buffer {
|
pub fn buffer(&self) -> &cosmic_text::Buffer {
|
||||||
&self.internal().buffer
|
&self.internal().buffer
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Creates a [`Weak`] reference to the [`Paragraph`].
|
||||||
|
///
|
||||||
|
/// This is useful to avoid cloning the [`Editor`] when
|
||||||
|
/// referential guarantees are unnecessary. For instance,
|
||||||
|
/// when creating a rendering tree.
|
||||||
pub fn downgrade(&self) -> Weak {
|
pub fn downgrade(&self) -> Weak {
|
||||||
let paragraph = self.internal();
|
let paragraph = self.internal();
|
||||||
|
|
||||||
|
|
@ -269,15 +278,20 @@ impl Default for Internal {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// A weak reference to a [`Paragraph`].
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub struct Weak {
|
pub struct Weak {
|
||||||
raw: sync::Weak<Internal>,
|
raw: sync::Weak<Internal>,
|
||||||
|
/// The minimum bounds of the [`Paragraph`].
|
||||||
pub min_bounds: Size,
|
pub min_bounds: Size,
|
||||||
|
/// The horizontal alignment of the [`Paragraph`].
|
||||||
pub horizontal_alignment: alignment::Horizontal,
|
pub horizontal_alignment: alignment::Horizontal,
|
||||||
|
/// The vertical alignment of the [`Paragraph`].
|
||||||
pub vertical_alignment: alignment::Vertical,
|
pub vertical_alignment: alignment::Vertical,
|
||||||
}
|
}
|
||||||
|
|
||||||
impl Weak {
|
impl Weak {
|
||||||
|
/// Tries to update the reference into a [`Paragraph`].
|
||||||
pub fn upgrade(&self) -> Option<Paragraph> {
|
pub fn upgrade(&self) -> Option<Paragraph> {
|
||||||
self.raw.upgrade().map(Some).map(Paragraph)
|
self.raw.upgrade().map(Some).map(Paragraph)
|
||||||
}
|
}
|
||||||
|
|
|
||||||
|
|
@ -10,7 +10,7 @@
|
||||||
#![forbid(unsafe_code, rust_2018_idioms)]
|
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||||
#![deny(
|
#![deny(
|
||||||
unused_results,
|
unused_results,
|
||||||
// missing_docs,
|
missing_docs,
|
||||||
unused_results,
|
unused_results,
|
||||||
rustdoc::broken_intra_doc_links
|
rustdoc::broken_intra_doc_links
|
||||||
)]
|
)]
|
||||||
|
|
|
||||||
|
|
@ -4,19 +4,24 @@ use crate::core::{Color, Font, Pixels, Point, Rectangle};
|
||||||
use crate::graphics::text::editor;
|
use crate::graphics::text::editor;
|
||||||
use crate::graphics::text::paragraph;
|
use crate::graphics::text::paragraph;
|
||||||
|
|
||||||
/// A paragraph of text.
|
/// A text primitive.
|
||||||
#[derive(Debug, Clone)]
|
#[derive(Debug, Clone)]
|
||||||
pub enum Text<'a> {
|
pub enum Text<'a> {
|
||||||
|
/// A paragraph.
|
||||||
|
#[allow(missing_docs)]
|
||||||
Paragraph {
|
Paragraph {
|
||||||
paragraph: paragraph::Weak,
|
paragraph: paragraph::Weak,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
},
|
},
|
||||||
|
/// An editor.
|
||||||
|
#[allow(missing_docs)]
|
||||||
Editor {
|
Editor {
|
||||||
editor: editor::Weak,
|
editor: editor::Weak,
|
||||||
position: Point,
|
position: Point,
|
||||||
color: Color,
|
color: Color,
|
||||||
},
|
},
|
||||||
|
/// A cached text.
|
||||||
Cached(Cached<'a>),
|
Cached(Cached<'a>),
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -23,7 +23,7 @@
|
||||||
#![forbid(rust_2018_idioms)]
|
#![forbid(rust_2018_idioms)]
|
||||||
#![deny(
|
#![deny(
|
||||||
missing_debug_implementations,
|
missing_debug_implementations,
|
||||||
//missing_docs,
|
missing_docs,
|
||||||
unsafe_code,
|
unsafe_code,
|
||||||
unused_results,
|
unused_results,
|
||||||
rustdoc::broken_intra_doc_links
|
rustdoc::broken_intra_doc_links
|
||||||
|
|
|
||||||
|
|
@ -4,8 +4,8 @@
|
||||||
)]
|
)]
|
||||||
#![forbid(unsafe_code, rust_2018_idioms)]
|
#![forbid(unsafe_code, rust_2018_idioms)]
|
||||||
#![deny(
|
#![deny(
|
||||||
// missing_debug_implementations,
|
//missing_debug_implementations,
|
||||||
// missing_docs,
|
missing_docs,
|
||||||
unused_results,
|
unused_results,
|
||||||
rustdoc::broken_intra_doc_links
|
rustdoc::broken_intra_doc_links
|
||||||
)]
|
)]
|
||||||
|
|
|
||||||
|
|
@ -1,3 +1,4 @@
|
||||||
|
//! Display a multi-line text input for text editing.
|
||||||
use crate::core::event::{self, Event};
|
use crate::core::event::{self, Event};
|
||||||
use crate::core::keyboard;
|
use crate::core::keyboard;
|
||||||
use crate::core::layout::{self, Layout};
|
use crate::core::layout::{self, Layout};
|
||||||
|
|
@ -19,6 +20,7 @@ use std::sync::Arc;
|
||||||
pub use crate::style::text_editor::{Appearance, StyleSheet};
|
pub use crate::style::text_editor::{Appearance, StyleSheet};
|
||||||
pub use text::editor::{Action, Edit, Motion};
|
pub use text::editor::{Action, Edit, Motion};
|
||||||
|
|
||||||
|
/// A multi-line text input.
|
||||||
pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer>
|
pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer>
|
||||||
where
|
where
|
||||||
Highlighter: text::Highlighter,
|
Highlighter: text::Highlighter,
|
||||||
|
|
@ -47,6 +49,7 @@ where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
{
|
{
|
||||||
|
/// Creates new [`TextEditor`] with the given [`Content`].
|
||||||
pub fn new(content: &'a Content<Renderer>) -> Self {
|
pub fn new(content: &'a Content<Renderer>) -> Self {
|
||||||
Self {
|
Self {
|
||||||
content,
|
content,
|
||||||
|
|
@ -73,21 +76,34 @@ where
|
||||||
Renderer: text::Renderer,
|
Renderer: text::Renderer,
|
||||||
Renderer::Theme: StyleSheet,
|
Renderer::Theme: StyleSheet,
|
||||||
{
|
{
|
||||||
pub fn on_edit(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self {
|
/// Sets the message that should be produced when some action is performed in
|
||||||
|
/// the [`TextEditor`].
|
||||||
|
///
|
||||||
|
/// If this method is not called, the [`TextEditor`] will be disabled.
|
||||||
|
pub fn on_action(
|
||||||
|
mut self,
|
||||||
|
on_edit: impl Fn(Action) -> Message + 'a,
|
||||||
|
) -> Self {
|
||||||
self.on_edit = Some(Box::new(on_edit));
|
self.on_edit = Some(Box::new(on_edit));
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the [`Font`] of the [`TextEditor`].
|
||||||
|
///
|
||||||
|
/// [`Font`]: text::Renderer::Font
|
||||||
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
pub fn font(mut self, font: impl Into<Renderer::Font>) -> Self {
|
||||||
self.font = Some(font.into());
|
self.font = Some(font.into());
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Sets the [`Padding`] of the [`TextEditor`].
|
||||||
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
pub fn padding(mut self, padding: impl Into<Padding>) -> Self {
|
||||||
self.padding = padding.into();
|
self.padding = padding.into();
|
||||||
self
|
self
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Highlights the [`TextEditor`] with the given [`Highlighter`] and
|
||||||
|
/// a strategy to turn its highlights into some text format.
|
||||||
pub fn highlight<H: text::Highlighter>(
|
pub fn highlight<H: text::Highlighter>(
|
||||||
self,
|
self,
|
||||||
settings: H::Settings,
|
settings: H::Settings,
|
||||||
|
|
@ -112,6 +128,7 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// The content of a [`TextEditor`].
|
||||||
pub struct Content<R = crate::Renderer>(RefCell<Internal<R>>)
|
pub struct Content<R = crate::Renderer>(RefCell<Internal<R>>)
|
||||||
where
|
where
|
||||||
R: text::Renderer;
|
R: text::Renderer;
|
||||||
|
|
@ -128,28 +145,33 @@ impl<R> Content<R>
|
||||||
where
|
where
|
||||||
R: text::Renderer,
|
R: text::Renderer,
|
||||||
{
|
{
|
||||||
|
/// Creates an empty [`Content`].
|
||||||
pub fn new() -> Self {
|
pub fn new() -> Self {
|
||||||
Self::with("")
|
Self::with_text("")
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn with(text: &str) -> Self {
|
/// Creates a [`Content`] with the given text.
|
||||||
|
pub fn with_text(text: &str) -> Self {
|
||||||
Self(RefCell::new(Internal {
|
Self(RefCell::new(Internal {
|
||||||
editor: R::Editor::with_text(text),
|
editor: R::Editor::with_text(text),
|
||||||
is_dirty: true,
|
is_dirty: true,
|
||||||
}))
|
}))
|
||||||
}
|
}
|
||||||
|
|
||||||
pub fn edit(&mut self, action: Action) {
|
/// Performs an [`Action`] on the [`Content`].
|
||||||
|
pub fn perform(&mut self, action: Action) {
|
||||||
let internal = self.0.get_mut();
|
let internal = self.0.get_mut();
|
||||||
|
|
||||||
internal.editor.perform(action);
|
internal.editor.perform(action);
|
||||||
internal.is_dirty = true;
|
internal.is_dirty = true;
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the amount of lines of the [`Content`].
|
||||||
pub fn line_count(&self) -> usize {
|
pub fn line_count(&self) -> usize {
|
||||||
self.0.borrow().editor.line_count()
|
self.0.borrow().editor.line_count()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the text of the line at the given index, if it exists.
|
||||||
pub fn line(
|
pub fn line(
|
||||||
&self,
|
&self,
|
||||||
index: usize,
|
index: usize,
|
||||||
|
|
@ -160,6 +182,7 @@ where
|
||||||
.ok()
|
.ok()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns an iterator of the text of the lines in the [`Content`].
|
||||||
pub fn lines(
|
pub fn lines(
|
||||||
&self,
|
&self,
|
||||||
) -> impl Iterator<Item = impl std::ops::Deref<Target = str> + '_> {
|
) -> impl Iterator<Item = impl std::ops::Deref<Target = str> + '_> {
|
||||||
|
|
@ -190,6 +213,9 @@ where
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the text of the [`Content`].
|
||||||
|
///
|
||||||
|
/// Lines are joined with `'\n'`.
|
||||||
pub fn text(&self) -> String {
|
pub fn text(&self) -> String {
|
||||||
let mut text = self.lines().enumerate().fold(
|
let mut text = self.lines().enumerate().fold(
|
||||||
String::new(),
|
String::new(),
|
||||||
|
|
@ -211,10 +237,12 @@ where
|
||||||
text
|
text
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the selected text of the [`Content`].
|
||||||
pub fn selection(&self) -> Option<String> {
|
pub fn selection(&self) -> Option<String> {
|
||||||
self.0.borrow().editor.selection()
|
self.0.borrow().editor.selection()
|
||||||
}
|
}
|
||||||
|
|
||||||
|
/// Returns the current cursor position of the [`Content`].
|
||||||
pub fn cursor_position(&self) -> (usize, usize) {
|
pub fn cursor_position(&self) -> (usize, usize) {
|
||||||
self.0.borrow().editor.cursor_position()
|
self.0.borrow().editor.cursor_position()
|
||||||
}
|
}
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue