@@ -3,6 +3,7 @@ use crate::*;
3
3
/// View-model for `text_editor`.
4
4
struct TextEditorState {
5
5
cursor : usize ,
6
+ selection_start : Option < usize > ,
6
7
glyph_rects : Vec < LocalRect > ,
7
8
lines : Vec < LineMetrics > ,
8
9
}
@@ -21,13 +22,64 @@ impl TextEditorState {
21
22
}
22
23
}
23
24
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
+ }
25
71
self . cursor += 1 ;
26
72
if self . cursor > len {
27
73
self . cursor = len;
28
74
}
29
75
}
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
+ }
31
83
if self . cursor > 0 {
32
84
self . cursor -= 1 ;
33
85
}
@@ -64,7 +116,13 @@ impl TextEditorState {
64
116
closest
65
117
}
66
118
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
+
68
126
let p = self . cursor_pos ( ) ;
69
127
70
128
let line = self . find_line ( ) + 1 ;
@@ -75,7 +133,13 @@ impl TextEditorState {
75
133
}
76
134
}
77
135
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
+
79
143
let p = self . cursor_pos ( ) ;
80
144
81
145
let line = self . find_line ( ) ;
@@ -86,51 +150,79 @@ impl TextEditorState {
86
150
}
87
151
}
88
152
89
- fn key ( & mut self , k : & Key , text : String ) -> String {
153
+ fn key ( & mut self , k : & Key , text : String , shift_pressed : bool ) -> String {
154
+
90
155
match k {
91
156
Key :: ArrowLeft => {
92
- self . back ( ) ;
157
+ self . back ( shift_pressed ) ;
93
158
text
94
159
}
95
160
Key :: ArrowRight => {
96
- self . fwd ( text. len ( ) ) ;
161
+ self . fwd ( text. len ( ) , shift_pressed ) ;
97
162
text
98
163
}
99
164
Key :: ArrowUp => {
100
- self . up ( ) ;
165
+ self . up ( shift_pressed ) ;
101
166
text
102
167
}
103
168
Key :: ArrowDown => {
104
- self . down ( ) ;
169
+ self . down ( shift_pressed ) ;
105
170
text
106
171
}
107
172
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;
110
178
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 ) ;
112
192
t
113
193
} else {
114
- text
194
+ t
115
195
}
116
196
}
117
197
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 ( ) ) ;
120
201
self . cursor += 1 ;
121
202
t
122
203
}
123
204
Key :: Space => {
124
- let mut t = text;
205
+ // Replace selection or insert space
206
+ let mut t = self . delete_selection ( text) ;
125
207
t. insert ( self . cursor , ' ' ) ;
126
208
self . cursor += 1 ;
127
209
t
128
210
}
129
211
Key :: Home => {
212
+ if shift_pressed {
213
+ self . start_selection ( ) ;
214
+ } else {
215
+ self . clear_selection ( ) ;
216
+ }
130
217
self . cursor = 0 ;
131
218
text
132
219
}
133
220
Key :: End => {
221
+ if shift_pressed {
222
+ self . start_selection ( ) ;
223
+ } else {
224
+ self . clear_selection ( ) ;
225
+ }
134
226
self . cursor = text. len ( ) ;
135
227
text
136
228
}
@@ -143,6 +235,7 @@ impl TextEditorState {
143
235
fn new ( ) -> Self {
144
236
Self {
145
237
cursor : 0 ,
238
+ selection_start : None ,
146
239
glyph_rects : vec ! [ ] ,
147
240
lines : vec ! [ ] ,
148
241
}
@@ -166,19 +259,49 @@ pub fn text_editor(text: impl Binding<String>) -> impl View {
166
259
if has_focus {
167
260
let rects = vger. glyph_positions ( text. get ( cx) , font_size, break_width) ;
168
261
let lines = vger. line_metrics ( text. get ( cx) , font_size, break_width) ;
169
- let glyph_rect_paint = vger. color_paint ( vger:: Color :: MAGENTA ) ;
170
262
171
263
cx[ state] . glyph_rects = rects;
172
264
cx[ state] . lines = lines;
173
265
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 ) ;
174
296
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 ) ;
176
298
}
177
299
} )
178
300
. key ( move |cx, k| {
179
301
if has_focus {
180
302
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) ;
182
305
text. with_mut ( cx, |t| * t = new_t) ;
183
306
}
184
307
} )
0 commit comments