Skip to content

Commit 0f7a8c9

Browse files
committed
Prepare cargo release
1 parent 65008f2 commit 0f7a8c9

File tree

4 files changed

+174
-7
lines changed

4 files changed

+174
-7
lines changed

Cargo.lock

Lines changed: 1 addition & 1 deletion
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

Cargo.toml

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -32,6 +32,6 @@ tracing = "0.1.40"
3232
feather-ui = { path = "feather-ui" }
3333
alicorn = "0.1.2"
3434
feather-macro = { version = "0.2", path = "feather-macro" }
35-
cosmic-text = { git = "https://github.com/pop-os/cosmic-text" } # TODO: change back to a proper release once fixes we need make it into stable
35+
cosmic-text = { version = "0.14.2", git = "https://github.com/pop-os/cosmic-text", rev = "d15011fba547ae6bd2cbc7591991ff74424c6db7" } # TODO: change back to a proper release once fixes we need make it into stable
3636

3737
[workspace.lints]

feather-ui/src/editor.rs

Lines changed: 171 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -13,6 +13,173 @@ use cosmic_text::{
1313

1414
use crate::text::Change;
1515

16+
// Temporary hack until cosmic-text deploys a new version with this fix: https://github.com/pop-os/cosmic-text/pull/396
17+
#[derive(Debug)]
18+
pub struct FixedRunIter<'b> {
19+
buffer: &'b cosmic_text::Buffer,
20+
line_i: usize,
21+
layout_i: usize,
22+
total_height: f32,
23+
line_top: f32,
24+
}
25+
26+
impl<'b> FixedRunIter<'b> {
27+
pub fn new(buffer: &'b cosmic_text::Buffer) -> Self {
28+
Self {
29+
buffer,
30+
line_i: buffer.scroll().line,
31+
layout_i: 0,
32+
total_height: 0.0,
33+
line_top: 0.0,
34+
}
35+
}
36+
}
37+
38+
impl<'b> Iterator for FixedRunIter<'b> {
39+
type Item = cosmic_text::LayoutRun<'b>;
40+
41+
fn next(&mut self) -> Option<Self::Item> {
42+
while let Some(line) = self.buffer.lines.get(self.line_i) {
43+
let shape = line.shape_opt()?;
44+
let layout = line.layout_opt()?;
45+
while let Some(layout_line) = layout.get(self.layout_i) {
46+
self.layout_i += 1;
47+
48+
let line_height = layout_line
49+
.line_height_opt
50+
.unwrap_or(self.buffer.metrics().line_height);
51+
self.total_height += line_height;
52+
53+
let line_top = self.line_top - self.buffer.scroll().vertical;
54+
let glyph_height = layout_line.max_ascent + layout_line.max_descent;
55+
let centering_offset = (line_height - glyph_height) / 2.0;
56+
let line_y = line_top + centering_offset + layout_line.max_ascent;
57+
if let Some(height) = self.buffer.size().1 {
58+
if line_y - layout_line.max_ascent > height {
59+
return None;
60+
}
61+
}
62+
self.line_top += line_height;
63+
if line_y + layout_line.max_descent < 0.0 {
64+
continue;
65+
}
66+
67+
return Some(cosmic_text::LayoutRun {
68+
line_i: self.line_i,
69+
text: line.text(),
70+
rtl: shape.rtl,
71+
glyphs: &layout_line.glyphs,
72+
line_y,
73+
line_top,
74+
line_height,
75+
line_w: layout_line.w,
76+
});
77+
}
78+
self.line_i += 1;
79+
self.layout_i = 0;
80+
}
81+
82+
None
83+
}
84+
}
85+
86+
// Reimplementation of hit() using our FixedRunIter
87+
fn hit_fixed(buffer: &Buffer, x: f32, y: f32) -> Option<Cursor> {
88+
let mut new_cursor_opt = None;
89+
90+
let mut runs = FixedRunIter::new(buffer).peekable();
91+
let mut first_run = true;
92+
while let Some(run) = runs.next() {
93+
let line_top = run.line_top;
94+
let line_height = run.line_height;
95+
96+
if first_run && y < line_top {
97+
first_run = false;
98+
let new_cursor = Cursor::new(run.line_i, 0);
99+
new_cursor_opt = Some(new_cursor);
100+
} else if y >= line_top && y < line_top + line_height {
101+
let mut new_cursor_glyph = run.glyphs.len();
102+
let mut new_cursor_char = 0;
103+
let mut new_cursor_affinity = cosmic_text::Affinity::After;
104+
105+
let mut first_glyph = true;
106+
107+
'hit: for (glyph_i, glyph) in run.glyphs.iter().enumerate() {
108+
if first_glyph {
109+
first_glyph = false;
110+
if (run.rtl && x > glyph.x) || (!run.rtl && x < 0.0) {
111+
new_cursor_glyph = 0;
112+
new_cursor_char = 0;
113+
}
114+
}
115+
if x >= glyph.x && x <= glyph.x + glyph.w {
116+
new_cursor_glyph = glyph_i;
117+
118+
let cluster = &run.text[glyph.start..glyph.end];
119+
let total = cluster.grapheme_indices(true).count();
120+
let mut egc_x = glyph.x;
121+
let egc_w = glyph.w / (total as f32);
122+
for (egc_i, egc) in cluster.grapheme_indices(true) {
123+
if x >= egc_x && x <= egc_x + egc_w {
124+
new_cursor_char = egc_i;
125+
126+
let right_half = x >= egc_x + egc_w / 2.0;
127+
if right_half != glyph.level.is_rtl() {
128+
// If clicking on last half of glyph, move cursor past glyph
129+
new_cursor_char += egc.len();
130+
new_cursor_affinity = cosmic_text::Affinity::Before;
131+
}
132+
break 'hit;
133+
}
134+
egc_x += egc_w;
135+
}
136+
137+
let right_half = x >= glyph.x + glyph.w / 2.0;
138+
if right_half != glyph.level.is_rtl() {
139+
// If clicking on last half of glyph, move cursor past glyph
140+
new_cursor_char = cluster.len();
141+
new_cursor_affinity = cosmic_text::Affinity::Before;
142+
}
143+
break 'hit;
144+
}
145+
}
146+
147+
let mut new_cursor = Cursor::new(run.line_i, 0);
148+
149+
match run.glyphs.get(new_cursor_glyph) {
150+
Some(glyph) => {
151+
// Position at glyph
152+
new_cursor.index = glyph.start + new_cursor_char;
153+
new_cursor.affinity = new_cursor_affinity;
154+
}
155+
None => {
156+
if let Some(glyph) = run.glyphs.last() {
157+
// Position at end of line
158+
new_cursor.index = glyph.end;
159+
new_cursor.affinity = cosmic_text::Affinity::Before;
160+
}
161+
}
162+
}
163+
164+
new_cursor_opt = Some(new_cursor);
165+
166+
break;
167+
} else if runs.peek().is_none() && y > run.line_y {
168+
let mut new_cursor = Cursor::new(run.line_i, 0);
169+
if let Some(glyph) = run.glyphs.last() {
170+
new_cursor = if run.rtl {
171+
Cursor::new_with_affinity(run.line_i, glyph.start, cosmic_text::Affinity::After)
172+
} else {
173+
Cursor::new_with_affinity(run.line_i, glyph.end, cosmic_text::Affinity::Before)
174+
}
175+
}
176+
new_cursor_opt = Some(new_cursor);
177+
}
178+
}
179+
180+
new_cursor_opt
181+
}
182+
16183
/// A wrapper of [`Buffer`] for easy editing. Modified to use an external Rc<RefCell<Buffer>> reference
17184
/// to work better with our immutable framework
18185
#[derive(Debug, Clone)]
@@ -718,7 +885,7 @@ impl Editor {
718885
Action::Click { x, y } => {
719886
self.set_selection(buffer, Selection::None);
720887

721-
if let Some(new_cursor) = buffer.hit(x as f32, y as f32) {
888+
if let Some(new_cursor) = hit_fixed(buffer, x as f32, y as f32) {
722889
if new_cursor != self.cursor {
723890
self.set_cursor(buffer, new_cursor);
724891
buffer.set_redraw(true);
@@ -729,7 +896,7 @@ impl Editor {
729896
Action::DoubleClick { x, y } => {
730897
self.set_selection(buffer, Selection::None);
731898

732-
if let Some(new_cursor) = buffer.hit(x as f32, y as f32) {
899+
if let Some(new_cursor) = hit_fixed(buffer, x as f32, y as f32) {
733900
if new_cursor != self.cursor {
734901
self.set_cursor(buffer, new_cursor);
735902
buffer.set_redraw(true);
@@ -740,7 +907,7 @@ impl Editor {
740907
SmallVec::new()
741908
}
742909
Action::Drag { x, y } => {
743-
if let Some(new_cursor) = buffer.hit(x as f32, y as f32) {
910+
if let Some(new_cursor) = hit_fixed(buffer, x as f32, y as f32) {
744911
// We do not trigger a selection if only the affinity changes, because it's not visible and ends up being confusing.
745912
if new_cursor.index != self.cursor.index || new_cursor.line != self.cursor.line
746913
{
@@ -757,7 +924,7 @@ impl Editor {
757924
Action::Scroll { lines } => {
758925
let mut scroll = buffer.scroll();
759926
//TODO: align to layout lines
760-
scroll.vertical += lines as f32 * buffer.metrics().line_height;
927+
scroll.vertical += lines as f32 * buffer.metrics().line_height + 0.0001; // temporary hack until cosmic-text deploys a new version with this fix: https://github.com/pop-os/cosmic-text/pull/401
761928
buffer.set_scroll(scroll);
762929
SmallVec::new()
763930
}

feather-ui/src/render/text.rs

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -36,7 +36,7 @@ impl Instance {
3636
context: &mut ScaleContext,
3737
cache_key: CacheKey,
3838
) -> Option<Image> {
39-
let font = match font_system.get_font(cache_key.font_id, cache_key.font_weight) {
39+
let font = match font_system.get_font(cache_key.font_id) {
4040
Some(some) => some,
4141
None => {
4242
debug_assert!(false, "did not find font {:?}", cache_key.font_id);

0 commit comments

Comments
 (0)