summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
-rw-r--r--core/src/renderer/null.rs11
-rw-r--r--core/src/text.rs2
-rw-r--r--core/src/text/editor.rs9
-rw-r--r--core/src/text/highlighter.rs56
-rw-r--r--graphics/src/text.rs8
-rw-r--r--graphics/src/text/editor.rs67
-rw-r--r--style/src/lib.rs2
-rw-r--r--style/src/text_editor.rs16
-rw-r--r--widget/src/helpers.rs2
-rw-r--r--widget/src/text_editor.rs64
10 files changed, 218 insertions, 19 deletions
diff --git a/core/src/renderer/null.rs b/core/src/renderer/null.rs
index 01a52c7a..21597c8e 100644
--- a/core/src/renderer/null.rs
+++ b/core/src/renderer/null.rs
@@ -149,6 +149,17 @@ impl text::Editor for () {
_new_font: Self::Font,
_new_size: Pixels,
_new_line_height: text::LineHeight,
+ _new_highlighter: &mut impl text::Highlighter,
+ ) {
+ }
+
+ fn highlight<H: text::Highlighter>(
+ &mut self,
+ _font: Self::Font,
+ _highlighter: &mut H,
+ _format_highlight: impl Fn(
+ &H::Highlight,
+ ) -> text::highlighter::Format<Self::Font>,
) {
}
}
diff --git a/core/src/text.rs b/core/src/text.rs
index 90581fea..9b9c753c 100644
--- a/core/src/text.rs
+++ b/core/src/text.rs
@@ -2,8 +2,10 @@
mod paragraph;
pub mod editor;
+pub mod highlighter;
pub use editor::Editor;
+pub use highlighter::Highlighter;
pub use paragraph::Paragraph;
use crate::alignment;
diff --git a/core/src/text/editor.rs b/core/src/text/editor.rs
index 003557c1..0f439c8d 100644
--- a/core/src/text/editor.rs
+++ b/core/src/text/editor.rs
@@ -1,3 +1,4 @@
+use crate::text::highlighter::{self, Highlighter};
use crate::text::LineHeight;
use crate::{Pixels, Point, Rectangle, Size};
@@ -29,6 +30,14 @@ pub trait Editor: Sized + Default {
new_font: Self::Font,
new_size: Pixels,
new_line_height: LineHeight,
+ new_highlighter: &mut impl Highlighter,
+ );
+
+ fn highlight<H: Highlighter>(
+ &mut self,
+ font: Self::Font,
+ highlighter: &mut H,
+ format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
);
}
diff --git a/core/src/text/highlighter.rs b/core/src/text/highlighter.rs
new file mode 100644
index 00000000..1f9ac840
--- /dev/null
+++ b/core/src/text/highlighter.rs
@@ -0,0 +1,56 @@
+use crate::Color;
+
+use std::hash::Hash;
+use std::ops::Range;
+
+pub trait Highlighter: Clone + 'static {
+ type Settings: Hash;
+ type Highlight;
+
+ type Iterator<'a>: Iterator<Item = (Range<usize>, Self::Highlight)>
+ where
+ Self: 'a;
+
+ fn new(settings: &Self::Settings) -> Self;
+
+ fn change_line(&mut self, line: usize);
+
+ fn highlight_line(&mut self, line: &str) -> Self::Iterator<'_>;
+
+ fn current_line(&self) -> usize;
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Style {
+ pub color: Color,
+}
+
+#[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 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
+ }
+}
+
+#[derive(Debug, Clone, Copy)]
+pub struct Format<Font> {
+ pub color: Option<Color>,
+ pub font: Option<Font>,
+}
diff --git a/graphics/src/text.rs b/graphics/src/text.rs
index b4aeb2be..5fcfc699 100644
--- a/graphics/src/text.rs
+++ b/graphics/src/text.rs
@@ -10,7 +10,7 @@ pub use cosmic_text;
use crate::core::font::{self, Font};
use crate::core::text::Shaping;
-use crate::core::Size;
+use crate::core::{Color, Size};
use once_cell::sync::OnceCell;
use std::borrow::Cow;
@@ -129,3 +129,9 @@ pub fn to_shaping(shaping: Shaping) -> cosmic_text::Shaping {
Shaping::Advanced => cosmic_text::Shaping::Advanced,
}
}
+
+pub fn to_color(color: Color) -> cosmic_text::Color {
+ let [r, g, b, a] = color.into_rgba8();
+
+ cosmic_text::Color::rgba(r, g, b, a)
+}
diff --git a/graphics/src/text/editor.rs b/graphics/src/text/editor.rs
index a828a3bc..901b4295 100644
--- a/graphics/src/text/editor.rs
+++ b/graphics/src/text/editor.rs
@@ -1,4 +1,5 @@
use crate::core::text::editor::{self, Action, Cursor, Direction, Motion};
+use crate::core::text::highlighter::{self, Highlighter};
use crate::core::text::LineHeight;
use crate::core::{Font, Pixels, Point, Rectangle, Size};
use crate::text;
@@ -15,6 +16,7 @@ struct Internal {
editor: cosmic_text::Editor,
font: Font,
bounds: Size,
+ topmost_line_changed: Option<usize>,
version: text::Version,
}
@@ -433,6 +435,7 @@ impl editor::Editor for Editor {
new_font: Font,
new_size: Pixels,
new_line_height: LineHeight,
+ new_highlighter: &mut impl Highlighter,
) {
let editor =
self.0.take().expect("editor should always be initialized");
@@ -479,6 +482,69 @@ impl editor::Editor for Editor {
internal.bounds = new_bounds;
}
+ if let Some(topmost_line_changed) = internal.topmost_line_changed.take()
+ {
+ new_highlighter.change_line(topmost_line_changed);
+ }
+
+ self.0 = Some(Arc::new(internal));
+ }
+
+ fn highlight<H: Highlighter>(
+ &mut self,
+ font: Self::Font,
+ highlighter: &mut H,
+ format_highlight: impl Fn(&H::Highlight) -> highlighter::Format<Self::Font>,
+ ) {
+ let internal = self.internal();
+
+ let scroll = internal.editor.buffer().scroll();
+ let visible_lines = internal.editor.buffer().visible_lines();
+ let last_visible_line = (scroll + visible_lines - 1) as usize;
+
+ let current_line = highlighter.current_line();
+
+ if current_line > last_visible_line {
+ return;
+ }
+
+ 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 attributes = text::to_attributes(font);
+
+ for line in &mut internal.editor.buffer_mut().lines
+ [current_line..=last_visible_line]
+ {
+ let mut list = cosmic_text::AttrsList::new(attributes);
+
+ for (range, highlight) in highlighter.highlight_line(line.text()) {
+ let format = format_highlight(&highlight);
+
+ list.add_span(
+ range,
+ cosmic_text::Attrs {
+ color_opt: format.color.map(text::to_color),
+ ..if let Some(font) = format.font {
+ text::to_attributes(font)
+ } else {
+ attributes
+ }
+ },
+ );
+ }
+
+ let _ = line.set_attrs_list(list);
+ }
+
+ internal.editor.shape_as_needed(font_system.raw());
+
self.0 = Some(Arc::new(internal));
}
}
@@ -508,6 +574,7 @@ impl Default for Internal {
)),
font: Font::default(),
bounds: Size::ZERO,
+ topmost_line_changed: None,
version: text::Version::default(),
}
}
diff --git a/style/src/lib.rs b/style/src/lib.rs
index 7a97ac77..c9879f24 100644
--- a/style/src/lib.rs
+++ b/style/src/lib.rs
@@ -15,7 +15,7 @@
clippy::needless_borrow,
clippy::new_without_default,
clippy::useless_conversion,
- missing_docs,
+ // missing_docs,
unused_results,
rustdoc::broken_intra_doc_links
)]
diff --git a/style/src/text_editor.rs b/style/src/text_editor.rs
index 45c9bad8..f1c31287 100644
--- a/style/src/text_editor.rs
+++ b/style/src/text_editor.rs
@@ -1,5 +1,6 @@
//! Change the appearance of a text editor.
-use iced_core::{Background, BorderRadius, Color};
+use crate::core::text::highlighter;
+use crate::core::{self, Background, BorderRadius, Color};
/// The appearance of a text input.
#[derive(Debug, Clone, Copy)]
@@ -45,3 +46,16 @@ pub trait StyleSheet {
/// Produces the style of a disabled text input.
fn disabled(&self, style: &Self::Style) -> Appearance;
}
+
+pub trait Highlight<Font = core::Font, Theme = crate::Theme> {
+ fn format(&self, theme: &Theme) -> highlighter::Format<Font>;
+}
+
+impl<Font, Theme> Highlight<Font, Theme> for () {
+ fn format(&self, _theme: &Theme) -> highlighter::Format<Font> {
+ highlighter::Format {
+ color: None,
+ font: None,
+ }
+ }
+}
diff --git a/widget/src/helpers.rs b/widget/src/helpers.rs
index e3f31513..e0b58722 100644
--- a/widget/src/helpers.rs
+++ b/widget/src/helpers.rs
@@ -212,7 +212,7 @@ where
/// [`TextEditor`]: crate::TextEditor
pub fn text_editor<Message, Renderer>(
content: &text_editor::Content<Renderer>,
-) -> TextEditor<'_, Message, Renderer>
+) -> TextEditor<'_, core::text::highlighter::PlainText, Message, Renderer>
where
Message: Clone,
Renderer: core::text::Renderer,
diff --git a/widget/src/text_editor.rs b/widget/src/text_editor.rs
index 68e3c656..b17e1156 100644
--- a/widget/src/text_editor.rs
+++ b/widget/src/text_editor.rs
@@ -4,6 +4,7 @@ use crate::core::layout::{self, Layout};
use crate::core::mouse;
use crate::core::renderer;
use crate::core::text::editor::{Cursor, Editor as _};
+use crate::core::text::highlighter::{self, Highlighter};
use crate::core::text::{self, LineHeight};
use crate::core::widget::{self, Widget};
use crate::core::{
@@ -12,13 +13,15 @@ use crate::core::{
};
use std::cell::RefCell;
+use std::ops::DerefMut;
use std::sync::Arc;
-pub use crate::style::text_editor::{Appearance, StyleSheet};
+pub use crate::style::text_editor::{Appearance, Highlight, StyleSheet};
pub use text::editor::{Action, Motion};
-pub struct TextEditor<'a, Message, Renderer = crate::Renderer>
+pub struct TextEditor<'a, Highlighter, Message, Renderer = crate::Renderer>
where
+ Highlighter: text::Highlighter,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
@@ -31,9 +34,11 @@ where
padding: Padding,
style: <Renderer::Theme as StyleSheet>::Style,
on_edit: Option<Box<dyn Fn(Action) -> Message + 'a>>,
+ highlighter_settings: Highlighter::Settings,
}
-impl<'a, Message, Renderer> TextEditor<'a, Message, Renderer>
+impl<'a, Message, Renderer>
+ TextEditor<'a, highlighter::PlainText, Message, Renderer>
where
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
@@ -49,9 +54,19 @@ where
padding: Padding::new(5.0),
style: Default::default(),
on_edit: None,
+ highlighter_settings: (),
}
}
+}
+impl<'a, Highlighter, Message, Renderer>
+ TextEditor<'a, Highlighter, Message, Renderer>
+where
+ Highlighter: text::Highlighter,
+ Highlighter::Highlight: Highlight<Renderer::Font, Renderer::Theme>,
+ Renderer: text::Renderer,
+ Renderer::Theme: StyleSheet,
+{
pub fn on_edit(mut self, on_edit: impl Fn(Action) -> Message + 'a) -> Self {
self.on_edit = Some(Box::new(on_edit));
self
@@ -160,20 +175,23 @@ where
}
}
-struct State {
+struct State<Highlighter> {
is_focused: bool,
last_click: Option<mouse::Click>,
drag_click: Option<mouse::click::Kind>,
+ highlighter: RefCell<Highlighter>,
}
-impl<'a, Message, Renderer> Widget<Message, Renderer>
- for TextEditor<'a, Message, Renderer>
+impl<'a, Highlighter, Message, Renderer> Widget<Message, Renderer>
+ for TextEditor<'a, Highlighter, Message, Renderer>
where
+ Highlighter: text::Highlighter,
+ Highlighter::Highlight: Highlight<Renderer::Font, Renderer::Theme>,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
fn tag(&self) -> widget::tree::Tag {
- widget::tree::Tag::of::<State>()
+ widget::tree::Tag::of::<State<Highlighter>>()
}
fn state(&self) -> widget::tree::State {
@@ -181,6 +199,9 @@ where
is_focused: false,
last_click: None,
drag_click: None,
+ highlighter: RefCell::new(Highlighter::new(
+ &self.highlighter_settings,
+ )),
})
}
@@ -194,17 +215,19 @@ where
fn layout(
&self,
- _tree: &mut widget::Tree,
+ tree: &mut widget::Tree,
renderer: &Renderer,
limits: &layout::Limits,
) -> iced_renderer::core::layout::Node {
let mut internal = self.content.0.borrow_mut();
+ let state = tree.state.downcast_mut::<State<Highlighter>>();
internal.editor.update(
limits.pad(self.padding).max(),
self.font.unwrap_or_else(|| renderer.default_font()),
self.text_size.unwrap_or_else(|| renderer.default_size()),
self.line_height,
+ state.highlighter.borrow_mut().deref_mut(),
);
layout::Node::new(limits.max())
@@ -225,7 +248,7 @@ where
return event::Status::Ignored;
};
- let state = tree.state.downcast_mut::<State>();
+ let state = tree.state.downcast_mut::<State<Highlighter>>();
let Some(update) = Update::from_event(
event,
@@ -290,8 +313,14 @@ where
) {
let bounds = layout.bounds();
- let internal = self.content.0.borrow();
- let state = tree.state.downcast_ref::<State>();
+ let mut internal = self.content.0.borrow_mut();
+ let state = tree.state.downcast_ref::<State<Highlighter>>();
+
+ internal.editor.highlight(
+ self.font.unwrap_or_else(|| renderer.default_font()),
+ state.highlighter.borrow_mut().deref_mut(),
+ |highlight| highlight.format(theme),
+ );
let is_disabled = self.on_edit.is_none();
let is_mouse_over = cursor.is_over(bounds);
@@ -389,14 +418,19 @@ where
}
}
-impl<'a, Message, Renderer> From<TextEditor<'a, Message, Renderer>>
+impl<'a, Highlighter, Message, Renderer>
+ From<TextEditor<'a, Highlighter, Message, Renderer>>
for Element<'a, Message, Renderer>
where
+ Highlighter: text::Highlighter,
+ Highlighter::Highlight: Highlight<Renderer::Font, Renderer::Theme>,
Message: 'a,
Renderer: text::Renderer,
Renderer::Theme: StyleSheet,
{
- fn from(text_editor: TextEditor<'a, Message, Renderer>) -> Self {
+ fn from(
+ text_editor: TextEditor<'a, Highlighter, Message, Renderer>,
+ ) -> Self {
Self::new(text_editor)
}
}
@@ -411,9 +445,9 @@ enum Update {
}
impl Update {
- fn from_event(
+ fn from_event<H: Highlighter>(
event: Event,
- state: &State,
+ state: &State<H>,
bounds: Rectangle,
padding: Padding,
cursor: mouse::Cursor,