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]]
|
||||
name = "cosmic-text"
|
||||
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 = [
|
||||
"bitflags 2.11.0",
|
||||
"fontdb",
|
||||
|
|
|
|||
|
|
@ -256,15 +256,11 @@ pub fn align(
|
|||
}
|
||||
}
|
||||
|
||||
// TODO: Avoid relayout with some changes to `cosmic-text` (?)
|
||||
if needs_relayout {
|
||||
log::trace!("Relayouting paragraph...");
|
||||
|
||||
buffer.set_size(
|
||||
font_system,
|
||||
Some(min_bounds.width),
|
||||
Some(min_bounds.height),
|
||||
);
|
||||
buffer.set_size(Some(min_bounds.width), Some(min_bounds.height));
|
||||
buffer.shape_until_scroll(font_system, false);
|
||||
}
|
||||
|
||||
min_bounds
|
||||
|
|
|
|||
|
|
@ -47,18 +47,17 @@ impl Cache {
|
|||
let mut buffer = cosmic_text::Buffer::new(font_system, metrics);
|
||||
|
||||
buffer.set_size(
|
||||
font_system,
|
||||
Some(key.bounds.width),
|
||||
Some(key.bounds.height.max(key.line_height)),
|
||||
);
|
||||
buffer.set_text(
|
||||
font_system,
|
||||
key.content,
|
||||
&text::to_attributes(key.font),
|
||||
text::to_shaping(key.shaping, key.content),
|
||||
None,
|
||||
);
|
||||
|
||||
buffer.shape_until_scroll(font_system, false);
|
||||
let bounds = text::align(&mut buffer, font_system, key.align_x);
|
||||
|
||||
let _ = entry.insert(Entry {
|
||||
|
|
|
|||
|
|
@ -96,13 +96,13 @@ impl editor::Editor for Editor {
|
|||
text::font_system().write().expect("Write font system");
|
||||
|
||||
buffer.set_text(
|
||||
font_system.raw(),
|
||||
text,
|
||||
&cosmic_text::Attrs::new(),
|
||||
cosmic_text::Shaping::Advanced,
|
||||
None,
|
||||
);
|
||||
|
||||
buffer.shape_until_scroll(font_system.raw(), false);
|
||||
Editor(Some(Arc::new(Internal {
|
||||
editor: cosmic_text::Editor::new(buffer),
|
||||
version: font_system.version(),
|
||||
|
|
@ -151,120 +151,54 @@ impl editor::Editor for Editor {
|
|||
let cursor = match internal.editor.selection_bounds() {
|
||||
Some((start, end)) => {
|
||||
let line_height = buffer.metrics().line_height;
|
||||
let selected_lines = end.line - start.line + 1;
|
||||
|
||||
let visual_lines_offset =
|
||||
visual_lines_offset(start.line, buffer);
|
||||
let scroll_y = buffer.scroll().vertical;
|
||||
|
||||
let regions = buffer
|
||||
.lines
|
||||
.iter()
|
||||
.skip(start.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()
|
||||
},
|
||||
)
|
||||
.layout_runs()
|
||||
.filter(|run| {
|
||||
run.line_i >= start.line && run.line_i <= end.line
|
||||
})
|
||||
.enumerate()
|
||||
.filter_map(|(visual_line, (x, width))| {
|
||||
if width > 0.0 {
|
||||
Some(Rectangle {
|
||||
.flat_map(|run| {
|
||||
let line_top = run.line_top;
|
||||
run.highlight(start, end)
|
||||
.filter(|(_, width)| *width > 0.0)
|
||||
.map(move |(x, width)| Rectangle {
|
||||
x,
|
||||
width,
|
||||
y: (visual_line as i32 + visual_lines_offset)
|
||||
as f32
|
||||
* line_height
|
||||
- buffer.scroll().vertical,
|
||||
y: line_top - scroll_y,
|
||||
height: line_height,
|
||||
})
|
||||
} else {
|
||||
None
|
||||
}
|
||||
.collect::<Vec<_>>()
|
||||
})
|
||||
.collect();
|
||||
|
||||
Selection::Range(regions)
|
||||
}
|
||||
_ => {
|
||||
let line_height = buffer.metrics().line_height;
|
||||
let scroll_y = buffer.scroll().vertical;
|
||||
|
||||
let visual_lines_offset =
|
||||
visual_lines_offset(cursor.line, buffer);
|
||||
|
||||
let line = buffer
|
||||
.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
|
||||
}
|
||||
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((
|
||||
layout.len().saturating_sub(1),
|
||||
layout.last().map(|line| line.w).unwrap_or(0.0),
|
||||
));
|
||||
.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 =
|
||||
visual_lines_offset(cursor.line, buffer);
|
||||
Point::new(
|
||||
0.0,
|
||||
visual_lines_offset as f32 * line_height - scroll_y,
|
||||
)
|
||||
});
|
||||
|
||||
Selection::Caret(Point::new(
|
||||
offset,
|
||||
(visual_lines_offset + visual_line as i32) as f32
|
||||
* line_height
|
||||
- buffer.scroll().vertical,
|
||||
))
|
||||
Selection::Caret(point)
|
||||
}
|
||||
};
|
||||
|
||||
|
|
@ -590,10 +524,10 @@ impl editor::Editor for Editor {
|
|||
{
|
||||
log::trace!("Updating `Metrics` of `Editor`...");
|
||||
|
||||
buffer.set_metrics(
|
||||
font_system.raw(),
|
||||
cosmic_text::Metrics::new(new_size.0, new_line_height.0),
|
||||
);
|
||||
buffer.set_metrics(cosmic_text::Metrics::new(
|
||||
new_size.0,
|
||||
new_line_height.0,
|
||||
));
|
||||
}
|
||||
|
||||
let new_wrap = text::to_wrap(new_wrapping);
|
||||
|
|
@ -601,17 +535,14 @@ impl editor::Editor for Editor {
|
|||
if new_wrap != buffer.wrap() {
|
||||
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 {
|
||||
log::trace!("Updating size of `Editor`...");
|
||||
|
||||
buffer.set_size(
|
||||
font_system.raw(),
|
||||
Some(new_bounds.width),
|
||||
Some(new_bounds.height),
|
||||
);
|
||||
buffer
|
||||
.set_size(Some(new_bounds.width), Some(new_bounds.height));
|
||||
|
||||
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 {
|
||||
let scroll = buffer.scroll();
|
||||
|
||||
|
|
|
|||
|
|
@ -79,26 +79,19 @@ impl core::text::Paragraph for Paragraph {
|
|||
),
|
||||
);
|
||||
|
||||
buffer.set_size(
|
||||
font_system.raw(),
|
||||
Some(text.bounds.width),
|
||||
Some(text.bounds.height),
|
||||
);
|
||||
buffer.set_size(Some(text.bounds.width), Some(text.bounds.height));
|
||||
|
||||
buffer.set_wrap(font_system.raw(), text::to_wrap(text.wrapping));
|
||||
buffer.set_ellipsize(
|
||||
font_system.raw(),
|
||||
text::to_ellipsize(text.ellipsize),
|
||||
);
|
||||
buffer.set_wrap(text::to_wrap(text.wrapping));
|
||||
buffer.set_ellipsize(text::to_ellipsize(text.ellipsize));
|
||||
|
||||
buffer.set_text(
|
||||
font_system.raw(),
|
||||
text.content,
|
||||
&text::to_attributes(text.font),
|
||||
text::to_shaping(text.shaping, text.content),
|
||||
None,
|
||||
);
|
||||
|
||||
buffer.shape_until_scroll(font_system.raw(), false);
|
||||
let min_bounds =
|
||||
text::align(&mut buffer, font_system.raw(), text.align_x);
|
||||
|
||||
|
|
@ -130,16 +123,11 @@ impl core::text::Paragraph for Paragraph {
|
|||
),
|
||||
);
|
||||
|
||||
buffer.set_size(
|
||||
font_system.raw(),
|
||||
Some(text.bounds.width),
|
||||
Some(text.bounds.height),
|
||||
);
|
||||
buffer.set_size(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(
|
||||
font_system.raw(),
|
||||
text.content.iter().enumerate().map(|(i, span)| {
|
||||
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 =
|
||||
text::align(&mut buffer, font_system.raw(), text.align_x);
|
||||
|
||||
|
|
@ -199,11 +188,12 @@ impl core::text::Paragraph for Paragraph {
|
|||
let mut font_system =
|
||||
text::font_system().write().expect("Write font system");
|
||||
|
||||
paragraph.buffer.set_size(
|
||||
font_system.raw(),
|
||||
Some(new_bounds.width),
|
||||
Some(new_bounds.height),
|
||||
);
|
||||
paragraph
|
||||
.buffer
|
||||
.set_size(Some(new_bounds.width), Some(new_bounds.height));
|
||||
paragraph
|
||||
.buffer
|
||||
.shape_until_scroll(font_system.raw(), false);
|
||||
|
||||
let min_bounds = text::align(
|
||||
&mut paragraph.buffer,
|
||||
|
|
|
|||
Loading…
Add table
Add a link
Reference in a new issue