diff options
| author | alex-ds13 <145657253+alex-ds13@users.noreply.github.com> | 2025-11-25 15:56:51 +0000 |
|---|---|---|
| committer | Polesznyák Márk <contact@pml68.dev> | 2026-02-05 14:36:21 +0100 |
| commit | cc2b7e3d58a88d8ba40c2dd70ec686108d098f39 (patch) | |
| tree | a6aa726d8efe20fee521a57754d911d42c40c2a9 /src | |
| parent | feat: correct selection on wrapped lines and allow mouse drag out of bounds (diff) | |
| download | iced_selection-cc2b7e3d58a88d8ba40c2dd70ec686108d098f39.tar.gz | |
fix: take visual bounds into consideration when text is centered
Diffstat (limited to 'src')
| -rw-r--r-- | src/text.rs | 60 | ||||
| -rw-r--r-- | src/text/rich.rs | 57 |
2 files changed, 108 insertions, 9 deletions
diff --git a/src/text.rs b/src/text.rs index bef9ed4..204587b 100644 --- a/src/text.rs +++ b/src/text.rs @@ -178,6 +178,7 @@ pub struct State { dragging: Option<Dragging>, last_click: Option<mouse::Click>, keyboard_modifiers: keyboard::Modifiers, + visual_lines_bounds: Vec<core::Rectangle>, } /// The type of dragging selection. @@ -204,10 +205,31 @@ impl State { }; let bounded_y = point.y.max(bounds.y).min(bounds.y + bounds.height); let bounded_point = Point::new(bounded_x, bounded_y); - let relative_point = + let mut relative_point = bounded_point - core::Vector::new(bounds.x, bounds.y); let buffer = self.paragraph.buffer(); + let line_height = buffer.metrics().line_height; + let visual_line = (relative_point.y / line_height).floor() as usize; + let visual_line_start_offset = self + .visual_lines_bounds + .get(visual_line) + .map(|r| r.x) + .unwrap_or_default(); + let visual_line_end = self + .visual_lines_bounds + .get(visual_line) + .map(|r| r.x + r.width) + .unwrap_or_default(); + + if relative_point.x < visual_line_start_offset { + relative_point.x = visual_line_start_offset; + } + + if relative_point.x > visual_line_end { + relative_point.x = visual_line_end; + } + let cursor = buffer.hit(relative_point.x, relative_point.y)?; let value = buffer.lines[cursor.line].text(); @@ -269,17 +291,39 @@ impl State { if self.content != text.content { text.content.clone_into(&mut self.content); self.paragraph = Paragraph::with_text(text); + self.update_visual_bounds(); return; } match self.paragraph.compare(text.with_content(())) { text::Difference::None => {} - text::Difference::Bounds => self.paragraph.resize(text.bounds), + text::Difference::Bounds => { + self.paragraph.resize(text.bounds); + self.update_visual_bounds(); + } text::Difference::Shape => { self.paragraph = Paragraph::with_text(text); + self.update_visual_bounds(); } } } + + fn update_visual_bounds(&mut self) { + let buffer = self.paragraph.buffer(); + let line_height = buffer.metrics().line_height; + self.visual_lines_bounds = buffer + .lines + .iter() + .flat_map(|line| highlight_line(line, 0, line.text().len())) + .enumerate() + .map(|(visual_line, (x, width))| core::Rectangle { + x, + width, + y: visual_line as f32 * line_height - buffer.scroll().vertical, + height: line_height, + }) + .collect(); + } } impl<Message, Theme, Renderer> Widget<Message, Theme, Renderer> @@ -788,10 +832,16 @@ fn highlight_line( let range = start.max(from)..end.min(to); + let x_offset = visual_line + .glyphs + .first() + .map(|glyph| glyph.x) + .unwrap_or_default(); + if range.is_empty() { - (0.0, 0.0) + (x_offset, 0.0) } else if range.start == start && range.end == end { - (0.0, visual_line.w) + (x_offset, visual_line.w) } else { let first_glyph = visual_line .glyphs @@ -809,7 +859,7 @@ fn highlight_line( .map(|glyph| glyph.w) .sum(); - (x, width) + (x_offset + x, width) } }) } diff --git a/src/text/rich.rs b/src/text/rich.rs index 6815a44..e54b64a 100644 --- a/src/text/rich.rs +++ b/src/text/rich.rs @@ -221,6 +221,7 @@ struct State<Link> { dragging: Option<Dragging>, last_click: Option<mouse::Click>, keyboard_modifiers: keyboard::Modifiers, + visual_lines_bounds: Vec<core::Rectangle>, } impl<Link> State<Link> { @@ -238,10 +239,31 @@ impl<Link> State<Link> { }; let bounded_y = point.y.max(bounds.y).min(bounds.y + bounds.height); let bounded_point = Point::new(bounded_x, bounded_y); - let relative_point = + let mut relative_point = bounded_point - core::Vector::new(bounds.x, bounds.y); let buffer = self.paragraph.buffer(); + let line_height = buffer.metrics().line_height; + let visual_line = (relative_point.y / line_height).floor() as usize; + let visual_line_start_offset = self + .visual_lines_bounds + .get(visual_line) + .map(|r| r.x) + .unwrap_or_default(); + let visual_line_end = self + .visual_lines_bounds + .get(visual_line) + .map(|r| r.x + r.width) + .unwrap_or_default(); + + if relative_point.x < visual_line_start_offset { + relative_point.x = visual_line_start_offset; + } + + if relative_point.x > visual_line_end { + relative_point.x = visual_line_end; + } + let cursor = buffer.hit(relative_point.x, relative_point.y)?; let value = buffer.lines[cursor.line].text(); @@ -298,6 +320,23 @@ impl<Link> State<Link> { }) .collect() } + + fn update_visual_bounds(&mut self) { + let buffer = self.paragraph.buffer(); + let line_height = buffer.metrics().line_height; + self.visual_lines_bounds = buffer + .lines + .iter() + .flat_map(|line| highlight_line(line, 0, line.text().len())) + .enumerate() + .map(|(visual_line, (x, width))| core::Rectangle { + x, + width, + y: visual_line as f32 * line_height - buffer.scroll().vertical, + height: line_height, + }) + .collect(); + } } impl<Link, Message, Theme, Renderer> Widget<Message, Theme, Renderer> @@ -322,6 +361,7 @@ where dragging: None, last_click: None, keyboard_modifiers: keyboard::Modifiers::default(), + visual_lines_bounds: Vec::new(), }) } @@ -856,6 +896,7 @@ where state.paragraph = Renderer::Paragraph::with_spans(text_with_spans()); state.spans = spans.iter().cloned().map(Span::to_static).collect(); + state.update_visual_bounds(); } else { match state.paragraph.compare(core::Text { content: (), @@ -871,10 +912,12 @@ where core::text::Difference::None => {} core::text::Difference::Bounds => { state.paragraph.resize(bounds); + state.update_visual_bounds(); } core::text::Difference::Shape => { state.paragraph = Renderer::Paragraph::with_spans(text_with_spans()); + state.update_visual_bounds(); } } } @@ -936,10 +979,16 @@ fn highlight_line( let range = start.max(from)..end.min(to); + let x_offset = visual_line + .glyphs + .first() + .map(|glyph| glyph.x) + .unwrap_or_default(); + if range.is_empty() { - (0.0, 0.0) + (x_offset, 0.0) } else if range.start == start && range.end == end { - (0.0, visual_line.w) + (x_offset, visual_line.w) } else { let first_glyph = visual_line .glyphs @@ -957,7 +1006,7 @@ fn highlight_line( .map(|glyph| glyph.w) .sum(); - (x, width) + (x_offset + x, width) } }) } |
