Skip to content

Commit a7f62f3

Browse files
committed
#9 work on text_editor selection
1 parent eae6b47 commit a7f62f3

File tree

1 file changed

+142
-19
lines changed

1 file changed

+142
-19
lines changed

src/views/text_editor.rs

Lines changed: 142 additions & 19 deletions
Original file line numberDiff line numberDiff line change
@@ -3,6 +3,7 @@ use crate::*;
33
/// View-model for `text_editor`.
44
struct TextEditorState {
55
cursor: usize,
6+
selection_start: Option<usize>,
67
glyph_rects: Vec<LocalRect>,
78
lines: Vec<LineMetrics>,
89
}
@@ -21,13 +22,64 @@ impl TextEditorState {
2122
}
2223
}
2324

24-
fn fwd(&mut self, len: usize) {
25+
/// Returns the current selection range (start, end) with start <= end
26+
fn selection_range(&self) -> Option<(usize, usize)> {
27+
if let Some(start) = self.selection_start {
28+
let (start, end) = if start <= self.cursor {
29+
(start, self.cursor)
30+
} else {
31+
(self.cursor, start)
32+
};
33+
Some((start, end))
34+
} else {
35+
None
36+
}
37+
}
38+
39+
/// Clears the current selection
40+
fn clear_selection(&mut self) {
41+
self.selection_start = None;
42+
}
43+
44+
/// Starts or extends a selection
45+
fn start_selection(&mut self) {
46+
if self.selection_start.is_none() {
47+
self.selection_start = Some(self.cursor);
48+
}
49+
}
50+
51+
/// Deletes the selected text and returns the new text
52+
fn delete_selection(&mut self, text: String) -> String {
53+
if let Some((start, end)) = self.selection_range() {
54+
if start != end {
55+
let mut t = text;
56+
t.drain(start..end);
57+
self.cursor = start;
58+
self.clear_selection();
59+
return t;
60+
}
61+
}
62+
text
63+
}
64+
65+
fn fwd(&mut self, len: usize, extend_selection: bool) {
66+
if extend_selection {
67+
self.start_selection();
68+
} else {
69+
self.clear_selection();
70+
}
2571
self.cursor += 1;
2672
if self.cursor > len {
2773
self.cursor = len;
2874
}
2975
}
30-
fn back(&mut self) {
76+
77+
fn back(&mut self, extend_selection: bool) {
78+
if extend_selection {
79+
self.start_selection();
80+
} else {
81+
self.clear_selection();
82+
}
3183
if self.cursor > 0 {
3284
self.cursor -= 1;
3385
}
@@ -64,7 +116,13 @@ impl TextEditorState {
64116
closest
65117
}
66118

67-
fn down(&mut self) {
119+
fn down(&mut self, extend_selection: bool) {
120+
if extend_selection {
121+
self.start_selection();
122+
} else {
123+
self.clear_selection();
124+
}
125+
68126
let p = self.cursor_pos();
69127

70128
let line = self.find_line() + 1;
@@ -75,7 +133,13 @@ impl TextEditorState {
75133
}
76134
}
77135

78-
fn up(&mut self) {
136+
fn up(&mut self, extend_selection: bool) {
137+
if extend_selection {
138+
self.start_selection();
139+
} else {
140+
self.clear_selection();
141+
}
142+
79143
let p = self.cursor_pos();
80144

81145
let line = self.find_line();
@@ -86,51 +150,79 @@ impl TextEditorState {
86150
}
87151
}
88152

89-
fn key(&mut self, k: &Key, text: String) -> String {
153+
fn key(&mut self, k: &Key, text: String, shift_pressed: bool) -> String {
154+
90155
match k {
91156
Key::ArrowLeft => {
92-
self.back();
157+
self.back(shift_pressed);
93158
text
94159
}
95160
Key::ArrowRight => {
96-
self.fwd(text.len());
161+
self.fwd(text.len(), shift_pressed);
97162
text
98163
}
99164
Key::ArrowUp => {
100-
self.up();
165+
self.up(shift_pressed);
101166
text
102167
}
103168
Key::ArrowDown => {
104-
self.down();
169+
self.down(shift_pressed);
105170
text
106171
}
107172
Key::Backspace => {
108-
if self.cursor > 0 {
109-
let mut t = text;
173+
// First try to delete selection
174+
let t = self.delete_selection(text);
175+
if self.selection_range().is_none() && self.cursor > 0 {
176+
// No selection was deleted, do normal backspace
177+
let mut t = t;
110178
t.remove(self.cursor - 1);
111-
self.back();
179+
self.back(false);
180+
t
181+
} else {
182+
t
183+
}
184+
}
185+
Key::Delete => {
186+
// Delete selection or character at cursor
187+
let t = self.delete_selection(text);
188+
if self.selection_range().is_none() && self.cursor < t.len() {
189+
// No selection was deleted, delete character at cursor
190+
let mut t = t;
191+
t.remove(self.cursor);
112192
t
113193
} else {
114-
text
194+
t
115195
}
116196
}
117197
Key::Character(c) => {
118-
let mut t = text;
119-
t.insert_str(self.cursor, &format!("{}", c));
198+
// Replace selection or insert character
199+
let mut t = self.delete_selection(text);
200+
t.insert_str(self.cursor, &c.to_string());
120201
self.cursor += 1;
121202
t
122203
}
123204
Key::Space => {
124-
let mut t = text;
205+
// Replace selection or insert space
206+
let mut t = self.delete_selection(text);
125207
t.insert(self.cursor, ' ');
126208
self.cursor += 1;
127209
t
128210
}
129211
Key::Home => {
212+
if shift_pressed {
213+
self.start_selection();
214+
} else {
215+
self.clear_selection();
216+
}
130217
self.cursor = 0;
131218
text
132219
}
133220
Key::End => {
221+
if shift_pressed {
222+
self.start_selection();
223+
} else {
224+
self.clear_selection();
225+
}
134226
self.cursor = text.len();
135227
text
136228
}
@@ -143,6 +235,7 @@ impl TextEditorState {
143235
fn new() -> Self {
144236
Self {
145237
cursor: 0,
238+
selection_start: None,
146239
glyph_rects: vec![],
147240
lines: vec![],
148241
}
@@ -166,19 +259,49 @@ pub fn text_editor(text: impl Binding<String>) -> impl View {
166259
if has_focus {
167260
let rects = vger.glyph_positions(text.get(cx), font_size, break_width);
168261
let lines = vger.line_metrics(text.get(cx), font_size, break_width);
169-
let glyph_rect_paint = vger.color_paint(vger::Color::MAGENTA);
170262

171263
cx[state].glyph_rects = rects;
172264
cx[state].lines = lines;
173265

266+
// Render selection background
267+
if let Some((start, end)) = cx[state].selection_range() {
268+
if start != end {
269+
let selection_paint = vger.color_paint(vger::Color::new(0.3, 0.6, 1.0, 0.3));
270+
271+
for i in start..end.min(cx[state].glyph_rects.len()) {
272+
let rect = cx[state].glyph_rects[i];
273+
vger.fill_rect(
274+
LocalRect::new(rect.origin, [rect.size.width.max(2.0), 20.0].into()),
275+
0.0,
276+
selection_paint
277+
);
278+
}
279+
280+
// Handle selection at end of text
281+
if end >= cx[state].glyph_rects.len() && !cx[state].glyph_rects.is_empty() {
282+
if let Some(last_rect) = cx[state].glyph_rects.last() {
283+
let end_pos = [last_rect.origin.x + last_rect.size.width, last_rect.origin.y];
284+
vger.fill_rect(
285+
LocalRect::new(end_pos.into(), [2.0, 20.0].into()),
286+
0.0,
287+
selection_paint
288+
);
289+
}
290+
}
291+
}
292+
}
293+
294+
// Render cursor
295+
let cursor_paint = vger.color_paint(vger::Color::MAGENTA);
174296
let p = cx[state].cursor_pos();
175-
vger.fill_rect(LocalRect::new(p, [2.0, 20.0].into()), 0.0, glyph_rect_paint);
297+
vger.fill_rect(LocalRect::new(p, [2.0, 20.0].into()), 0.0, cursor_paint);
176298
}
177299
})
178300
.key(move |cx, k| {
179301
if has_focus {
180302
let t = text.with(cx, |t| t.clone());
181-
let new_t = cx[state].key(&k, t);
303+
let shift_pressed = cx.key_mods.shift;
304+
let new_t = cx[state].key(&k, t, shift_pressed);
182305
text.with_mut(cx, |t| *t = new_t);
183306
}
184307
})

0 commit comments

Comments
 (0)