1use super::StateMachine;
5use crate::color::sRGB;
6use crate::editor::Editor;
7use crate::input::{ModifierKeys, MouseButton, MouseState, RawEvent, RawEventKind};
8use crate::layout::{Layout, base, leaf};
9use crate::text::{Change, EditBuffer};
10use crate::{Dispatchable, Error, SourceID, WindowStateMachine, layout};
11use cosmic_text::{Action, Buffer, Cursor};
12use derive_where::derive_where;
13use enum_variant_type::EnumVariantType;
14use feather_macro::Dispatch;
15use smallvec::SmallVec;
16use std::rc::Rc;
17use std::sync::Arc;
18use std::sync::atomic::{AtomicUsize, Ordering};
19use winit::keyboard::{Key, KeyCode, NamedKey, PhysicalKey};
20
21#[derive(Debug, Dispatch, EnumVariantType, Clone, PartialEq, Eq)]
22#[evt(derive(Clone), module = "mouse_area_event")]
23pub enum TextBoxEvent {
24 Edit(SmallVec<[Change; 1]>),
25}
26
27struct TextBoxState {
28 last_x_offset: Option<f32>, history: Vec<SmallVec<[Change; 1]>>,
30 undo_index: usize,
31 insert_mode: bool,
32 text_count: AtomicUsize,
33 cursor_count: AtomicUsize,
34 focused: bool,
35 editor: Editor,
36 props: Rc<dyn Prop + 'static>,
37}
38
39impl TextBoxState {
40 fn translate(e: RawEvent) -> RawEvent {
41 match e {
42 RawEvent::Key {
43 device_id,
44 physical_key: PhysicalKey::Code(key),
45 location,
46 down,
47 logical_key,
48 modifiers,
49 } => {
50 let k = match (key, down, modifiers & ModifierKeys::Control as u8 != 0) {
51 (KeyCode::KeyA, true, true) => Key::Named(NamedKey::Select),
52 (KeyCode::KeyC, true, true) => Key::Named(NamedKey::Copy),
53 (KeyCode::KeyX, true, true) => Key::Named(NamedKey::Cut),
54 (KeyCode::KeyV, true, true) => Key::Named(NamedKey::Paste),
55 (KeyCode::KeyZ, true, true) => Key::Named(NamedKey::Undo),
56 (KeyCode::KeyY, true, true) => Key::Named(NamedKey::Redo),
57 _ => logical_key,
58 };
59 RawEvent::Key {
60 device_id,
61 physical_key: PhysicalKey::Code(key),
62 location,
63 down,
64 logical_key: k,
65 modifiers,
66 }
67 }
68 _ => e,
69 }
70 }
71}
72
73impl super::EventRouter for TextBoxState {
74 type Input = RawEvent;
75 type Output = TextBoxEvent;
76
77 fn process(
78 mut self,
79 input: Self::Input,
80 area: crate::AbsRect,
81 _: crate::AbsRect,
82 dpi: crate::Vec2,
83 driver: &std::sync::Weak<crate::Driver>,
84 ) -> eyre::Result<(Self, SmallVec<[Self::Output; 1]>), (Self, SmallVec<[Self::Output; 1]>)>
85 {
86 let obj = self.props.textedit().obj.clone();
87 let buffer = &mut obj.buffer.borrow_mut();
88 match Self::translate(input) {
89 RawEvent::Focus { acquired, window } => {
90 self.focused = acquired;
91 window.set_ime_allowed(acquired);
92 if acquired {
93 window.set_ime_purpose(winit::window::ImePurpose::Normal);
94 }
96 }
97 RawEvent::Key {
98 down,
99 logical_key,
100 modifiers,
101 ..
102 } => match logical_key {
103 Key::Named(named_key) => {
104 if down {
105 if let Some(driver) = driver.upgrade() {
106 let change = match named_key {
107 NamedKey::Enter => self.editor.action(
108 &mut driver.font_system.write(),
109 buffer,
110 Action::Enter,
111 ),
112 NamedKey::Tab => self.editor.action(
113 &mut driver.font_system.write(),
114 buffer,
115 if (modifiers & ModifierKeys::Shift as u8) != 0 {
116 Action::Unindent
117 } else {
118 Action::Indent
119 },
120 ),
121 NamedKey::Space => self.editor.action(
122 &mut driver.font_system.write(),
123 buffer,
124 Action::Insert(' '),
125 ),
126 NamedKey::ArrowLeft
127 | NamedKey::ArrowRight
128 | NamedKey::ArrowDown
129 | NamedKey::ArrowUp
130 | NamedKey::End
131 | NamedKey::Home
132 | NamedKey::PageDown
133 | NamedKey::PageUp => {
134 let ctrl = (modifiers & ModifierKeys::Control as u8) != 0;
135 let shift = (modifiers & ModifierKeys::Shift as u8) != 0;
136 let font_system = &mut driver.font_system.write();
137 if !shift {
138 match (named_key, ctrl) {
139 (NamedKey::ArrowUp, true)
140 | (NamedKey::ArrowDown, true) => {
141 self.editor.action(
142 font_system,
143 buffer,
144 Action::Scroll {
145 lines: if named_key == NamedKey::ArrowUp {
146 -1
147 } else {
148 1
149 },
150 },
151 );
152 return Ok((self, SmallVec::new()));
153 }
154 _ => (),
155 }
156
157 if let Some((start, end)) =
158 self.editor.selection_bounds(buffer)
159 {
160 if named_key == NamedKey::ArrowLeft {
161 self.editor.set_cursor(buffer, start);
162 } else if named_key == NamedKey::ArrowRight {
163 self.editor.set_cursor(buffer, end);
164 }
165 }
166 self.editor.action(font_system, buffer, Action::Escape);
167 } else if self.editor.selection()
168 == cosmic_text::Selection::None
169 {
170 self.editor.set_selection(
172 buffer,
173 cosmic_text::Selection::Normal(self.editor.cursor()),
174 );
175 }
176 self.editor.action(
177 font_system,
178 buffer,
179 Action::Motion(match (named_key, ctrl) {
180 (NamedKey::ArrowLeft, false) => {
181 cosmic_text::Motion::Previous
182 }
183 (NamedKey::ArrowRight, false) => {
184 cosmic_text::Motion::Next
185 }
186 (NamedKey::ArrowUp, false) => cosmic_text::Motion::Up,
187 (NamedKey::ArrowDown, false) => {
188 cosmic_text::Motion::Down
189 }
190 (NamedKey::Home, false) => cosmic_text::Motion::Home,
191 (NamedKey::End, false) => cosmic_text::Motion::End,
192 (NamedKey::PageUp, false) => {
193 cosmic_text::Motion::PageUp
194 }
195 (NamedKey::PageDown, false) => {
196 cosmic_text::Motion::PageDown
197 }
198 (NamedKey::ArrowLeft, true) => {
199 cosmic_text::Motion::PreviousWord
200 }
201 (NamedKey::ArrowRight, true) => {
202 cosmic_text::Motion::NextWord
203 }
204 (NamedKey::Home, true) => {
205 cosmic_text::Motion::BufferStart
206 }
207 (NamedKey::End, true) => cosmic_text::Motion::BufferEnd,
208 _ => return Ok((self, SmallVec::new())),
209 }),
210 )
211 }
212 NamedKey::Select => {
213 self.editor.set_selection(
215 buffer,
216 cosmic_text::Selection::Normal(Cursor {
217 line: 0,
218 index: 0,
219 affinity: cosmic_text::Affinity::Before,
220 }),
221 );
222 self.editor.action(
223 &mut driver.font_system.write(),
224 buffer,
225 Action::Motion(cosmic_text::Motion::BufferEnd),
226 );
227 SmallVec::new()
228 }
229 NamedKey::Backspace => self.editor.action(
230 &mut driver.font_system.write(),
231 buffer,
232 Action::Backspace,
233 ),
234 NamedKey::Delete => self.editor.action(
235 &mut driver.font_system.write(),
236 buffer,
237 Action::Delete,
238 ),
239 NamedKey::Clear => {
240 let change = self
241 .editor
242 .delete_selection(&mut driver.font_system.write(), buffer)
243 .map(|x| SmallVec::from_buf([x]))
244 .unwrap_or_default();
245 self.editor.shape_as_needed(
246 &mut driver.font_system.write(),
247 buffer,
248 false,
249 );
250 change
251 }
252 NamedKey::EraseEof => {
253 self.editor.set_selection(
254 buffer,
255 cosmic_text::Selection::Normal(self.editor.cursor()),
256 );
257 self.editor.action(
258 &mut driver.font_system.write(),
259 buffer,
260 Action::Motion(cosmic_text::Motion::BufferEnd),
261 );
262 let change = self
263 .editor
264 .delete_selection(&mut driver.font_system.write(), buffer)
265 .map(|x| SmallVec::from_buf([x]))
266 .unwrap_or_default();
267 self.editor.shape_as_needed(
268 &mut driver.font_system.write(),
269 buffer,
270 false,
271 );
272 change
273 }
274 NamedKey::Insert => {
275 self.insert_mode = !self.insert_mode;
276 SmallVec::new()
277 }
278 NamedKey::Cut | NamedKey::Copy => {
279 if modifiers & ModifierKeys::Held as u8 == 0 {
280 if let Some(s) = self.editor.copy_selection(buffer) {
281 if let Ok(mut clipboard) = arboard::Clipboard::new() {
282 if clipboard.set_text(&s).is_ok()
283 && named_key == NamedKey::Cut
284 {
285 if let Some(c) = self.editor.delete_selection(
287 &mut driver.font_system.write(),
288 buffer,
289 ) {
290 self.editor.shape_as_needed(
291 &mut driver.font_system.write(),
292 buffer,
293 false,
294 );
295 self.append(SmallVec::from_buf([c]))
296 }
297 }
298 }
299 }
300 }
301 SmallVec::new()
302 }
303 NamedKey::Paste => {
304 if let Ok(mut clipboard) = arboard::Clipboard::new() {
305 if let Ok(s) = clipboard.get_text() {
306 let c = self.editor.insert_string(
307 &mut driver.font_system.write(),
308 buffer,
309 &s,
310 None,
311 );
312 self.editor.shape_as_needed(
313 &mut driver.font_system.write(),
314 buffer,
315 false,
316 );
317 self.append(SmallVec::from_buf([c]))
318 }
319 }
320 SmallVec::new()
321 }
322 NamedKey::Redo => {
323 if self.undo_index > 0 {
324 self.undo_index =
325 self.redo(&mut driver.font_system.write(), buffer)
326 }
327 SmallVec::new()
328 }
329 NamedKey::Undo => {
330 if self.undo_index > 0 {
331 self.undo_index =
332 self.undo(&mut driver.font_system.write(), buffer)
333 }
334 SmallVec::new()
335 }
336 _ => return Err((self, SmallVec::new())),
338 };
339
340 self.append(change);
341 obj.set_selection(
342 EditBuffer::from_cursor(buffer, self.editor.selection_or_cursor()),
343 EditBuffer::from_cursor(buffer, self.editor.cursor()),
344 );
345 return Ok((self, SmallVec::new()));
346 }
347 }
348 return Ok((self, SmallVec::new()));
350 }
351 Key::Character(c) => {
352 if down {
353 if let Some(driver) = driver.upgrade() {
354 let c = self.editor.insert_string(
355 &mut driver.font_system.write(),
356 buffer,
357 &c,
358 None,
359 );
360 self.append(SmallVec::from_buf([c]));
361
362 self.editor.shape_as_needed(
363 &mut driver.font_system.write(),
364 buffer,
365 false,
366 );
367 obj.set_selection(
368 EditBuffer::from_cursor(buffer, self.editor.selection_or_cursor()),
369 EditBuffer::from_cursor(buffer, self.editor.cursor()),
370 );
371 }
372 return Ok((self, SmallVec::new()));
373 }
374 }
375 _ => (),
376 },
377 RawEvent::MouseMove {
378 pos, all_buttons, ..
379 } => {
380 if let Some(d) = driver.upgrade() {
381 *d.cursor.write() = winit::window::CursorIcon::Text;
382 let p = area.topleft() + self.props.padding().resolve(dpi).topleft();
383
384 if (all_buttons & MouseButton::Left as u16) != 0 {
385 self.editor.action(
386 &mut d.font_system.write(),
387 buffer,
388 Action::Drag {
389 x: (pos.x - p.x).round() as i32,
390 y: (pos.y - p.y).round() as i32,
391 },
392 );
393 }
394 }
395 obj.set_selection(
396 EditBuffer::from_cursor(buffer, self.editor.selection_or_cursor()),
397 EditBuffer::from_cursor(buffer, self.editor.cursor()),
398 );
399 return Ok((self, SmallVec::new()));
400 }
401 RawEvent::Mouse {
402 pos, state, button, ..
403 } => {
404 if let Some(d) = driver.upgrade() {
405 let p = area.topleft() + self.props.padding().resolve(dpi).topleft();
406 let action = match (state, button) {
407 (MouseState::Down, MouseButton::Left) => Action::Click {
408 x: (pos.x - p.x).round() as i32,
409 y: (pos.y - p.y).round() as i32,
410 },
411 (MouseState::DblClick, MouseButton::Left) => Action::DoubleClick {
412 x: (pos.x - p.x).round() as i32,
413 y: (pos.y - p.y).round() as i32,
414 },
415 _ => return Ok((self, SmallVec::new())),
416 };
417 self.editor
418 .action(&mut d.font_system.write(), buffer, action);
419 }
420 obj.set_selection(
421 EditBuffer::from_cursor(buffer, self.editor.selection_or_cursor()),
422 EditBuffer::from_cursor(buffer, self.editor.cursor()),
423 );
424 return Ok((self, SmallVec::new()));
425 }
426 RawEvent::MouseScroll { delta, pixels, .. } => {
427 if let Some(d) = driver.upgrade() {
428 if pixels {
429 let mut scroll = buffer.scroll();
430 scroll.vertical += delta.y;
432 buffer.set_scroll(scroll);
433 } else {
434 self.editor.action(
435 &mut d.font_system.write(),
436 buffer,
437 Action::Scroll {
438 lines: -(delta.y.round() as i32),
439 },
440 );
441 }
442 }
443 }
444 _ => (),
445 }
446 Err((self, SmallVec::new()))
447 }
448}
449
450impl Clone for TextBoxState {
451 fn clone(&self) -> Self {
452 Self {
453 last_x_offset: self.last_x_offset,
454 history: self.history.clone(),
455 undo_index: self.undo_index,
456 insert_mode: self.insert_mode,
457 text_count: self.text_count.load(Ordering::Relaxed).into(),
458 cursor_count: self.cursor_count.load(Ordering::Relaxed).into(),
459 focused: self.focused,
460 editor: self.editor.clone(),
461 props: self.props.clone(),
462 }
463 }
464}
465
466impl PartialEq for TextBoxState {
467 fn eq(&self, other: &Self) -> bool {
468 self.last_x_offset == other.last_x_offset
469 && self.history == other.history
470 && self.undo_index == other.undo_index
471 && self.insert_mode == other.insert_mode
472 && self.text_count.load(Ordering::Relaxed) == other.text_count.load(Ordering::Relaxed)
473 && self.cursor_count.load(Ordering::Relaxed)
474 == other.cursor_count.load(Ordering::Relaxed)
475 && self.editor == other.editor
476 && Rc::ptr_eq(&self.props, &other.props)
477 }
478}
479
480impl TextBoxState {}
481pub trait Prop: leaf::Padded + base::TextEdit {}
482
483#[derive_where(Clone)]
484pub struct TextBox<T: Prop + 'static> {
485 id: Arc<SourceID>,
486 props: Rc<T>,
487 pub font_size: f32,
488 pub line_height: f32,
489 pub font: cosmic_text::FamilyOwned,
490 pub color: sRGB,
491 pub weight: cosmic_text::Weight,
492 pub style: cosmic_text::Style,
493 pub wrap: cosmic_text::Wrap,
494 pub slots: [Option<crate::Slot>; TextBoxEvent::SIZE],
495}
496
497impl TextBoxState {
498 fn redo(&mut self, font_system: &mut cosmic_text::FontSystem, buffer: &mut Buffer) -> usize {
499 if self.undo_index < self.history.len() {
501 self.history[self.undo_index] =
502 self.editor
503 .apply_change(font_system, buffer, &self.history[self.undo_index]);
504 self.undo_index + 1
505 } else {
506 self.undo_index
507 }
508 }
509
510 fn undo(&mut self, font_system: &mut cosmic_text::FontSystem, buffer: &mut Buffer) -> usize {
511 if self.undo_index > 0 {
512 self.history[self.undo_index - 1] =
513 self.editor
514 .apply_change(font_system, buffer, &self.history[self.undo_index - 1]);
515 self.undo_index - 1
516 } else {
517 self.undo_index
518 }
519 }
520
521 fn append(&mut self, change: SmallVec<[Change; 1]>) {
522 self.history.truncate(self.undo_index);
523 self.undo_index += 1;
524 self.history.push(change);
525 }
526}
527
528impl<T: Prop + 'static> TextBox<T> {
529 pub fn new(
530 id: Arc<SourceID>,
531 props: T,
532 font_size: f32,
533 line_height: f32,
534 font: cosmic_text::FamilyOwned,
535 color: sRGB,
536 weight: cosmic_text::Weight,
537 style: cosmic_text::Style,
538 wrap: cosmic_text::Wrap,
539 ) -> Self {
540 Self {
541 id: id.clone(),
542 props: props.into(),
543 font_size,
544 line_height,
545 font,
546 color,
547 weight,
548 style,
549 wrap,
550 slots: [None],
551 }
552 }
553}
554
555impl<T: Prop + 'static> crate::StateMachineChild for TextBox<T> {
556 fn id(&self) -> Arc<SourceID> {
557 self.id.clone()
558 }
559
560 fn init(
561 &self,
562 _: &std::sync::Weak<crate::Driver>,
563 ) -> Result<Box<dyn super::StateMachineWrapper>, Error> {
564 let statemachine = StateMachine {
565 state: Some(TextBoxState {
566 editor: Editor::new(),
567 last_x_offset: Default::default(),
568 history: Default::default(),
569 undo_index: Default::default(),
570 insert_mode: Default::default(),
571 text_count: Default::default(),
572 cursor_count: Default::default(),
573 focused: Default::default(),
574 props: self.props.clone(),
575 }),
576 input_mask: RawEventKind::Focus as u64
577 | RawEventKind::Mouse as u64
578 | RawEventKind::MouseMove as u64
579 | RawEventKind::MouseScroll as u64
580 | RawEventKind::Touch as u64
581 | RawEventKind::Key as u64,
582 output: self.slots.clone(),
583 changed: true,
584 };
585 Ok(Box::new(statemachine))
586 }
587}
588
589impl<T: Prop + 'static> super::Component for TextBox<T> {
590 type Props = T;
591
592 fn layout(
593 &self,
594 manager: &mut crate::StateManager,
595 driver: &crate::graphics::Driver,
596 window: &Arc<SourceID>,
597 ) -> Box<dyn Layout<T>> {
598 let winstate: &WindowStateMachine = manager.get(window).unwrap();
599 let winstate = winstate.state.as_ref().expect("No window state available");
600 let dpi = winstate.dpi;
601 let mut font_system = driver.font_system.write();
602
603 let textstate: &mut StateMachine<TextBoxState, { TextBoxEvent::SIZE }> =
604 manager.get_mut(&self.id).unwrap();
605 let textstate = textstate.state.as_mut().unwrap();
606 textstate.props = self.props.clone();
607
608 if self.props.textedit().obj.reflow.load(Ordering::Acquire) {
609 let attrs = cosmic_text::Attrs::new()
610 .family(self.font.as_family())
611 .color(self.color.into())
612 .weight(self.weight)
613 .style(self.style);
614 self.props.textedit().obj.flowtext(
615 &mut font_system,
616 self.font_size,
617 self.line_height,
618 self.wrap,
619 dpi,
620 attrs,
621 );
622 }
623
624 let instance = crate::render::textbox::Instance {
625 text_buffer: self.props.textedit().obj.buffer.clone(),
626 padding: self.props.padding().resolve(dpi),
627 selection: textstate
628 .editor
629 .selection_bounds(&self.props.textedit().obj.buffer.borrow()),
630 color: self.color,
631 cursor_color: if textstate.focused {
632 self.color
633 } else {
634 sRGB::transparent()
635 },
636 cursor: textstate.editor.cursor(),
637 selection_bg: sRGB::new(0.2, 0.2, 0.5, 1.0),
638 selection_color: self.color,
639 scale: 1.0,
640 };
641
642 Box::new(layout::text::Node::<T> {
643 props: self.props.clone(),
644 id: Arc::downgrade(&self.id),
645 renderable: Rc::new(instance),
646 buffer: self.props.textedit().obj.buffer.clone(),
647 })
648 }
649}