1 let edit_keys : Set<Key> = array@ 2 Key/Z Key/X Key/V Key/Tab Key/Enter Key/NumPadEnter Key/Delete Key/Backspace 3 4 let remove_range (text_box : MultilineTextBox 5 start : TextPos 6 end : TextPos 7 cursor_pos : TextPos) = 8 text_box.is_changed = true 9 let display_text = text_box.display_text 10 let item = HistoryItem/RemoveStr 11 position = start 12 =cursor_pos 13 s = display_text.range start end 14 15 text_box.history.add item 16 display_text.remove_range start end 17 text_box.update_colors start.line 18 if start.line <> end.line && display_text.lines.size > start.line + 1 then 19 display_text.lines[start.line + 1].commented_in = 20 display_text.lines[start.line].commented_out 21 22 text_box.update_colors (start.line + 1) 23 24 let get_first_character_position (text : Text) (position : TextPos) = 25 let line = text.lines[position.line] 26 for i = 0 until line.length do 27 let c = line[i].char 28 if c <> ' ' && c <> '\n' then 29 return TextPos position.line i 30 31 TextPos position.line 0 32 33 let get_line_end_position (text : Text) (position : TextPos) = 34 let line = text.lines[position.line] 35 TextPos position.line (line.length - 1) 36 37 let insert (text_box : MultilineTextBox) (s : String) = 38 assert s.is_not_empty 39 text_box.is_changed = true 40 41 let display_text = text_box.display_text 42 let cursor_pos = text_box.cursor_pos 43 let last_pos : TextPos @late 44 case text_box.selection of 45 None -> 46 last_pos = display_text.insert cursor_pos s 47 let item = HistoryItem/AddStr 48 from = cursor_pos 49 to = last_pos 50 51 text_box.history.add item 52 cursor_pos = last_pos 53 54 Some (start, end) -> 55 let remove_item = HistoryItem/RemoveStr 56 position = start 57 =cursor_pos 58 s = display_text.range start end 59 60 text_box.history.add remove_item 61 display_text.remove_range start end 62 last_pos = display_text.insert start s 63 let add_item = HistoryItem/AddStr 64 from = start 65 to = last_pos 66 67 text_box.history.add add_item 68 selection = None 69 cursor_pos = last_pos 70 71 text_box.update_colors cursor_pos.line (last_pos.line + 1) 72 73 type MultilineTextBox 74 def TextEditControl.process_key (self 75 key : Key 76 action : KeyAction 77 mods : KeyModifiers) = 78 assert self.is_focused 79 if self.is_readonly && edit_keys.contains key then return 80 81 let display_text = self.display_text 82 let scroller = self.scroller 83 let scroll_to (y : u32) = 84 scroller/move_to scroller None -y.as<i32> 85 86 let remove_at (position : TextPos) (cursor_pos : TextPos) = 87 self.is_changed = true 88 let item = HistoryItem/Remove 89 =position 90 =cursor_pos 91 c = display_text[position] 92 93 self.history.add item 94 display_text.remove position 95 self.update_colors position.line 96 97 if action <> KeyAction/Press && action <> KeyAction/Repeat then 98 return 99 100 case key of 101 Key/Backspace -> 102 let selection = self.selection 103 if self.cursor_pos <> TextPos@zero || selection.is_some 104 then 105 case selection of 106 None -> 107 let cursor_pos = self.cursor_pos 108 let remove_pos = display_text.prev cursor_pos 109 if cursor_pos.char == 0 then 110 assert display_text[remove_pos] == '\n' 111 112 remove_at remove_pos cursor_pos 113 self.cursor_pos = remove_pos 114 115 Some (l, r) -> 116 remove_range self l r self.cursor_pos 117 self.selection = None 118 cursor_pos = l 119 120 self.update_offset 121 122 Key/Left 123 | Key/Right 124 | Key/Up 125 | Key/Down 126 | Key/Home 127 | Key/End 128 | Key/PageUp 129 | Key/PageDown -> 130 let prev_cursor_pos = self.cursor_pos 131 let mut cursor_pos = prev_cursor_pos 132 case key of 133 Key/Left -> 134 if self.cursor_pos <> TextPos@zero then 135 cursor_pos = display_text.prev self.cursor_pos 136 self.show cursor_pos 137 138 Key/Right -> 139 if self.cursor_pos < display_text.max then 140 cursor_pos = display_text.next self.cursor_pos 141 self.show cursor_pos 142 143 Key/Up | Key/Down -> 144 let is_up = key == Key/Up 145 if not is_up || cursor_pos.line > 0 then 146 let position = if is_up 147 then cursor_pos.up 148 else cursor_pos.down 149 150 cursor_pos = self.coerce_pos position 151 152 if cursor_pos <> prev_cursor_pos then 153 let (up, down) = text_position_to_y self.text_display 154 cursor_pos 155 let line_height = self.text_display.line_height 156 if scroller.visible_y.down - line_height < down then 157 let y = down + line_height 158 + scroller.visible_y.up 159 - scroller.visible_y.down 160 161 scroll_to y 162 163 else if scroller.visible_y.up + line_height > up then 164 let y = if up > line_height 165 then up - line_height 166 else 0 167 168 scroll_to y 169 170 Key/Home -> 171 cursor_pos = get_first_character_position display_text 172 prev_cursor_pos 173 Key/End -> 174 cursor_pos = get_line_end_position display_text 175 prev_cursor_pos 176 Key/PageUp -> 177 let text_display = self.text_display 178 let (visible_up, visible_down) = scroller.visible_y 179 let n = visible_down - visible_up |> as<f32> 180 / text_display.line_height as f32 181 |> round as u32 182 183 let line = if cursor_pos.line > n 184 then cursor_pos.line - n 185 else 0 186 187 cursor_pos = self.coerce_pos (TextPos line cursor_pos.char) 188 189 let y = if visible_up > visible_down - visible_up then 190 let y = visible_up + visible_up - visible_down 191 192 (y - text_display.margin.up) / text_display.line_height 193 * text_display.line_height 194 else 195 0 196 197 scroll_to y 198 199 Key/PageDown -> 200 let text_display = self.text_display 201 let (visible_up, visible_down) = scroller.visible_y 202 let n = visible_down - visible_up 203 |> as<f32> / text_display.line_height as f32 204 |> round as u32 205 206 let position = TextPos 207 line = cursor_pos.line + n 208 char = cursor_pos.char 209 210 cursor_pos = self.coerce_pos position 211 let y = (visible_down - text_display.margin.up) 212 / text_display.line_height * text_display.line_height 213 214 scroll_to y 215 216 else -> fail 217 218 set_cursor_position self prev_cursor_pos cursor_pos 219 is_mouse_move = false 220 is_move = true 221 222 Key/Delete -> 223 let selection = self.selection 224 if display_text.is_not_empty 225 && (self.cursor_pos < display_text.max || selection.is_some) 226 then 227 let cursor_pos = self.cursor_pos 228 case selection of 229 None -> remove_at cursor_pos cursor_pos 230 Some (l, r) -> 231 remove_range self l r cursor_pos 232 self.selection = None 233 cursor_pos = l 234 235 self.update_offset 236 237 Key/Enter | Key/NumPadEnter -> 238 let cursor_pos = self.cursor_pos 239 let first_character = get_first_character_position display_text 240 cursor_pos 241 let indent = 242 if first_character.char == 0 243 && display_text.lines[cursor_pos.line].is_blank 244 then cursor_pos.char 245 else first_character.char.min cursor_pos.char 246 247 let sb = StringBuilder.new 248 sb.append '\n' 249 for _ = 0 until indent do 250 sb.append ' ' 251 252 insert self sb.as_string 253 self.show self.cursor_pos 254 255 Key/Tab -> insert self " " 256 Key/C if mods.contains KeyModifiers/Control -> 257 let window = self.os_window 258 if self.selection ? Some (start, end) then 259 let selected_text = display_text.range start end 260 window.set_clipboard selected_text 261 262 Key/X if mods.contains KeyModifiers/Control -> 263 let window = self.os_window 264 if self.selection ? Some (start, end) then 265 let selected_text = display_text.range start end 266 window.set_clipboard selected_text 267 remove_range self start end self.cursor_pos 268 self.selection = None 269 cursor_pos = start 270 271 Key/V if mods.contains KeyModifiers/Control -> 272 let window = self.os_window 273 let s = window.get_clipboard 274 if s.is_not_empty then 275 insert self s 276 self.show self.cursor_pos 277 278 Key/Z if mods.contains KeyModifiers/Control -> 279 let history = self.history 280 if history.is_not_empty then 281 if self.selection.is_some then 282 self.clear_selection 283 284 let item = history.last 285 case item of 286 is HistoryItem/Add -> 287 display_text.remove item.position 288 self.cursor_pos = item.position 289 update_colors item.position.line 290 291 is HistoryItem/AddStr -> 292 display_text.remove_range item.from item.to 293 self.cursor_pos = item.from 294 update_colors item.from.line 295 296 if item.from.line <> item.to.line then 297 self.update_colors (item.from.line + 1) 298 299 is HistoryItem/Remove -> 300 display_text.insert item.position item.c 301 self.cursor_pos = item.cursor_pos 302 update_colors item.position.line 303 304 is HistoryItem/RemoveStr -> 305 let last_pos = display_text.insert item.position item.s 306 self.cursor_pos = item.cursor_pos 307 update_colors item.position.line (last_pos.line + 1) 308 selection = (item.position, last_pos) 309 310 history.remove_at (history.size - 1) 311 self.is_changed = true 312 313 else -> () 314 315 def TextEditControl.process_char (c : Char) = 316 assert is_focused 317 if self.is_readonly then return 318 319 is_changed = true 320 if selection ? Some (l, r) then 321 remove_range self l r cursor_pos 322 cursor_pos = l 323 324 let item = HistoryItem/Add 325 position = cursor_pos 326 327 history.add item 328 display_text.insert cursor_pos c 329 set_cursor_position self cursor_pos (display_text.next cursor_pos) 330 is_mouse_move = false 331 is_move = false 332 333 update_colors cursor_pos.line 334