fix: use highlight and cursor from cosmic-text editor
This fixes RTL text in the text_editor widget.
This commit is contained in:
parent
1fdd24ab99
commit
e1fc659e64
5 changed files with 57 additions and 188 deletions
2
Cargo.lock
generated
2
Cargo.lock
generated
|
|
@ -1316,7 +1316,7 @@ dependencies = [
|
||||||
[[package]]
|
[[package]]
|
||||||
name = "cosmic-text"
|
name = "cosmic-text"
|
||||||
version = "0.18.2"
|
version = "0.18.2"
|
||||||
source = "git+https://github.com/pop-os/cosmic-text.git#ddad5fb7410e374612925415a13843ed38f14245"
|
source = "git+https://github.com/pop-os/cosmic-text.git#5651c2d96727749f8b19df4fda332bd8a2e6d823"
|
||||||
dependencies = [
|
dependencies = [
|
||||||
"bitflags 2.11.0",
|
"bitflags 2.11.0",
|
||||||
"fontdb",
|
"fontdb",
|
||||||
|
|
|
||||||
|
|
@ -256,15 +256,11 @@ pub fn align(
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// TODO: Avoid relayout with some changes to `cosmic-text` (?)
|
|
||||||
if needs_relayout {
|
if needs_relayout {
|
||||||
log::trace!("Relayouting paragraph...");
|
log::trace!("Relayouting paragraph...");
|
||||||
|
|
||||||
buffer.set_size(
|
buffer.set_size(Some(min_bounds.width), Some(min_bounds.height));
|
||||||
font_system,
|
buffer.shape_until_scroll(font_system, false);
|
||||||
Some(min_bounds.width),
|
|
||||||
Some(min_bounds.height),
|
|
||||||
);
|
|
||||||
}
|
}
|
||||||
|
|
||||||
min_bounds
|
min_bounds
|
||||||
|
|
|
||||||
|
|
@ -47,18 +47,17 @@ impl Cache {
|
||||||
let mut buffer = cosmic_text::Buffer::new(font_system, metrics);
|
let mut buffer = cosmic_text::Buffer::new(font_system, metrics);
|
||||||
|
|
||||||
buffer.set_size(
|
buffer.set_size(
|
||||||
font_system,
|
|
||||||
Some(key.bounds.width),
|
Some(key.bounds.width),
|
||||||
Some(key.bounds.height.max(key.line_height)),
|
Some(key.bounds.height.max(key.line_height)),
|
||||||
);
|
);
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
font_system,
|
|
||||||
key.content,
|
key.content,
|
||||||
&text::to_attributes(key.font),
|
&text::to_attributes(key.font),
|
||||||
text::to_shaping(key.shaping, key.content),
|
text::to_shaping(key.shaping, key.content),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
buffer.shape_until_scroll(font_system, false);
|
||||||
let bounds = text::align(&mut buffer, font_system, key.align_x);
|
let bounds = text::align(&mut buffer, font_system, key.align_x);
|
||||||
|
|
||||||
let _ = entry.insert(Entry {
|
let _ = entry.insert(Entry {
|
||||||
|
|
|
||||||
|
|
@ -96,13 +96,13 @@ impl editor::Editor for Editor {
|
||||||
text::font_system().write().expect("Write font system");
|
text::font_system().write().expect("Write font system");
|
||||||
|
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
font_system.raw(),
|
|
||||||
text,
|
text,
|
||||||
&cosmic_text::Attrs::new(),
|
&cosmic_text::Attrs::new(),
|
||||||
cosmic_text::Shaping::Advanced,
|
cosmic_text::Shaping::Advanced,
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
buffer.shape_until_scroll(font_system.raw(), false);
|
||||||
Editor(Some(Arc::new(Internal {
|
Editor(Some(Arc::new(Internal {
|
||||||
editor: cosmic_text::Editor::new(buffer),
|
editor: cosmic_text::Editor::new(buffer),
|
||||||
version: font_system.version(),
|
version: font_system.version(),
|
||||||
|
|
@ -151,120 +151,54 @@ impl editor::Editor for Editor {
|
||||||
let cursor = match internal.editor.selection_bounds() {
|
let cursor = match internal.editor.selection_bounds() {
|
||||||
Some((start, end)) => {
|
Some((start, end)) => {
|
||||||
let line_height = buffer.metrics().line_height;
|
let line_height = buffer.metrics().line_height;
|
||||||
let selected_lines = end.line - start.line + 1;
|
let scroll_y = buffer.scroll().vertical;
|
||||||
|
|
||||||
let visual_lines_offset =
|
|
||||||
visual_lines_offset(start.line, buffer);
|
|
||||||
|
|
||||||
let regions = buffer
|
let regions = buffer
|
||||||
.lines
|
.layout_runs()
|
||||||
.iter()
|
.filter(|run| {
|
||||||
.skip(start.line)
|
run.line_i >= start.line && run.line_i <= end.line
|
||||||
.take(selected_lines)
|
|
||||||
.enumerate()
|
|
||||||
.flat_map(|(i, line)| {
|
|
||||||
highlight_line(
|
|
||||||
line,
|
|
||||||
if i == 0 { start.index } else { 0 },
|
|
||||||
if i == selected_lines - 1 {
|
|
||||||
end.index
|
|
||||||
} else {
|
|
||||||
line.text().len()
|
|
||||||
},
|
|
||||||
)
|
|
||||||
})
|
})
|
||||||
.enumerate()
|
.flat_map(|run| {
|
||||||
.filter_map(|(visual_line, (x, width))| {
|
let line_top = run.line_top;
|
||||||
if width > 0.0 {
|
run.highlight(start, end)
|
||||||
Some(Rectangle {
|
.filter(|(_, width)| *width > 0.0)
|
||||||
|
.map(move |(x, width)| Rectangle {
|
||||||
x,
|
x,
|
||||||
width,
|
width,
|
||||||
y: (visual_line as i32 + visual_lines_offset)
|
y: line_top - scroll_y,
|
||||||
as f32
|
|
||||||
* line_height
|
|
||||||
- buffer.scroll().vertical,
|
|
||||||
height: line_height,
|
height: line_height,
|
||||||
})
|
})
|
||||||
} else {
|
.collect::<Vec<_>>()
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
})
|
||||||
.collect();
|
.collect();
|
||||||
|
|
||||||
Selection::Range(regions)
|
Selection::Range(regions)
|
||||||
}
|
}
|
||||||
_ => {
|
_ => {
|
||||||
let line_height = buffer.metrics().line_height;
|
let scroll_y = buffer.scroll().vertical;
|
||||||
|
|
||||||
|
let point = buffer
|
||||||
|
.layout_runs()
|
||||||
|
.filter(|run| run.line_i == cursor.line)
|
||||||
|
.find_map(|run| {
|
||||||
|
run.cursor_position(&cursor).map(|x| {
|
||||||
|
let buffer_w = buffer.size().0.unwrap_or(x + 1.0);
|
||||||
|
let x = x.min((buffer_w - 1.0).max(0.0));
|
||||||
|
Point::new(x, run.line_top - scroll_y)
|
||||||
|
})
|
||||||
|
})
|
||||||
|
.unwrap_or_else(|| {
|
||||||
|
// Fallback: cursor not found in any run (e.g. empty buffer).
|
||||||
|
let line_height = buffer.metrics().line_height;
|
||||||
let visual_lines_offset =
|
let visual_lines_offset =
|
||||||
visual_lines_offset(cursor.line, buffer);
|
visual_lines_offset(cursor.line, buffer);
|
||||||
|
Point::new(
|
||||||
|
0.0,
|
||||||
|
visual_lines_offset as f32 * line_height - scroll_y,
|
||||||
|
)
|
||||||
|
});
|
||||||
|
|
||||||
let line = buffer
|
Selection::Caret(point)
|
||||||
.lines
|
|
||||||
.get(cursor.line)
|
|
||||||
.expect("Cursor line should be present");
|
|
||||||
|
|
||||||
let layout =
|
|
||||||
line.layout_opt().expect("Line layout should be cached");
|
|
||||||
|
|
||||||
let mut lines = layout.iter().enumerate();
|
|
||||||
|
|
||||||
let (visual_line, offset) = lines
|
|
||||||
.find_map(|(i, line)| {
|
|
||||||
let start = line
|
|
||||||
.glyphs
|
|
||||||
.first()
|
|
||||||
.map(|glyph| glyph.start)
|
|
||||||
.unwrap_or(0);
|
|
||||||
let end = line
|
|
||||||
.glyphs
|
|
||||||
.last()
|
|
||||||
.map(|glyph| glyph.end)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let is_cursor_before_start = start > cursor.index;
|
|
||||||
|
|
||||||
let is_cursor_before_end = match cursor.affinity {
|
|
||||||
cosmic_text::Affinity::Before => {
|
|
||||||
cursor.index <= end
|
|
||||||
}
|
|
||||||
cosmic_text::Affinity::After => cursor.index < end,
|
|
||||||
};
|
|
||||||
|
|
||||||
if is_cursor_before_start {
|
|
||||||
// Sometimes, the glyph we are looking for is right
|
|
||||||
// between lines. This can happen when a line wraps
|
|
||||||
// on a space.
|
|
||||||
// In that case, we can assume the cursor is at the
|
|
||||||
// end of the previous line.
|
|
||||||
// i is guaranteed to be > 0 because `start` is always
|
|
||||||
// 0 for the first line, so there is no way for the
|
|
||||||
// cursor to be before it.
|
|
||||||
Some((i - 1, layout[i - 1].w))
|
|
||||||
} else if is_cursor_before_end {
|
|
||||||
let offset = line
|
|
||||||
.glyphs
|
|
||||||
.iter()
|
|
||||||
.take_while(|glyph| cursor.index > glyph.start)
|
|
||||||
.map(|glyph| glyph.w)
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
Some((i, offset))
|
|
||||||
} else {
|
|
||||||
None
|
|
||||||
}
|
|
||||||
})
|
|
||||||
.unwrap_or((
|
|
||||||
layout.len().saturating_sub(1),
|
|
||||||
layout.last().map(|line| line.w).unwrap_or(0.0),
|
|
||||||
));
|
|
||||||
|
|
||||||
Selection::Caret(Point::new(
|
|
||||||
offset,
|
|
||||||
(visual_lines_offset + visual_line as i32) as f32
|
|
||||||
* line_height
|
|
||||||
- buffer.scroll().vertical,
|
|
||||||
))
|
|
||||||
}
|
}
|
||||||
};
|
};
|
||||||
|
|
||||||
|
|
@ -590,10 +524,10 @@ impl editor::Editor for Editor {
|
||||||
{
|
{
|
||||||
log::trace!("Updating `Metrics` of `Editor`...");
|
log::trace!("Updating `Metrics` of `Editor`...");
|
||||||
|
|
||||||
buffer.set_metrics(
|
buffer.set_metrics(cosmic_text::Metrics::new(
|
||||||
font_system.raw(),
|
new_size.0,
|
||||||
cosmic_text::Metrics::new(new_size.0, new_line_height.0),
|
new_line_height.0,
|
||||||
);
|
));
|
||||||
}
|
}
|
||||||
|
|
||||||
let new_wrap = text::to_wrap(new_wrapping);
|
let new_wrap = text::to_wrap(new_wrapping);
|
||||||
|
|
@ -601,17 +535,14 @@ impl editor::Editor for Editor {
|
||||||
if new_wrap != buffer.wrap() {
|
if new_wrap != buffer.wrap() {
|
||||||
log::trace!("Updating `Wrap` strategy of `Editor`...");
|
log::trace!("Updating `Wrap` strategy of `Editor`...");
|
||||||
|
|
||||||
buffer.set_wrap(font_system.raw(), new_wrap);
|
buffer.set_wrap(new_wrap);
|
||||||
}
|
}
|
||||||
|
|
||||||
if new_bounds != internal.bounds {
|
if new_bounds != internal.bounds {
|
||||||
log::trace!("Updating size of `Editor`...");
|
log::trace!("Updating size of `Editor`...");
|
||||||
|
|
||||||
buffer.set_size(
|
buffer
|
||||||
font_system.raw(),
|
.set_size(Some(new_bounds.width), Some(new_bounds.height));
|
||||||
Some(new_bounds.width),
|
|
||||||
Some(new_bounds.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
internal.bounds = new_bounds;
|
internal.bounds = new_bounds;
|
||||||
}
|
}
|
||||||
|
|
@ -778,53 +709,6 @@ impl PartialEq for Weak {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
fn highlight_line(
|
|
||||||
line: &cosmic_text::BufferLine,
|
|
||||||
from: usize,
|
|
||||||
to: usize,
|
|
||||||
) -> impl Iterator<Item = (f32, f32)> + '_ {
|
|
||||||
let layout = line.layout_opt().map(Vec::as_slice).unwrap_or_default();
|
|
||||||
|
|
||||||
layout.iter().map(move |visual_line| {
|
|
||||||
let start = visual_line
|
|
||||||
.glyphs
|
|
||||||
.first()
|
|
||||||
.map(|glyph| glyph.start)
|
|
||||||
.unwrap_or(0);
|
|
||||||
let end = visual_line
|
|
||||||
.glyphs
|
|
||||||
.last()
|
|
||||||
.map(|glyph| glyph.end)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let range = start.max(from)..end.min(to);
|
|
||||||
|
|
||||||
if range.is_empty() {
|
|
||||||
(0.0, 0.0)
|
|
||||||
} else if range.start == start && range.end == end {
|
|
||||||
(0.0, visual_line.w)
|
|
||||||
} else {
|
|
||||||
let first_glyph = visual_line
|
|
||||||
.glyphs
|
|
||||||
.iter()
|
|
||||||
.position(|glyph| range.start <= glyph.start)
|
|
||||||
.unwrap_or(0);
|
|
||||||
|
|
||||||
let mut glyphs = visual_line.glyphs.iter();
|
|
||||||
|
|
||||||
let x =
|
|
||||||
glyphs.by_ref().take(first_glyph).map(|glyph| glyph.w).sum();
|
|
||||||
|
|
||||||
let width: f32 = glyphs
|
|
||||||
.take_while(|glyph| range.end > glyph.start)
|
|
||||||
.map(|glyph| glyph.w)
|
|
||||||
.sum();
|
|
||||||
|
|
||||||
(x, width)
|
|
||||||
}
|
|
||||||
})
|
|
||||||
}
|
|
||||||
|
|
||||||
fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
|
fn visual_lines_offset(line: usize, buffer: &cosmic_text::Buffer) -> i32 {
|
||||||
let scroll = buffer.scroll();
|
let scroll = buffer.scroll();
|
||||||
|
|
||||||
|
|
|
||||||
|
|
@ -79,26 +79,19 @@ impl core::text::Paragraph for Paragraph {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
buffer.set_size(
|
buffer.set_size(Some(text.bounds.width), Some(text.bounds.height));
|
||||||
font_system.raw(),
|
|
||||||
Some(text.bounds.width),
|
|
||||||
Some(text.bounds.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
|
buffer.set_wrap(text::to_wrap(text.wrapping));
|
||||||
buffer.set_ellipsize(
|
buffer.set_ellipsize(text::to_ellipsize(text.ellipsize));
|
||||||
font_system.raw(),
|
|
||||||
text::to_ellipsize(text.ellipsize),
|
|
||||||
);
|
|
||||||
|
|
||||||
buffer.set_text(
|
buffer.set_text(
|
||||||
font_system.raw(),
|
|
||||||
text.content,
|
text.content,
|
||||||
&text::to_attributes(text.font),
|
&text::to_attributes(text.font),
|
||||||
text::to_shaping(text.shaping, text.content),
|
text::to_shaping(text.shaping, text.content),
|
||||||
None,
|
None,
|
||||||
);
|
);
|
||||||
|
|
||||||
|
buffer.shape_until_scroll(font_system.raw(), false);
|
||||||
let min_bounds =
|
let min_bounds =
|
||||||
text::align(&mut buffer, font_system.raw(), text.align_x);
|
text::align(&mut buffer, font_system.raw(), text.align_x);
|
||||||
|
|
||||||
|
|
@ -130,16 +123,11 @@ impl core::text::Paragraph for Paragraph {
|
||||||
),
|
),
|
||||||
);
|
);
|
||||||
|
|
||||||
buffer.set_size(
|
buffer.set_size(Some(text.bounds.width), Some(text.bounds.height));
|
||||||
font_system.raw(),
|
|
||||||
Some(text.bounds.width),
|
|
||||||
Some(text.bounds.height),
|
|
||||||
);
|
|
||||||
|
|
||||||
buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
|
buffer.set_wrap(text::to_wrap(text.wrapping));
|
||||||
|
|
||||||
buffer.set_rich_text(
|
buffer.set_rich_text(
|
||||||
font_system.raw(),
|
|
||||||
text.content.iter().enumerate().map(|(i, span)| {
|
text.content.iter().enumerate().map(|(i, span)| {
|
||||||
let attrs = text::to_attributes(span.font.unwrap_or(text.font));
|
let attrs = text::to_attributes(span.font.unwrap_or(text.font));
|
||||||
|
|
||||||
|
|
@ -176,6 +164,7 @@ impl core::text::Paragraph for Paragraph {
|
||||||
},
|
},
|
||||||
);
|
);
|
||||||
|
|
||||||
|
buffer.shape_until_scroll(font_system.raw(), false);
|
||||||
let min_bounds =
|
let min_bounds =
|
||||||
text::align(&mut buffer, font_system.raw(), text.align_x);
|
text::align(&mut buffer, font_system.raw(), text.align_x);
|
||||||
|
|
||||||
|
|
@ -199,11 +188,12 @@ impl core::text::Paragraph for Paragraph {
|
||||||
let mut font_system =
|
let mut font_system =
|
||||||
text::font_system().write().expect("Write font system");
|
text::font_system().write().expect("Write font system");
|
||||||
|
|
||||||
paragraph.buffer.set_size(
|
paragraph
|
||||||
font_system.raw(),
|
.buffer
|
||||||
Some(new_bounds.width),
|
.set_size(Some(new_bounds.width), Some(new_bounds.height));
|
||||||
Some(new_bounds.height),
|
paragraph
|
||||||
);
|
.buffer
|
||||||
|
.shape_until_scroll(font_system.raw(), false);
|
||||||
|
|
||||||
let min_bounds = text::align(
|
let min_bounds = text::align(
|
||||||
&mut paragraph.buffer,
|
&mut paragraph.buffer,
|
||||||
|
|
|
||||||
Loading…
Add table
Add a link
Reference in a new issue