Move color and x_opt out of Cursor

This commit is contained in:
Jeremy Soller 2023-12-15 15:03:29 -07:00
parent 04d53ac5f4
commit ae030e9885
12 changed files with 645 additions and 546 deletions

View file

@ -7,7 +7,7 @@ license = "MIT OR Apache-2.0"
publish = false
[dependencies]
cosmic-text = { path = "../../", features = ["syntect"] }
cosmic-text = { path = "../../", features = ["syntect", "vi"] }
env_logger = "0.10"
fontdb = "0.13"
lazy_static = "1.4"
@ -26,4 +26,4 @@ version = "0.11"
[features]
default = []
vi = ["cosmic-text/vi"]
vi = []

View file

@ -86,9 +86,6 @@ pub struct Window {
path_opt: Option<PathBuf>,
attrs: Attrs<'static>,
font_size: FontSize,
#[cfg(not(feature = "vi"))]
editor: Mutex<SyntaxEditor<'static>>,
#[cfg(feature = "vi")]
editor: Mutex<cosmic_text::ViEditor<'static>>,
}
@ -133,7 +130,7 @@ impl Application for Window {
fn new(_flags: ()) -> (Self, Command<Self::Message>) {
let attrs = cosmic_text::Attrs::new().family(cosmic_text::Family::Monospace);
let mut editor = SyntaxEditor::new(
let editor = SyntaxEditor::new(
Buffer::new(
&mut FONT_SYSTEM.lock().unwrap(),
FontSize::Body.to_metrics(),
@ -143,8 +140,8 @@ impl Application for Window {
)
.unwrap();
#[cfg(feature = "vi")]
let mut editor = cosmic_text::ViEditor::new(editor);
editor.set_passthrough(cfg!(feature = "vi"));
update_attrs(&mut editor, attrs);

View file

@ -5,14 +5,13 @@ use cosmic::{
iced_runtime::keyboard::KeyCode,
theme::{Theme, ThemeType},
};
use cosmic_text::{Action, Edit, Motion, SwashCache};
use cosmic_text::{Action, Edit, Motion, SwashCache, ViEditor};
use std::{cmp, sync::Mutex, time::Instant};
use crate::FONT_SYSTEM;
pub struct Appearance {
background_color: Option<Color>,
text_color: Color,
}
pub trait StyleSheet {
@ -24,23 +23,21 @@ impl StyleSheet for Theme {
match self.theme_type {
ThemeType::Dark | ThemeType::HighContrastDark | ThemeType::Custom(_) => Appearance {
background_color: Some(Color::from_rgb8(0x34, 0x34, 0x34)),
text_color: Color::from_rgb8(0xFF, 0xFF, 0xFF),
},
ThemeType::Light | ThemeType::HighContrastLight => Appearance {
background_color: Some(Color::from_rgb8(0xFC, 0xFC, 0xFC)),
text_color: Color::from_rgb8(0x00, 0x00, 0x00),
},
}
}
}
pub struct TextBox<'a, Editor> {
editor: &'a Mutex<Editor>,
pub struct TextBox<'a, 'editor> {
editor: &'a Mutex<ViEditor<'editor>>,
padding: Padding,
}
impl<'a, Editor> TextBox<'a, Editor> {
pub fn new(editor: &'a Mutex<Editor>) -> Self {
impl<'a, 'editor> TextBox<'a, 'editor> {
pub fn new(editor: &'a Mutex<ViEditor<'editor>>) -> Self {
Self {
editor,
padding: Padding::new(0.),
@ -53,7 +50,7 @@ impl<'a, Editor> TextBox<'a, Editor> {
}
}
pub fn text_box<'a, Editor>(editor: &'a Mutex<Editor>) -> TextBox<'a, Editor> {
pub fn text_box<'a, 'editor>(editor: &'a Mutex<ViEditor<'editor>>) -> TextBox<'a, 'editor> {
TextBox::new(editor)
}
@ -106,11 +103,10 @@ fn draw_pixel(
buffer[offset + 3] = (current >> 24) as u8;
}
impl<'a, 'editor, Editor, Message, Renderer> Widget<Message, Renderer> for TextBox<'a, Editor>
impl<'a, 'editor, Message, Renderer> Widget<Message, Renderer> for TextBox<'a, 'editor>
where
Renderer: cosmic::iced_core::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet,
Editor: Edit,
{
fn tag(&self) -> tree::Tag {
tree::Tag::of::<State>()
@ -193,13 +189,6 @@ where
);
}
let text_color = cosmic_text::Color::rgba(
cmp::max(0, cmp::min(255, (appearance.text_color.r * 255.0) as i32)) as u8,
cmp::max(0, cmp::min(255, (appearance.text_color.g * 255.0) as i32)) as u8,
cmp::max(0, cmp::min(255, (appearance.text_color.b * 255.0) as i32)) as u8,
cmp::max(0, cmp::min(255, (appearance.text_color.a * 255.0) as i32)) as u8,
);
let mut editor = self.editor.lock().unwrap();
let view_w = cmp::min(viewport.width as i32, layout.bounds().width as i32)
@ -229,18 +218,14 @@ where
// Draw to pixel buffer
let mut pixels = vec![0; image_w as usize * image_h as usize * 4];
editor.draw(
&mut state.cache.lock().unwrap(),
text_color,
|x, y, w, h, color| {
//TODO: improve performance
for row in 0..h as i32 {
for col in 0..w as i32 {
draw_pixel(&mut pixels, image_w, image_h, x + col, y + row, color);
}
editor.draw(&mut state.cache.lock().unwrap(), |x, y, w, h, color| {
//TODO: improve performance
for row in 0..h as i32 {
for col in 0..w as i32 {
draw_pixel(&mut pixels, image_w, image_h, x + col, y + row, color);
}
},
);
}
});
// Restore original metrics
editor.buffer_mut().set_metrics(metrics);
@ -370,14 +355,12 @@ where
}
}
impl<'a, 'editor, Editor, Message, Renderer> From<TextBox<'a, Editor>>
for Element<'a, Message, Renderer>
impl<'a, 'editor, Message, Renderer> From<TextBox<'a, 'editor>> for Element<'a, Message, Renderer>
where
Renderer: renderer::Renderer + image::Renderer<Handle = image::Handle>,
Renderer::Theme: StyleSheet,
Editor: Edit,
{
fn from(text_box: TextBox<'a, Editor>) -> Self {
fn from(text_box: TextBox<'a, 'editor>) -> Self {
Self::new(text_box)
}
}

View file

@ -95,8 +95,7 @@ fn main() {
let bg = editor.background_color();
window.set(orbclient::Color::rgb(bg.r(), bg.g(), bg.b()));
let fg = editor.foreground_color();
editor.draw(&mut swash_cache, fg, |x, y, w, h, color| {
editor.draw(&mut swash_cache, |x, y, w, h, color| {
window.rect(
line_x as i32 + x,
y,

View file

@ -15,6 +15,8 @@ fn redraw(
) {
let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34);
let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
let cursor_color = Color::rgb(0xFF, 0xFF, 0xFF);
let selection_color = Color::rgba(0xFF, 0xFF, 0xFF, 0x33);
editor.shape_as_needed(true);
if editor.buffer().redraw() {
@ -22,9 +24,15 @@ fn redraw(
window.set(bg_color);
editor.draw(swash_cache, font_color, |x, y, w, h, color| {
window.rect(x, y, w, h, orbclient::Color { data: color.0 });
});
editor.draw(
swash_cache,
font_color,
cursor_color,
selection_color,
|x, y, w, h, color| {
window.rect(x, y, w, h, orbclient::Color { data: color.0 });
},
);
window.sync();

View file

@ -130,6 +130,8 @@ fn main() {
loop {
let bg_color = orbclient::Color::rgb(0x34, 0x34, 0x34);
let font_color = Color::rgb(0xFF, 0xFF, 0xFF);
let cursor_color = Color::rgb(0xFF, 0xFF, 0xFF);
let selection_color = Color::rgba(0xFF, 0xFF, 0xFF, 0x33);
editor.shape_as_needed(true);
if editor.buffer().redraw() {
@ -137,9 +139,15 @@ fn main() {
window.set(bg_color);
editor.draw(&mut swash_cache, font_color, |x, y, w, h, color| {
window.rect(x, y, w, h, orbclient::Color { data: color.0 });
});
editor.draw(
&mut swash_cache,
font_color,
cursor_color,
selection_color,
|x, y, w, h, color| {
window.rect(x, y, w, h, orbclient::Color { data: color.0 });
},
);
window.sync();

View file

@ -365,10 +365,9 @@ impl Buffer {
while self.scroll.layout < 0 {
if self.scroll.line > 0 {
self.scroll.line -= 1;
let layout = self
.line_layout(font_system, self.scroll.line)
.expect("shape_until_scroll invalid scroll.line");
self.scroll.layout += layout.len() as i32;
if let Some(layout) = self.line_layout(font_system, self.scroll.line) {
self.scroll.layout += layout.len() as i32;
}
} else {
self.scroll.layout = 0;
break;
@ -801,8 +800,9 @@ impl Buffer {
&mut self,
font_system: &mut FontSystem,
mut cursor: Cursor,
mut cursor_x_opt: Option<i32>,
motion: Motion,
) -> Option<Cursor> {
) -> Option<(Cursor, Option<i32>)> {
match motion {
Motion::LayoutCursor(layout_cursor) => {
let layout = self.line_layout(font_system, layout_cursor.line)?;
@ -811,7 +811,9 @@ impl Buffer {
Some(some) => some,
None => match layout.last() {
Some(some) => some,
None => return None,
None => {
return None;
}
},
};
@ -853,7 +855,7 @@ impl Buffer {
cursor.index = self.lines.get(cursor.line)?.text().len();
cursor.affinity = Affinity::After;
}
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::Next => {
let line = self.lines.get(cursor.line)?;
@ -870,7 +872,7 @@ impl Buffer {
cursor.index = 0;
cursor.affinity = Affinity::Before;
}
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::Left => {
let rtl_opt = self
@ -878,9 +880,15 @@ impl Buffer {
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
cursor = self.cursor_motion(font_system, cursor, Motion::Next)?;
(cursor, cursor_x_opt) =
self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
} else {
cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Previous,
)?;
}
}
}
@ -890,17 +898,23 @@ impl Buffer {
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
cursor = self.cursor_motion(font_system, cursor, Motion::Previous)?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Previous,
)?;
} else {
cursor = self.cursor_motion(font_system, cursor, Motion::Next)?;
(cursor, cursor_x_opt) =
self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Next)?;
}
}
}
Motion::Up => {
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
if cursor.x_opt.is_none() {
cursor.x_opt = Some(
if cursor_x_opt.is_none() {
cursor_x_opt = Some(
layout_cursor.glyph as i32, //TODO: glyph x position
);
}
@ -912,20 +926,24 @@ impl Buffer {
layout_cursor.layout = usize::max_value();
}
if let Some(cursor_x) = cursor.x_opt {
if let Some(cursor_x) = cursor_x_opt {
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
}
cursor =
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
Motion::Down => {
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
let layout_len = self.line_layout(font_system, layout_cursor.line)?.len();
if cursor.x_opt.is_none() {
cursor.x_opt = Some(
if cursor_x_opt.is_none() {
cursor_x_opt = Some(
layout_cursor.glyph as i32, //TODO: glyph x position
);
}
@ -937,19 +955,30 @@ impl Buffer {
layout_cursor.layout = 0;
}
if let Some(cursor_x) = cursor.x_opt {
if let Some(cursor_x) = cursor_x_opt {
layout_cursor.glyph = cursor_x as usize; //TODO: glyph x position
}
cursor =
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
Motion::Home => {
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
layout_cursor.glyph = 0;
cursor =
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
cursor.x_opt = None;
#[allow(unused_assignments)]
{
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
cursor_x_opt = None;
}
Motion::SoftHome => {
let line = self.lines.get(cursor.line)?;
@ -959,34 +988,43 @@ impl Buffer {
.filter_map(|(i, c)| if c.is_whitespace() { None } else { Some(i) })
.next()
.unwrap_or(0);
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::End => {
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
layout_cursor.glyph = usize::max_value();
cursor =
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
cursor.x_opt = None;
#[allow(unused_assignments)]
{
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
cursor_x_opt = None;
}
Motion::ParagraphStart => {
cursor.index = 0;
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::ParagraphEnd => {
cursor.index = self.lines.get(cursor.line)?.text().len();
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::PageUp => {
cursor = self.cursor_motion(
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Vertical(-self.size().1 as i32),
)?;
}
Motion::PageDown => {
cursor = self.cursor_motion(
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Vertical(self.size().1 as i32),
)?;
}
@ -996,12 +1034,18 @@ impl Buffer {
match lines.cmp(&0) {
cmp::Ordering::Less => {
for _ in 0..-lines {
cursor = self.cursor_motion(font_system, cursor, Motion::Up)?;
(cursor, cursor_x_opt) =
self.cursor_motion(font_system, cursor, cursor_x_opt, Motion::Up)?;
}
}
cmp::Ordering::Greater => {
for _ in 0..lines {
cursor = self.cursor_motion(font_system, cursor, Motion::Down)?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::Down,
)?;
}
}
cmp::Ordering::Equal => {}
@ -1021,7 +1065,7 @@ impl Buffer {
cursor.line -= 1;
cursor.index = self.lines.get(cursor.line)?.text().len();
}
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::NextWord => {
let line = self.lines.get(cursor.line)?;
@ -1036,7 +1080,7 @@ impl Buffer {
cursor.line += 1;
cursor.index = 0;
}
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::LeftWord => {
let rtl_opt = self
@ -1044,9 +1088,19 @@ impl Buffer {
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::NextWord,
)?;
} else {
cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::PreviousWord,
)?;
}
}
}
@ -1056,30 +1110,44 @@ impl Buffer {
.map(|shape| shape.rtl);
if let Some(rtl) = rtl_opt {
if rtl {
cursor = self.cursor_motion(font_system, cursor, Motion::PreviousWord)?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::PreviousWord,
)?;
} else {
cursor = self.cursor_motion(font_system, cursor, Motion::NextWord)?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::NextWord,
)?;
}
}
}
Motion::BufferStart => {
cursor.line = 0;
cursor.index = 0;
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::BufferEnd => {
cursor.line = self.lines.len() - 1;
cursor.index = self.lines.get(cursor.line)?.text().len();
cursor.x_opt = None;
cursor_x_opt = None;
}
Motion::GotoLine(line) => {
let mut layout_cursor = self.layout_cursor(font_system, cursor)?;
layout_cursor.line = line;
cursor =
self.cursor_motion(font_system, cursor, Motion::LayoutCursor(layout_cursor))?;
(cursor, cursor_x_opt) = self.cursor_motion(
font_system,
cursor,
cursor_x_opt,
Motion::LayoutCursor(layout_cursor),
)?;
}
}
Some(cursor)
Some((cursor, cursor_x_opt))
}
/// Draw the buffer
@ -1203,8 +1271,14 @@ impl<'a> BorrowedWithFontSystem<'a, Buffer> {
}
/// Apply a [`Motion`] to a [`Cursor`]
pub fn cursor_motion(&mut self, cursor: Cursor, motion: Motion) -> Option<Cursor> {
self.inner.cursor_motion(self.font_system, cursor, motion)
pub fn cursor_motion(
&mut self,
cursor: Cursor,
cursor_x_opt: Option<i32>,
motion: Motion,
) -> Option<(Cursor, Option<i32>)> {
self.inner
.cursor_motion(self.font_system, cursor, cursor_x_opt, motion)
}
/// Draw the buffer

View file

@ -1,5 +1,3 @@
use crate::Color;
/// Current cursor location
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Cursor {
@ -10,10 +8,6 @@ pub struct Cursor {
/// Whether to associate the cursor with the run before it or the run after it if placed at the
/// boundary between two runs
pub affinity: Affinity,
/// Cached X position used for up and down movement
pub x_opt: Option<i32>,
/// Cursor color
pub color: Option<Color>,
}
impl Cursor {
@ -28,18 +22,6 @@ impl Cursor {
line,
index,
affinity,
x_opt: None,
color: None,
}
}
/// Create a new cursor, specifying the color
pub const fn new_with_color(line: usize, index: usize, color: Color) -> Self {
Self {
line,
index,
affinity: Affinity::Before,
x_opt: None,
color: Some(color),
}
}
}
@ -79,7 +61,7 @@ impl Affinity {
}
/// The position of a cursor within a [`Buffer`].
#[derive(Clone, Copy, Debug, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Eq, PartialEq, Ord, PartialOrd)]
pub struct LayoutCursor {
/// Index of [`BufferLine`] in [`Buffer::lines`]
pub line: usize,
@ -150,7 +132,7 @@ pub enum Motion {
}
/// Scroll position in [`Buffer`]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq)]
#[derive(Clone, Copy, Debug, Default, Eq, PartialEq, Ord, PartialOrd)]
pub struct Scroll {
/// Index of [`BufferLine`] in [`Buffer::lines`]. This will be adjusted as needed if layout is
/// out of bounds

View file

@ -8,8 +8,8 @@ use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
use crate::Color;
use crate::{
Action, AttrsList, Buffer, BufferLine, Change, ChangeItem, Cursor, Edit, FontSystem, Selection,
Shaping,
Action, AttrsList, BorrowedWithFontSystem, Buffer, BufferLine, Change, ChangeItem, Cursor,
Edit, FontSystem, Selection, Shaping,
};
/// A wrapper of [`Buffer`] for easy editing
@ -17,6 +17,7 @@ use crate::{
pub struct Editor {
buffer: Buffer,
cursor: Cursor,
cursor_x_opt: Option<i32>,
selection: Selection,
cursor_moved: bool,
auto_indent: bool,
@ -30,6 +31,7 @@ impl Editor {
Self {
buffer,
cursor: Cursor::default(),
cursor_x_opt: None,
selection: Selection::None,
cursor_moved: false,
auto_indent: false,
@ -37,6 +39,179 @@ impl Editor {
change: None,
}
}
/// Draw the editor
#[cfg(feature = "swash")]
pub fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
text_color: Color,
cursor_color: Color,
selection_color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let line_height = self.buffer.metrics().line_height;
for run in self.buffer.layout_runs() {
let line_i = run.line_i;
let line_y = run.line_y;
let line_top = run.line_top;
let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32)> {
if cursor.line == line_i {
for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
if cursor.index == glyph.start {
return Some((glyph_i, 0.0));
} else if cursor.index > glyph.start && cursor.index < glyph.end {
// Guess x offset based on characters
let mut before = 0;
let mut total = 0;
let cluster = &run.text[glyph.start..glyph.end];
for (i, _) in cluster.grapheme_indices(true) {
if glyph.start + i < cursor.index {
before += 1;
}
total += 1;
}
let offset = glyph.w * (before as f32) / (total as f32);
return Some((glyph_i, offset));
}
}
match run.glyphs.last() {
Some(glyph) => {
if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0));
}
}
None => {
return Some((0, 0.0));
}
}
}
None
};
// Highlight selection (TODO: HIGHLIGHT COLOR!)
if let Some((start, end)) = self.selection_bounds() {
if line_i >= start.line && line_i <= end.line {
let mut range_opt = None;
for glyph in run.glyphs.iter() {
// Guess x offset based on characters
let cluster = &run.text[glyph.start..glyph.end];
let total = cluster.grapheme_indices(true).count();
let mut c_x = glyph.x;
let c_w = glyph.w / total as f32;
for (i, c) in cluster.grapheme_indices(true) {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
selection_color,
);
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i {
// Highlight all of internal empty lines
range_opt = Some((0, self.buffer.size().0 as i32));
}
if let Some((mut min, mut max)) = range_opt.take() {
if end.line > line_i {
// Draw to end of line
if run.rtl {
min = 0;
} else {
max = self.buffer.size().0 as i32;
}
}
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
selection_color,
);
}
}
}
// Draw cursor
if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) {
let x = match run.glyphs.get(cursor_glyph) {
Some(glyph) => {
// Start of detected glyph
if glyph.level.is_rtl() {
(glyph.x + glyph.w - cursor_glyph_offset) as i32
} else {
(glyph.x + cursor_glyph_offset) as i32
}
}
None => match run.glyphs.last() {
Some(glyph) => {
// End of last glyph
if glyph.level.is_rtl() {
glyph.x as i32
} else {
(glyph.x + glyph.w) as i32
}
}
None => {
// Start of empty line
0
}
},
};
f(x, line_top as i32, 1, line_height as u32, cursor_color);
}
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => text_color,
};
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
}
}
}
}
impl Edit for Editor {
@ -342,10 +517,12 @@ impl Edit for Editor {
match action {
Action::Motion(motion) => {
if let Some(new_cursor) =
self.buffer.cursor_motion(font_system, self.cursor, motion)
if let Some((cursor, cursor_x_opt)) =
self.buffer
.cursor_motion(font_system, self.cursor, self.cursor_x_opt, motion)
{
self.cursor = new_cursor;
self.cursor = cursor;
self.cursor_x_opt = cursor_x_opt;
}
}
Action::Escape => {
@ -577,9 +754,7 @@ impl Edit for Editor {
if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) {
if new_cursor != self.cursor {
let color = self.cursor.color;
self.cursor = new_cursor;
self.cursor.color = color;
self.buffer.set_redraw(true);
}
}
@ -589,9 +764,7 @@ impl Edit for Editor {
if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) {
if new_cursor != self.cursor {
let color = self.cursor.color;
self.cursor = new_cursor;
self.cursor.color = color;
self.buffer.set_redraw(true);
}
self.selection = Selection::Word(self.cursor);
@ -603,9 +776,7 @@ impl Edit for Editor {
if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) {
if new_cursor != self.cursor {
let color = self.cursor.color;
self.cursor = new_cursor;
self.cursor.color = color;
}
self.selection = Selection::Line(self.cursor);
self.buffer.set_redraw(true);
@ -619,9 +790,7 @@ impl Edit for Editor {
if let Some(new_cursor) = self.buffer.hit(x as f32, y as f32) {
if new_cursor != self.cursor {
let color = self.cursor.color;
self.cursor = new_cursor;
self.cursor.color = color;
self.buffer.set_redraw(true);
}
}
@ -654,181 +823,27 @@ impl Edit for Editor {
*/
}
}
}
/// Draw the editor
impl<'a> BorrowedWithFontSystem<'a, Editor> {
#[cfg(feature = "swash")]
fn draw<F>(
&self,
font_system: &mut FontSystem,
pub fn draw<F>(
&mut self,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
text_color: Color,
cursor_color: Color,
selection_color: Color,
f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let line_height = self.buffer.metrics().line_height;
for run in self.buffer.layout_runs() {
let line_i = run.line_i;
let line_y = run.line_y;
let line_top = run.line_top;
let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32)> {
if cursor.line == line_i {
for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
if cursor.index == glyph.start {
return Some((glyph_i, 0.0));
} else if cursor.index > glyph.start && cursor.index < glyph.end {
// Guess x offset based on characters
let mut before = 0;
let mut total = 0;
let cluster = &run.text[glyph.start..glyph.end];
for (i, _) in cluster.grapheme_indices(true) {
if glyph.start + i < cursor.index {
before += 1;
}
total += 1;
}
let offset = glyph.w * (before as f32) / (total as f32);
return Some((glyph_i, offset));
}
}
match run.glyphs.last() {
Some(glyph) => {
if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0));
}
}
None => {
return Some((0, 0.0));
}
}
}
None
};
// Highlight selection (TODO: HIGHLIGHT COLOR!)
if let Some((start, end)) = self.selection_bounds() {
if line_i >= start.line && line_i <= end.line {
let mut range_opt = None;
for glyph in run.glyphs.iter() {
// Guess x offset based on characters
let cluster = &run.text[glyph.start..glyph.end];
let total = cluster.grapheme_indices(true).count();
let mut c_x = glyph.x;
let c_w = glyph.w / total as f32;
for (i, c) in cluster.grapheme_indices(true) {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i {
// Highlight all of internal empty lines
range_opt = Some((0, self.buffer.size().0 as i32));
}
if let Some((mut min, mut max)) = range_opt.take() {
if end.line > line_i {
// Draw to end of line
if run.rtl {
min = 0;
} else {
max = self.buffer.size().0 as i32;
}
}
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
}
}
// Draw cursor
if let Some((cursor_glyph, cursor_glyph_offset)) = cursor_glyph_opt(&self.cursor) {
let x = match run.glyphs.get(cursor_glyph) {
Some(glyph) => {
// Start of detected glyph
if glyph.level.is_rtl() {
(glyph.x + glyph.w - cursor_glyph_offset) as i32
} else {
(glyph.x + cursor_glyph_offset) as i32
}
}
None => match run.glyphs.last() {
Some(glyph) => {
// End of last glyph
if glyph.level.is_rtl() {
glyph.x as i32
} else {
(glyph.x + glyph.w) as i32
}
}
None => {
// Start of empty line
0
}
},
};
f(
x,
line_top as i32,
1,
line_height as u32,
self.cursor.color.unwrap_or(color),
);
}
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => color,
};
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
}
}
self.inner.draw(
self.font_system,
cache,
text_color,
cursor_color,
selection_color,
f,
);
}
}

View file

@ -3,8 +3,6 @@ use alloc::{string::String, vec::Vec};
use core::cmp;
use unicode_segmentation::UnicodeSegmentation;
#[cfg(feature = "swash")]
use crate::Color;
use crate::{AttrsList, BorrowedWithFontSystem, Buffer, Cursor, FontSystem, Motion};
pub use self::editor::*;
@ -265,17 +263,6 @@ pub trait Edit {
/// Perform an [Action] on the editor
fn action(&mut self, font_system: &mut FontSystem, action: Action);
/// Draw the editor
#[cfg(feature = "swash")]
fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
f: F,
) where
F: FnMut(i32, i32, u32, u32, Color);
}
impl<'a, T: Edit> BorrowedWithFontSystem<'a, T> {
@ -296,13 +283,4 @@ impl<'a, T: Edit> BorrowedWithFontSystem<'a, T> {
pub fn action(&mut self, action: Action) {
self.inner.action(self.font_system, action);
}
/// Draw the editor
#[cfg(feature = "swash")]
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, color: Color, f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(self.font_system, cache, color, f);
}
}

View file

@ -159,10 +159,52 @@ impl<'a> SyntaxEditor<'a> {
}
}
/// Get the default cursor color
pub fn cursor_color(&self) -> Color {
if let Some(some) = self.theme.settings.caret {
Color::rgba(some.r, some.g, some.b, some.a)
} else {
self.foreground_color()
}
}
/// Get the default selection color
pub fn selection_color(&self) -> Color {
if let Some(some) = self.theme.settings.selection {
Color::rgba(some.r, some.g, some.b, some.a)
} else {
let foreground_color = self.foreground_color();
Color::rgba(
foreground_color.r(),
foreground_color.g(),
foreground_color.b(),
0x33,
)
}
}
/// Get the current syntect theme
pub fn theme(&self) -> &SyntaxTheme {
self.theme
}
/// Draw the editor
#[cfg(feature = "swash")]
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let size = self.buffer().size();
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
self.editor.draw(
font_system,
cache,
self.foreground_color(),
self.cursor_color(),
self.selection_color(),
f,
);
}
}
impl<'a> Edit for SyntaxEditor<'a> {
@ -357,23 +399,6 @@ impl<'a> Edit for SyntaxEditor<'a> {
fn action(&mut self, font_system: &mut FontSystem, action: Action) {
self.editor.action(font_system, action);
}
/// Draw the editor
#[cfg(feature = "swash")]
fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
_color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let size = self.buffer().size();
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
self.editor
.draw(font_system, cache, self.foreground_color(), f);
}
}
impl<'a, 'b> BorrowedWithFontSystem<'b, SyntaxEditor<'a>> {
@ -386,4 +411,12 @@ impl<'a, 'b> BorrowedWithFontSystem<'b, SyntaxEditor<'a>> {
pub fn load_text<P: AsRef<Path>>(&mut self, path: P, attrs: crate::Attrs) -> io::Result<()> {
self.inner.load_text(self.font_system, path, attrs)
}
#[cfg(feature = "swash")]
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(self.font_system, cache, f);
}
}

View file

@ -201,6 +201,16 @@ impl<'a> ViEditor<'a> {
self.editor.foreground_color()
}
/// Get the default cursor color
pub fn cursor_color(&self) -> Color {
self.editor.cursor_color()
}
/// Get the default selection color
pub fn selection_color(&self) -> Color {
self.editor.selection_color()
}
/// Get the current syntect theme
pub fn theme(&self) -> &SyntaxTheme {
self.editor.theme()
@ -248,6 +258,218 @@ impl<'a> ViEditor<'a> {
self.changed = true;
}
}
#[cfg(feature = "swash")]
pub fn draw<F>(&self, font_system: &mut FontSystem, cache: &mut crate::SwashCache, mut f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
let size = self.buffer().size();
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
let font_size = self.buffer().metrics().font_size;
let line_height = self.buffer().metrics().line_height;
let foreground_color = self.foreground_color();
let cursor_color = self.cursor_color();
let selection_color = self.selection_color();
for run in self.buffer().layout_runs() {
let line_i = run.line_i;
let line_y = run.line_y;
let line_top = run.line_top;
let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> {
//TODO: better calculation of width
let default_width = font_size / 2.0;
if cursor.line == line_i {
for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
if cursor.index >= glyph.start && cursor.index < glyph.end {
// Guess x offset based on characters
let mut before = 0;
let mut total = 0;
let cluster = &run.text[glyph.start..glyph.end];
for (i, _) in cluster.grapheme_indices(true) {
if glyph.start + i < cursor.index {
before += 1;
}
total += 1;
}
let width = glyph.w / (total as f32);
let offset = (before as f32) * width;
return Some((glyph_i, offset, width));
}
}
match run.glyphs.last() {
Some(glyph) => {
if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0, default_width));
}
}
None => {
return Some((0, 0.0, default_width));
}
}
}
None
};
// Highlight selection (TODO: HIGHLIGHT COLOR!)
if let Some((start, end)) = self.selection_bounds() {
if line_i >= start.line && line_i <= end.line {
let mut range_opt = None;
for glyph in run.glyphs.iter() {
// Guess x offset based on characters
let cluster = &run.text[glyph.start..glyph.end];
let total = cluster.grapheme_indices(true).count();
let mut c_x = glyph.x;
let c_w = glyph.w / total as f32;
for (i, c) in cluster.grapheme_indices(true) {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
selection_color,
);
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i {
// Highlight all of internal empty lines
range_opt = Some((0, self.buffer().size().0 as i32));
}
if let Some((mut min, mut max)) = range_opt.take() {
if end.line > line_i {
// Draw to end of line
if run.rtl {
min = 0;
} else {
max = self.buffer().size().0 as i32;
}
}
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
selection_color,
);
}
}
}
// Draw cursor
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
cursor_glyph_opt(&self.cursor())
{
let block_cursor = if self.passthrough {
false
} else {
match self.parser.mode {
ViMode::Insert | ViMode::Replace => false,
_ => true, /*TODO: determine block cursor in other modes*/
}
};
let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
Some(glyph) => {
// Start of detected glyph
if glyph.level.is_rtl() {
(
(glyph.x + glyph.w - cursor_glyph_offset) as i32,
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
as i32,
)
} else {
(
(glyph.x + cursor_glyph_offset) as i32,
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
)
}
}
None => match run.glyphs.last() {
Some(glyph) => {
// End of last glyph
if glyph.level.is_rtl() {
(glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
} else {
(
(glyph.x + glyph.w) as i32,
(glyph.x + glyph.w + cursor_glyph_width) as i32,
)
}
}
None => {
// Start of empty line
(0, cursor_glyph_width as i32)
}
},
};
if block_cursor {
let left_x = cmp::min(start_x, end_x);
let right_x = cmp::max(start_x, end_x);
f(
left_x,
line_top as i32,
(right_x - left_x) as u32,
line_height as u32,
selection_color,
);
} else {
f(
start_x,
line_top as i32,
1,
line_height as u32,
cursor_color,
);
}
}
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => foreground_color,
};
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
}
}
}
}
impl<'a> Edit for ViEditor<'a> {
@ -838,214 +1060,6 @@ impl<'a> Edit for ViEditor<'a> {
editor.action(font_system, action);
});
}
#[cfg(feature = "swash")]
fn draw<F>(
&self,
font_system: &mut FontSystem,
cache: &mut crate::SwashCache,
color: Color,
mut f: F,
) where
F: FnMut(i32, i32, u32, u32, Color),
{
let size = self.buffer().size();
f(0, 0, size.0 as u32, size.1 as u32, self.background_color());
let font_size = self.buffer().metrics().font_size;
let line_height = self.buffer().metrics().line_height;
for run in self.buffer().layout_runs() {
let line_i = run.line_i;
let line_y = run.line_y;
let line_top = run.line_top;
let cursor_glyph_opt = |cursor: &Cursor| -> Option<(usize, f32, f32)> {
//TODO: better calculation of width
let default_width = font_size / 2.0;
if cursor.line == line_i {
for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
if cursor.index >= glyph.start && cursor.index < glyph.end {
// Guess x offset based on characters
let mut before = 0;
let mut total = 0;
let cluster = &run.text[glyph.start..glyph.end];
for (i, _) in cluster.grapheme_indices(true) {
if glyph.start + i < cursor.index {
before += 1;
}
total += 1;
}
let width = glyph.w / (total as f32);
let offset = (before as f32) * width;
return Some((glyph_i, offset, width));
}
}
match run.glyphs.last() {
Some(glyph) => {
if cursor.index == glyph.end {
return Some((run.glyphs.len(), 0.0, default_width));
}
}
None => {
return Some((0, 0.0, default_width));
}
}
}
None
};
// Highlight selection (TODO: HIGHLIGHT COLOR!)
if let Some((start, end)) = self.selection_bounds() {
if line_i >= start.line && line_i <= end.line {
let mut range_opt = None;
for glyph in run.glyphs.iter() {
// Guess x offset based on characters
let cluster = &run.text[glyph.start..glyph.end];
let total = cluster.grapheme_indices(true).count();
let mut c_x = glyph.x;
let c_w = glyph.w / total as f32;
for (i, c) in cluster.grapheme_indices(true) {
let c_start = glyph.start + i;
let c_end = glyph.start + i + c.len();
if (start.line != line_i || c_end > start.index)
&& (end.line != line_i || c_start < end.index)
{
range_opt = match range_opt.take() {
Some((min, max)) => Some((
cmp::min(min, c_x as i32),
cmp::max(max, (c_x + c_w) as i32),
)),
None => Some((c_x as i32, (c_x + c_w) as i32)),
};
} else if let Some((min, max)) = range_opt.take() {
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
c_x += c_w;
}
}
if run.glyphs.is_empty() && end.line > line_i {
// Highlight all of internal empty lines
range_opt = Some((0, self.buffer().size().0 as i32));
}
if let Some((mut min, mut max)) = range_opt.take() {
if end.line > line_i {
// Draw to end of line
if run.rtl {
min = 0;
} else {
max = self.buffer().size().0 as i32;
}
}
f(
min,
line_top as i32,
cmp::max(0, max - min) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
}
}
}
// Draw cursor
if let Some((cursor_glyph, cursor_glyph_offset, cursor_glyph_width)) =
cursor_glyph_opt(&self.cursor())
{
let block_cursor = if self.passthrough {
false
} else {
match self.parser.mode {
ViMode::Insert | ViMode::Replace => false,
_ => true, /*TODO: determine block cursor in other modes*/
}
};
let (start_x, end_x) = match run.glyphs.get(cursor_glyph) {
Some(glyph) => {
// Start of detected glyph
if glyph.level.is_rtl() {
(
(glyph.x + glyph.w - cursor_glyph_offset) as i32,
(glyph.x + glyph.w - cursor_glyph_offset - cursor_glyph_width)
as i32,
)
} else {
(
(glyph.x + cursor_glyph_offset) as i32,
(glyph.x + cursor_glyph_offset + cursor_glyph_width) as i32,
)
}
}
None => match run.glyphs.last() {
Some(glyph) => {
// End of last glyph
if glyph.level.is_rtl() {
(glyph.x as i32, (glyph.x - cursor_glyph_width) as i32)
} else {
(
(glyph.x + glyph.w) as i32,
(glyph.x + glyph.w + cursor_glyph_width) as i32,
)
}
}
None => {
// Start of empty line
(0, cursor_glyph_width as i32)
}
},
};
if block_cursor {
let left_x = cmp::min(start_x, end_x);
let right_x = cmp::max(start_x, end_x);
f(
left_x,
line_top as i32,
(right_x - left_x) as u32,
line_height as u32,
Color::rgba(color.r(), color.g(), color.b(), 0x33),
);
} else {
f(start_x, line_top as i32, 1, line_height as u32, color);
}
}
for glyph in run.glyphs.iter() {
let physical_glyph = glyph.physical((0., 0.), 1.0);
let glyph_color = match glyph.color_opt {
Some(some) => some,
None => color,
};
cache.with_pixels(
font_system,
physical_glyph.cache_key,
glyph_color,
|x, y, color| {
f(
physical_glyph.x + x,
line_y as i32 + physical_glyph.y + y,
1,
1,
color,
);
},
);
}
}
}
}
impl<'a, 'b> BorrowedWithFontSystem<'b, ViEditor<'a>> {
@ -1058,4 +1072,12 @@ impl<'a, 'b> BorrowedWithFontSystem<'b, ViEditor<'a>> {
) -> std::io::Result<()> {
self.inner.load_text(self.font_system, path, attrs)
}
#[cfg(feature = "swash")]
pub fn draw<F>(&mut self, cache: &mut crate::SwashCache, f: F)
where
F: FnMut(i32, i32, u32, u32, Color),
{
self.inner.draw(self.font_system, cache, f);
}
}