Compare commits

1 Commits

Author SHA1 Message Date
9deba3d9d7 fix wrapping text selection 2025-11-22 18:29:44 -05:00
2 changed files with 68 additions and 46 deletions

View File

@@ -1,7 +1,7 @@
use std::ops::{Deref, DerefMut}; use std::ops::{Deref, DerefMut};
use crate::prelude::*; use crate::prelude::*;
use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, Motion}; use cosmic_text::{Affinity, Attrs, Cursor, FontSystem, LayoutRun, Motion};
use unicode_segmentation::UnicodeSegmentation; use unicode_segmentation::UnicodeSegmentation;
use winit::{ use winit::{
event::KeyEvent, event::KeyEvent,
@@ -53,38 +53,16 @@ impl Widget for TextEdit {
TextSelection::Span { start, end } => { TextSelection::Span { start, end } => {
let (start, end) = sort_cursors(*start, *end); let (start, end) = sort_cursors(*start, *end);
let line_height = self.attrs.line_height; let line_height = self.attrs.line_height;
let mut highlight = |offset, width: f32| { for (top_left, width) in iter_layout_lines(&self.buf, &start, &end) {
painter.primitive_within( painter.primitive_within(
RectPrimitive::color(Color::SKY), RectPrimitive::color(Color::SKY),
vec2(width, line_height) vec2(width, line_height)
.align(Align::TOP_LEFT) .align(Align::TOP_LEFT)
.offset(offset) .offset(top_left)
.within(&region), .within(&region),
); );
}; }
if let (Some(start_offset), Some(end_offset)) = if let Some(end_offset) = cursor_pos(&end, &self.buf) {
(cursor_pos(&start, &self.buf), cursor_pos(&end, &self.buf))
{
let mut iter = self.buf.layout_runs().skip(start.line);
if start.line == end.line {
highlight(start_offset, end_offset.x - start_offset.x);
} else {
let first = iter.next().unwrap();
highlight(start_offset, first.line_w - start_offset.x);
for i in (start.line + 1)..end.line {
let line = iter.next().unwrap();
let offset = vec2(0.0, i as f32 * line_height);
highlight(offset, line.line_w);
}
let offset = vec2(0.0, end.line as f32 * line_height);
highlight(offset, end_offset.x);
}
// painter.primitive_within(
// RectPrimitive::color(Color::WHITE),
// size.align(Align::TOP_LEFT)
// .offset(end_offset)
// .within(&region),
// );
let size = vec2(1, self.attrs.line_height); let size = vec2(1, self.attrs.line_height);
painter.primitive_within( painter.primitive_within(
RectPrimitive::color(Color::WHITE), RectPrimitive::color(Color::WHITE),
@@ -106,7 +84,67 @@ impl Widget for TextEdit {
} }
} }
/// provides top left + width
fn iter_layout_lines(
buf: &TextBuffer,
start: &Cursor,
end: &Cursor,
) -> impl Iterator<Item = (Vec2, f32)> {
gen {
let mut iter = buf.layout_runs();
for line in iter.by_ref() {
if line.line_i == start.line
&& let Some(start_x) = index_x(&line, start.index)
{
if start.line == end.line
&& let Some(end_x) = index_x(&line, end.index)
{
yield (vec2(start_x, line.line_top), end_x - start_x);
return;
}
yield (vec2(start_x, line.line_top), line.line_w - start_x);
break;
}
}
for line in iter {
if line.line_i == end.line
&& let Some(end_x) = index_x(&line, end.index)
{
yield (vec2(0.0, line.line_top), end_x);
return;
}
yield (vec2(0.0, line.line_top), line.line_w);
}
}
}
/// copied & modified from fn found in Editor in cosmic_text /// copied & modified from fn found in Editor in cosmic_text
/// returns x pos of a (non layout) index within an layout run
fn index_x(run: &LayoutRun, index: usize) -> Option<f32> {
for glyph in run.glyphs.iter() {
if index == glyph.start {
return Some(glyph.x);
} else if index > glyph.start && 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 < index {
before += 1;
}
total += 1;
}
let offset = glyph.w * (before as f32) / (total as f32);
return Some(glyph.x + offset);
}
}
None
}
/// returns top of line segment where cursor should visually select
fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<Vec2> { fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<Vec2> {
let mut prev = None; let mut prev = None;
for run in buf for run in buf
@@ -115,25 +153,8 @@ fn cursor_pos(cursor: &Cursor, buf: &TextBuffer) -> Option<Vec2> {
.take_while(|r| r.line_i == cursor.line) .take_while(|r| r.line_i == cursor.line)
{ {
prev = Some(vec2(run.line_w, run.line_top)); prev = Some(vec2(run.line_w, run.line_top));
for glyph in run.glyphs.iter() { if let Some(pos) = index_x(&run, cursor.index) {
if cursor.index == glyph.start { return Some(vec2(pos, run.line_top));
return Some(vec2(glyph.x, run.line_top));
} 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(vec2(glyph.x + offset, run.line_top));
}
} }
} }
prev prev

View File

@@ -8,6 +8,7 @@
#![feature(const_cmp)] #![feature(const_cmp)]
#![feature(const_destruct)] #![feature(const_destruct)]
#![feature(portable_simd)] #![feature(portable_simd)]
#![feature(gen_blocks)]
pub mod core; pub mod core;
pub mod layout; pub mod layout;