1use {
2 crate::{
3 elements::menu::{MenuItem, MenuPath, MenuStyle},
4 Keyboard as KB, *,
5 },
6 crossterm::event::{KeyEvent, KeyModifiers, MouseButton, MouseEventKind},
7};
8
9#[derive(Clone)]
18pub struct TextBox {
19 pub pane: SelectablePane,
20 pub inner: Rc<RefCell<TextBoxInner>>,
21 pub x_scrollbar: Rc<RefCell<Option<HorizontalScrollbar>>>,
22 pub y_scrollbar: Rc<RefCell<Option<VerticalScrollbar>>>,
23 pub line_number_tb: Rc<RefCell<Option<TextBoxInner>>>,
24}
25
26#[yeehaw_derive::impl_pane_basics_from(pane)]
27impl TextBox {
28 const KIND: &'static str = "textbox";
29 pub fn new<S: Into<String>>(init_ctx: &Context, text: S) -> Self {
30 let text = text.into();
31 let s = Size::get_text_size(&text);
32 let pane = SelectablePane::new(init_ctx, Self::KIND)
33 .with_dyn_width(DynVal::new_fixed(s.width as i32))
34 .with_dyn_height(DynVal::new_fixed(s.height as i32))
35 .with_styles(TextBoxInner::STYLE);
36 let inner = TextBoxInner::new(init_ctx, text);
37
38 pane.pane.add_element(Box::new(inner.clone()));
39 let tb = TextBox {
40 pane,
41 inner: Rc::new(RefCell::new(inner)),
42 x_scrollbar: Rc::new(RefCell::new(None)),
43 y_scrollbar: Rc::new(RefCell::new(None)),
44 line_number_tb: Rc::new(RefCell::new(None)),
45 };
46
47 *tb.inner.borrow().current_sty.borrow_mut() = tb.pane.get_current_style();
48
49 let tb_ = tb.clone();
50
51 tb.pane
52 .set_post_hook_for_set_selectability(Box::new(move |_, _| {
53 tb_.post_hook_for_set_selectability();
54 }));
55
56 tb
57 }
58
59 pub fn post_hook_for_set_selectability(&self) {
60 let sel = self.pane.get_selectability();
61 *self.inner.borrow().selectedness.borrow_mut() = sel;
62 *self.inner.borrow().current_sty.borrow_mut() = self.pane.get_current_style();
63 *self.inner.borrow().is_dirty.borrow_mut() = true;
64 if sel != Selectability::Selected {
65 *self.inner.borrow().visual_mode.borrow_mut() = false;
66 }
67 }
68
69 pub fn set_dirty(&self) {
70 *self.inner.borrow().is_dirty.borrow_mut() = true;
71 }
72
73 pub fn with_scrollbars(self, init_ctx: &Context) -> Self {
74 self.set_x_scrollbar_inner(init_ctx, HorizontalSBPositions::Below);
75 self.set_y_scrollbar_inner(init_ctx, VerticalSBPositions::ToTheRight);
76 self
77 }
78
79 pub fn with_left_scrollbar(self, init_ctx: &Context) -> Self {
80 self.set_y_scrollbar_inner(init_ctx, VerticalSBPositions::ToTheLeft);
81 self
82 }
83
84 pub fn with_right_scrollbar(self, init_ctx: &Context) -> Self {
85 self.set_y_scrollbar_inner(init_ctx, VerticalSBPositions::ToTheRight);
86 self
87 }
88
89 fn set_y_scrollbar_inner(&self, init_ctx: &Context, pos: VerticalSBPositions) {
90 let content_height = self.inner.borrow().pane.content_height();
91 let content_size = self.inner.borrow().pane.content_size();
92
93 let inner_start_y = self.inner.borrow().pane.get_dyn_start_y();
95
96 let sb = VerticalScrollbar::new(init_ctx, DynVal::FULL, content_size, content_height)
97 .without_keyboard_events();
98 if self.x_scrollbar.borrow().is_some() {
99 sb.pane.set_end_y(DynVal::FULL.minus_fixed(1));
100 }
101 match pos {
102 VerticalSBPositions::ToTheLeft => {
103 sb.set_at(0.into(), inner_start_y);
104 if let Some(x_sb) = &*self.x_scrollbar.borrow() {
105 x_sb.pane.set_start_x(1);
106 }
107 self.inner.borrow().pane.set_start_x(1);
108 }
109 VerticalSBPositions::ToTheRight => {
110 sb.set_at(DynVal::FULL.minus_fixed(1), inner_start_y);
111 self.inner
112 .borrow()
113 .pane
114 .set_end_x(DynVal::FULL.minus_fixed(1));
115 if let Some(x_sb) = &*self.x_scrollbar.borrow() {
116 x_sb.pane.set_end_x(DynVal::FULL.minus_fixed(1));
117 }
118 }
119 VerticalSBPositions::None => {
120 return;
121 }
122 }
123 let size = Size::default();
124 sb.set_scrollable_view_size(size);
125 if let Some(x_sb) = &*self.x_scrollbar.borrow() {
126 x_sb.set_scrollable_view_size(size);
127 }
128
129 let pane_ = self.inner.borrow().pane.clone();
131 let is_dirty = self.inner.borrow().is_dirty.clone();
132 let hook = Box::new(move |_, y| {
133 pane_.set_content_y_offset(None, y);
134 is_dirty.replace(true);
135 });
136 *sb.position_changed_hook.borrow_mut() = Some(hook);
137 *self.y_scrollbar.borrow_mut() = Some(sb.clone());
138 self.pane.pane.add_element(Box::new(sb.clone()));
139 self.inner.borrow().y_scrollbar.replace(Some(sb));
140
141 if let VerticalSBPositions::ToTheLeft = pos {
142 self.reset_line_numbers(init_ctx);
143 }
144 self.set_corner_decor(init_ctx);
145 self.reset_sb_sizes(init_ctx);
146 }
147
148 pub fn with_top_scrollbar(self, init_ctx: &Context) -> Self {
149 self.set_x_scrollbar_inner(init_ctx, HorizontalSBPositions::Above);
150 self
151 }
152
153 pub fn with_bottom_scrollbar(self, init_ctx: &Context) -> Self {
154 self.set_x_scrollbar_inner(init_ctx, HorizontalSBPositions::Below);
155 self
156 }
157
158 fn set_x_scrollbar_inner(&self, init_ctx: &Context, pos: HorizontalSBPositions) {
159 let content_width = self.inner.borrow().pane.content_width();
160 let content_size = self.inner.borrow().pane.content_size();
161
162 let inner_start_x = if let Some(ln_tb) = &*self.inner.borrow().line_number_tb.borrow() {
164 ln_tb.pane.get_dyn_start_x()
165 } else {
166 self.inner.borrow().pane.get_dyn_start_x()
167 };
168
169 let sb = HorizontalScrollbar::new(init_ctx, DynVal::FULL, content_size, content_width)
170 .without_keyboard_events();
171 if self.x_scrollbar.borrow().is_some() {
172 sb.pane.set_end_y(DynVal::FULL.minus_fixed(1));
173 }
174 match pos {
175 HorizontalSBPositions::Above => {
176 sb.set_at(inner_start_x, 0.into());
177 self.inner.borrow().pane.set_start_y(1);
178 if let Some(ln_tb) = &*self.inner.borrow().line_number_tb.borrow() {
179 ln_tb.pane.set_start_y(1);
180 }
181 if let Some(y_sb) = &*self.y_scrollbar.borrow() {
182 y_sb.pane.set_start_y(1);
183 }
184 }
185 HorizontalSBPositions::Below => {
186 sb.set_at(inner_start_x, DynVal::FULL.minus_fixed(1));
187 self.inner
188 .borrow()
189 .pane
190 .set_end_y(DynVal::FULL.minus_fixed(1));
191 if let Some(ln_tb) = &*self.inner.borrow().line_number_tb.borrow() {
192 ln_tb.pane.set_end_y(DynVal::FULL.minus_fixed(1));
193 }
194 if let Some(y_sb) = &*self.y_scrollbar.borrow() {
195 y_sb.pane.set_end_y(DynVal::FULL.minus_fixed(1));
196 }
197 }
198 HorizontalSBPositions::None => {
199 return;
200 }
201 }
202
203 let pane_ = self.inner.borrow().pane.clone();
205 let is_dirty = self.inner.borrow().is_dirty.clone();
206 let hook = Box::new(move |_, x| {
207 pane_.set_content_x_offset(None, x);
208 is_dirty.replace(true);
209 });
210 *sb.position_changed_hook.borrow_mut() = Some(hook);
211 *self.x_scrollbar.borrow_mut() = Some(sb.clone());
212 self.pane.pane.add_element(Box::new(sb.clone()));
213 self.inner.borrow().x_scrollbar.replace(Some(sb));
214 self.set_corner_decor(init_ctx);
215 self.reset_sb_sizes(init_ctx);
216 }
217
218 pub fn reset_sb_sizes(&self, _init_ctx: &Context) {
219 let size = Size::default();
220 if let Some(y_sb) = &*self.y_scrollbar.borrow() {
221 y_sb.set_scrollable_view_size(size);
222 *y_sb.scrollable_view_chs.borrow_mut() = DynVal::new_fixed(size.height as i32);
223 }
224 if let Some(x_sb) = &*self.x_scrollbar.borrow() {
225 x_sb.set_scrollable_view_size(size);
226 *x_sb.scrollable_view_chs.borrow_mut() = DynVal::new_fixed(size.width as i32);
227 }
228 self.set_dirty();
229 }
230
231 pub fn with_line_numbers(self, init_ctx: &Context) -> Self {
232 self.set_line_numbers(init_ctx);
233 self
234 }
235
236 pub fn reset_line_numbers(&self, init_ctx: &Context) {
237 let ln_id = self
238 .inner
239 .borrow()
240 .line_number_tb
241 .borrow()
242 .as_ref()
243 .map(|ln_tb| ln_tb.pane.id());
244 if let Some(ln_id) = ln_id {
245 self.pane.pane.remove_element(&ln_id);
246 self.set_line_numbers(init_ctx);
247 }
248 }
249
250 pub fn set_line_numbers(&self, init_ctx: &Context) {
251 let start_x = self.inner.borrow().pane.get_dyn_start_x();
252 let start_y = self.inner.borrow().pane.get_dyn_start_y();
253 let end_y = self.inner.borrow().pane.get_dyn_end_y();
254
255 let (lns, lnw) = self.inner.borrow().get_line_numbers(None);
257
258 let ln_tb = TextBoxInner::new(init_ctx, lns)
260 .at(start_x.clone(), start_y)
261 .with_dyn_width(DynVal::new_fixed(lnw as i32))
262 .with_no_wordwrap()
263 .non_editable(init_ctx)
264 .with_no_ch_cursor()
265 .non_navigable();
266
267 ln_tb.pane.set_end_y(end_y);
268
269 *ln_tb.current_sty.borrow_mut() = TextBoxInner::LINE_NUMBERS_STYLE;
271
272 *ln_tb.selectedness.borrow_mut() = Selectability::Unselectable;
273 self.pane.pane.add_element(Box::new(ln_tb.clone()));
274
275 let new_inner_start_x = start_x.plus_fixed(lnw as i32);
276
277 self.inner.borrow().pane.set_start_x(new_inner_start_x);
279 *self.inner.borrow().line_number_tb.borrow_mut() = Some(ln_tb.clone());
280 self.reset_sb_sizes(init_ctx);
281 }
282
283 pub fn set_corner_decor(&self, init_ctx: &Context) {
284 if let (Some(x_sb), Some(y_sb)) = (&*self.x_scrollbar.borrow(), &*self.y_scrollbar.borrow())
286 {
287 let corner_decor = self.inner.borrow().corner_decor.borrow().clone();
288 let cd = Label::new(init_ctx, &(corner_decor.ch.to_string()))
289 .with_style(corner_decor.style.clone());
290
291 let cd_y = x_sb.pane.get_dyn_start_y();
292 let cd_x = y_sb.pane.get_dyn_start_x();
293 let cd = cd.at(cd_x, cd_y);
294 self.pane.pane.add_element(Box::new(cd));
295 }
296 }
297
298 pub fn with_right_click_menu(self, rcm: Option<RightClickMenu>) -> Self {
306 *self.inner.borrow().right_click_menu.borrow_mut() = rcm;
307 self
308 }
309
310 pub fn with_text_when_empty<S: Into<String>>(self, text: S) -> Self {
311 *self.inner.borrow().text_when_empty.borrow_mut() = text.into();
312 self.set_dirty();
313 self
314 }
315
316 pub fn set_text_when_empty(&self, text: String) {
317 *self.inner.borrow().text_when_empty.borrow_mut() = text;
318 self.set_dirty();
319 }
320
321 pub fn with_text_when_empty_fg(self, fg: Color) -> Self {
322 *self.inner.borrow().text_when_empty_fg.borrow_mut() = fg;
323 self.set_dirty();
324 self
325 }
326
327 pub fn with_styles(self, styles: SelStyles) -> Self {
328 let curr_sty = self.pane.get_current_style();
329 if let Some(ln_tb) = &*self.inner.borrow().line_number_tb.borrow() {
330 *ln_tb.current_sty.borrow_mut() = curr_sty;
331 }
332 *self.inner.borrow().current_sty.borrow_mut() = self.pane.get_current_style();
333 self.pane.set_styles(styles);
334 self.set_dirty();
335 self
336 }
337
338 pub fn with_dyn_width(self, width: DynVal) -> Self {
339 self.pane.set_dyn_width(width);
340 self.set_dirty();
341 self
342 }
343
344 pub fn with_dyn_height(self, height: DynVal) -> Self {
345 self.pane.set_dyn_height(height);
346 self.set_dirty();
347 self
348 }
349
350 pub fn at<D: Into<DynVal>, D2: Into<DynVal>>(self, loc_x: D, loc_y: D2) -> Self {
351 self.pane.set_at(loc_x.into(), loc_y.into());
352 self.set_dirty();
353 self
354 }
355
356 pub fn set_at(&self, loc_x: DynVal, loc_y: DynVal) {
357 self.pane.set_at(loc_x, loc_y);
358 self.set_dirty();
359 }
360
361 pub fn with_ch_cursor(self) -> Self {
362 *self.inner.borrow().ch_cursor.borrow_mut() = true;
363 self.set_dirty();
364 self
365 }
366
367 pub fn with_no_ch_cursor(self) -> Self {
368 *self.inner.borrow().ch_cursor.borrow_mut() = false;
369 self.set_dirty();
370 self
371 }
372
373 pub fn editable(self, init_ctx: &Context) -> Self {
374 self.inner.borrow().set_editable(init_ctx);
375 self.reset_sb_sizes(init_ctx);
376 self
377 }
378
379 pub fn non_editable(self, init_ctx: &Context) -> Self {
380 self.inner.borrow().set_non_editable(init_ctx);
381 self.reset_sb_sizes(init_ctx);
382 self
383 }
384
385 pub fn with_wordwrap(self, init_ctx: &Context) -> Self {
386 *self.inner.borrow().wordwrap.borrow_mut() = true;
387 self.reset_sb_sizes(init_ctx);
388 self
389 }
390
391 pub fn with_no_wordwrap(self, init_ctx: &Context) -> Self {
392 *self.inner.borrow().wordwrap.borrow_mut() = false;
393 self.reset_sb_sizes(init_ctx);
394 self
395 }
396
397 pub fn with_position_style_hook(
398 self, hook: Box<dyn FnMut(Context, usize, Style) -> Style>,
399 ) -> Self {
400 *self.inner.borrow().position_style_hook.borrow_mut() = Some(hook);
401 self.set_dirty();
402 self
403 }
404
405 pub fn set_position_style_hook(
406 &mut self, hook: Box<dyn FnMut(Context, usize, Style) -> Style>,
407 ) {
408 *self.inner.borrow().position_style_hook.borrow_mut() = Some(hook);
409 }
410
411 pub fn with_cursor_changed_hook(self, hook: CursorChangedHook) -> Self {
412 *self.inner.borrow().cursor_changed_hook.borrow_mut() = Some(hook);
413 self
414 }
415
416 pub fn set_cursor_changed_hook(&mut self, hook: CursorChangedHook) {
417 *self.inner.borrow().cursor_changed_hook.borrow_mut() = Some(hook);
418 }
419
420 pub fn with_text_changed_hook(
421 self, hook: Box<dyn FnMut(Context, String) -> EventResponses>,
422 ) -> Self {
423 *self.inner.borrow().text_changed_hook.borrow_mut() = Some(hook);
424 self
425 }
426
427 pub fn set_text_changed_hook(
428 &mut self, hook: Box<dyn FnMut(Context, String) -> EventResponses>,
429 ) {
430 *self.inner.borrow().text_changed_hook.borrow_mut() = Some(hook);
431 }
432
433 pub fn with_cursor_style(self, style: Style) -> Self {
434 *self.inner.borrow().cursor_style.borrow_mut() = style;
435 self.set_dirty();
436 self
437 }
438
439 pub fn with_corner_decor(self, decor: DrawCh) -> Self {
440 *self.inner.borrow().corner_decor.borrow_mut() = decor;
441 self.set_dirty();
442 self
443 }
444
445 pub fn get_text(&self) -> String {
446 self.inner.borrow().get_text()
447 }
448
449 pub fn set_text(&self, text: String) {
450 self.inner.borrow().set_text(text);
451 self.set_dirty();
452 }
453
454 pub fn set_cursor_pos(&self, pos: usize) {
455 self.inner.borrow().set_cursor_pos(pos);
456 self.set_dirty();
457 }
458}
459
460#[yeehaw_derive::impl_element_from(pane)]
461impl Element for TextBox {
462 fn receive_event(&self, ctx: &Context, ev: Event) -> (bool, EventResponses) {
463 if self.pane.get_selectability() == Selectability::Unselectable {
464 return (false, EventResponses::default());
465 }
466 self.pane.receive_event(ctx, ev)
467 }
468}
469
470#[allow(clippy::type_complexity)]
471#[derive(Clone)]
472pub struct TextBoxInner {
473 pub pane: Pane,
474 pub current_sty: Rc<RefCell<Style>>,
475 pub selectedness: Rc<RefCell<Selectability>>,
476
477 pub is_dirty: Rc<RefCell<bool>>,
479
480 pub last_size: Rc<RefCell<Size>>,
482
483 pub last_size_for_sbs: Rc<RefCell<Size>>,
485
486 pub text: Rc<RefCell<Vec<char>>>,
487 pub text_when_empty: Rc<RefCell<String>>,
489 pub text_when_empty_fg: Rc<RefCell<Color>>,
490
491 pub ch_cursor: Rc<RefCell<bool>>,
493 pub editable: Rc<RefCell<bool>>,
495 pub wordwrap: Rc<RefCell<bool>>,
497 pub cursor_pos: Rc<RefCell<usize>>,
499 pub cursor_style: Rc<RefCell<Style>>,
501 pub visual_mode: Rc<RefCell<bool>>,
503 pub mouse_dragging: Rc<RefCell<bool>>,
505 pub visual_mode_start_pos: Rc<RefCell<usize>>,
506
507 pub text_changed_hook: Rc<RefCell<Option<Box<dyn FnMut(Context, String) -> EventResponses>>>>,
509
510 pub position_style_hook: Rc<RefCell<Option<Box<dyn FnMut(Context, usize, Style) -> Style>>>>,
515
516 pub cursor_changed_hook: Rc<RefCell<Option<CursorChangedHook>>>,
518
519 pub x_scrollbar: Rc<RefCell<Option<HorizontalScrollbar>>>,
520 pub y_scrollbar: Rc<RefCell<Option<VerticalScrollbar>>>,
521 pub line_number_tb: Rc<RefCell<Option<TextBoxInner>>>,
522
523 pub corner_decor: Rc<RefCell<DrawCh>>,
525 pub right_click_menu: Rc<RefCell<Option<RightClickMenu>>>,
526}
527
528pub type CursorChangedHook = Box<dyn FnMut(usize) -> EventResponses>;
530
531#[yeehaw_derive::impl_pane_basics_from(pane)]
532impl TextBoxInner {
533 const KIND: &'static str = "textbox_inner";
534
535 const STYLE: SelStyles = SelStyles {
536 selected_style: Style::new_const(Color::BLACK, Color::WHITE),
537 ready_style: Style::new_const(Color::BLACK, Color::GREY17),
538 unselectable_style: Style::new_const(Color::BLACK, Color::GREY15),
539 };
540
541 const LINE_NUMBERS_STYLE: Style = Style::new_const(Color::BLACK, Color::GREY13);
542
543 const DEFAULT_CURSOR_STYLE: Style = Style::new_const(Color::WHITE, Color::BLUE);
544
545 pub fn editable_receivable_events() -> ReceivableEvents {
547 ReceivableEvents(vec![
548 (KeyPossibility::Chars.into()),
549 (KB::KEY_BACKSPACE.into()),
550 (KB::KEY_ENTER.into()),
551 (KB::KEY_SHIFT_ENTER.into()),
552 (KB::KEY_LEFT.into()),
553 (KB::KEY_RIGHT.into()),
554 (KB::KEY_UP.into()),
555 (KB::KEY_DOWN.into()),
556 (KB::KEY_SHIFT_LEFT.into()),
557 (KB::KEY_SHIFT_RIGHT.into()),
558 (KB::KEY_SHIFT_UP.into()),
559 (KB::KEY_SHIFT_DOWN.into()),
560 ])
561 }
562
563 pub fn non_editable_receivable_events() -> ReceivableEvents {
565 ReceivableEvents(vec![
566 (KB::KEY_LEFT.into()),
567 (KB::KEY_RIGHT.into()),
568 (KB::KEY_UP.into()),
569 (KB::KEY_DOWN.into()),
570 (KB::KEY_H.into()),
571 (KB::KEY_J.into()),
572 (KB::KEY_K.into()),
573 (KB::KEY_L.into()),
574 ])
575 }
576
577 pub fn new<S: Into<String>>(ctx: &Context, text: S) -> Self {
578 let text = text.into();
579 let pane = Pane::new(ctx, Self::KIND)
580 .with_dyn_width(DynVal::FULL)
581 .with_dyn_height(DynVal::FULL)
582 .with_focused_receivable_events(Self::editable_receivable_events())
583 .with_focused(true);
584
585 let tb = TextBoxInner {
586 pane,
587 current_sty: Rc::new(RefCell::new(Style::default())),
588 selectedness: Rc::new(RefCell::new(Selectability::Ready)),
589 is_dirty: Rc::new(RefCell::new(true)),
590 last_size: Rc::new(RefCell::new(Size::default())),
591 last_size_for_sbs: Rc::new(RefCell::new(Size::default())),
592 text: Rc::new(RefCell::new(text.chars().collect())),
593 text_when_empty: Rc::new(RefCell::new("enter text...".to_string())),
594 text_when_empty_fg: Rc::new(RefCell::new(Color::GREY6)),
595 wordwrap: Rc::new(RefCell::new(true)),
596 ch_cursor: Rc::new(RefCell::new(true)),
597 editable: Rc::new(RefCell::new(true)),
598 cursor_pos: Rc::new(RefCell::new(0)),
599 cursor_style: Rc::new(RefCell::new(Self::DEFAULT_CURSOR_STYLE)),
600 visual_mode: Rc::new(RefCell::new(false)),
601 mouse_dragging: Rc::new(RefCell::new(false)),
602 visual_mode_start_pos: Rc::new(RefCell::new(0)),
603 text_changed_hook: Rc::new(RefCell::new(None)),
604 position_style_hook: Rc::new(RefCell::new(None)),
605 cursor_changed_hook: Rc::new(RefCell::new(None)),
606 x_scrollbar: Rc::new(RefCell::new(None)),
607 y_scrollbar: Rc::new(RefCell::new(None)),
608 line_number_tb: Rc::new(RefCell::new(None)),
609 corner_decor: Rc::new(RefCell::new(DrawCh::new('⁙', Style::default_const()))),
610 right_click_menu: Rc::new(RefCell::new(None)),
611 };
612 tb.set_editable_right_click_menu(ctx);
613 tb
614 }
615
616 pub fn set_editable_right_click_menu(&self, ctx: &Context) {
617 let (tb1, tb2, tb3) = (self.clone(), self.clone(), self.clone());
618 let rcm = RightClickMenu::new(ctx, MenuStyle::default()).with_menu_items(
619 ctx,
620 vec![
621 MenuItem::new(ctx, MenuPath("Cut".to_string())).with_fn(Some(Box::new(
622 move |ctx| {
623 tb1.is_dirty.replace(true);
624 tb1.cut_to_clipboard(&ctx)
625 },
626 ))),
627 MenuItem::new(ctx, MenuPath("Copy".to_string())).with_fn(Some(Box::new(
628 move |_ctx| {
629 tb2.is_dirty.replace(true);
630 tb2.copy_to_clipboard();
631 EventResponses::default()
632 },
633 ))),
634 MenuItem::new(ctx, MenuPath("Paste".to_string())).with_fn(Some(Box::new(
635 move |ctx| {
636 tb3.is_dirty.replace(true);
637 tb3.paste_from_clipboard(&ctx)
638 },
639 ))),
640 ],
641 );
642 *self.right_click_menu.borrow_mut() = Some(rcm);
643 }
644
645 pub fn set_non_editable_right_click_menu(&self, ctx: &Context) {
646 let tb = self.clone();
647 let rcm = RightClickMenu::new(ctx, MenuStyle::default()).with_menu_items(
648 ctx,
649 vec![
650 MenuItem::new(ctx, MenuPath("Copy".to_string())).with_fn(Some(Box::new(
651 move |_ctx| {
652 tb.is_dirty.replace(true);
653 tb.copy_to_clipboard();
654 EventResponses::default()
655 },
656 ))),
657 ],
658 );
659 *self.right_click_menu.borrow_mut() = Some(rcm);
660 }
661
662 pub fn at<D: Into<DynVal>, D2: Into<DynVal>>(self, loc_x: D, loc_y: D2) -> Self {
663 self.pane.set_at(loc_x.into(), loc_y.into());
664 self
665 }
666
667 pub fn with_no_wordwrap(self) -> Self {
668 *self.wordwrap.borrow_mut() = false;
669 self
670 }
671
672 pub fn with_wordwrap(self) -> Self {
673 *self.wordwrap.borrow_mut() = true;
674 self
675 }
676
677 pub fn with_ch_cursor(self) -> Self {
678 *self.ch_cursor.borrow_mut() = true;
679 self
680 }
681
682 pub fn with_no_ch_cursor(self) -> Self {
683 *self.ch_cursor.borrow_mut() = false;
684 self
685 }
686
687 pub fn set_editable(&self, ctx: &Context) {
688 *self.editable.borrow_mut() = true;
689 self.pane
690 .set_focused_receivable_events(TextBoxInner::editable_receivable_events());
691 self.set_editable_right_click_menu(ctx);
692 }
693
694 pub fn set_non_editable(&self, ctx: &Context) {
695 *self.editable.borrow_mut() = false;
696 self.pane
697 .set_focused_receivable_events(TextBoxInner::non_editable_receivable_events());
698 self.set_non_editable_right_click_menu(ctx);
699 }
700
701 pub fn editable(self, ctx: &Context) -> Self {
702 self.set_editable(ctx);
703 self
704 }
705
706 pub fn non_editable(self, ctx: &Context) -> Self {
707 self.set_non_editable(ctx);
708 self
709 }
710
711 pub fn non_navigable(self) -> Self {
712 self.pane
713 .set_focused_receivable_events(ReceivableEvents(vec![]));
714 self
715 }
716
717 pub fn get_text(&self) -> String {
718 self.text.borrow().iter().collect()
719 }
720
721 pub fn set_text(&self, text: String) {
722 *self.text.borrow_mut() = text.chars().collect();
723 self.is_dirty.replace(true);
724 }
725
726 pub fn get_cursor_pos(&self) -> usize {
729 let cur_pos = *self.cursor_pos.borrow();
730 if cur_pos > self.text.borrow().len() {
733 self.text.borrow().len()
734 } else {
735 cur_pos
736 }
737 }
738
739 pub fn get_visual_mode_start_pos(&self) -> usize {
740 let pos = *self.visual_mode_start_pos.borrow();
741 if pos >= self.text.borrow().len() {
742 self.text.borrow().len() - 1
743 } else {
744 pos
745 }
746 }
747
748 pub fn set_cursor_pos(&self, new_abs_pos: usize) -> EventResponses {
749 *self.cursor_pos.borrow_mut() = new_abs_pos;
750 if let Some(hook) = &mut *self.cursor_changed_hook.borrow_mut() {
751 hook(new_abs_pos)
752 } else {
753 EventResponses::default()
754 }
755 }
756
757 pub fn incr_cursor_pos(&self, pos_change: isize) -> EventResponses {
758 let new_pos = (self.get_cursor_pos() as isize + pos_change).max(0) as usize;
759 self.set_cursor_pos(new_pos)
760 }
761
762 pub fn get_wrapped(&self, draw_size: Option<Size>) -> WrChs {
764 let width = draw_size
765 .map(|s| s.width)
766 .unwrap_or(self.get_last_size().width) as usize;
767
768 let mut rs = self.text.borrow().clone();
769 rs.push(' '); let mut chs = vec![];
771 let mut max_x = 0;
772 let (mut x, mut y) = (0, 0); for (abs_pos, r) in rs.iter().enumerate() {
774 if *self.wordwrap.borrow() && x == width {
775 y += 1;
776 x = 0;
777 if x > max_x {
778 max_x = x;
779 }
780 chs.push(WrCh::new('\n', None, x, y));
781 }
782
783 if *r == '\n' {
784 if *self.ch_cursor.borrow() && !*self.wordwrap.borrow() {
789 if x > max_x {
790 max_x = x;
791 }
792 chs.push(WrCh::new(' ', None, x, y));
793 }
794
795 if x > max_x {
798 max_x = x;
799 }
800 chs.push(WrCh::new('\n', Some(abs_pos), x, y));
801
802 y += 1;
804 x = 0;
805 } else {
806 if x > max_x {
807 max_x = x;
808 }
809 chs.push(WrCh::new(*r, Some(abs_pos), x, y));
810 x += 1;
811 }
812 }
813 WrChs { chs, max_x }
814 }
815
816 pub fn get_line_numbers(&self, draw_size: Option<Size>) -> (String, usize) {
819 let wr_chs = self.get_wrapped(draw_size);
820
821 let mut max_line_num = 0;
823 for (i, wr_ch) in wr_chs.chs.iter().enumerate() {
824 if (wr_ch.ch == '\n' && wr_ch.abs_pos.is_some()) || i == 0 {
825 max_line_num += 1;
826 }
827 }
828
829 let line_num_width = max_line_num.to_string().chars().count();
831
832 let mut s = String::new();
833 let mut true_line_num = 1;
834
835 for (i, wr_ch) in wr_chs.chs.iter().enumerate() {
836 if i == 0 {
839 s += &format!("{:line_num_width$} ", true_line_num);
840 true_line_num += 1;
841 s += "\n";
842 }
843 if wr_ch.ch == '\n' {
844 if wr_ch.abs_pos.is_some() {
845 s += &format!("{:line_num_width$} ", true_line_num);
846 true_line_num += 1;
847 }
848 s += "\n";
849 }
850 }
851
852 (s, line_num_width + 1) }
854
855 pub fn correct_offsets(&self, dr: &DrawRegion, w: &WrChs) -> EventResponse {
857 if *self.ch_cursor.borrow() {
858 let (x, y) = w.cursor_x_and_y(self.get_cursor_pos());
859 let (x, y) = (x.unwrap_or(0), y.unwrap_or(0));
860 self.pane.correct_offsets_to_view_position(dr, x, y);
861 }
862 self.correct_ln_and_sbs(dr)
863 }
864
865 pub fn correct_ln_and_sbs(&self, dr: &DrawRegion) -> EventResponse {
867 let y_offset = self.pane.get_content_y_offset();
868 let x_offset = self.pane.get_content_x_offset();
869
870 let update_size = if *self.last_size_for_sbs.borrow() != dr.size {
871 *self.last_size_for_sbs.borrow_mut() = dr.size;
872 self.is_dirty.replace(true);
873 true
874 } else {
875 false
876 };
877
878 if let Some(sb) = self.y_scrollbar.borrow().as_ref() {
880 if update_size {
881 sb.set_scrollable_view_size(dr.size);
882 *sb.scrollable_view_chs.borrow_mut() = DynVal::new_fixed(dr.size.height as i32);
883 }
884
885 sb.external_change(y_offset, self.pane.content_height(), dr.size);
886 }
887 let resp = EventResponse::default();
888 if let Some(ln_tb) = self.line_number_tb.borrow().as_ref() {
889 let (lns, lnw) = self.get_line_numbers(Some(dr.size));
890 let last_lnw = ln_tb.pane.get_width(dr);
891 if lnw != last_lnw {
892 let ln_start_x = ln_tb.pane.get_dyn_start_x();
893 let tb_start_x = ln_start_x.plus_fixed(lnw as i32);
894 self.pane.set_start_x(tb_start_x);
895 ln_tb.pane.set_dyn_width(DynVal::new_fixed(lnw as i32))
896 }
897 ln_tb.set_text(lns);
898 ln_tb.pane.set_content_y_offset(Some(dr), y_offset);
899 }
900 if let Some(sb) = self.x_scrollbar.borrow().as_ref() {
901 if update_size {
902 sb.set_scrollable_view_size(dr.size);
903 *sb.scrollable_view_chs.borrow_mut() = DynVal::new_fixed(dr.size.width as i32);
904 }
905 sb.external_change(x_offset, self.pane.content_width(), dr.size);
906 }
907 resp
908 }
909
910 pub fn visual_selection_pos(&self) -> Option<(usize, usize)> {
912 let mut cur_pos = self.get_cursor_pos();
913 if *self.visual_mode.borrow() {
914 let start_pos = self.get_visual_mode_start_pos();
915 if cur_pos >= self.text.borrow().len() {
916 cur_pos = self.text.borrow().len() - 1;
917 }
918 if start_pos < cur_pos {
919 Some((start_pos, cur_pos))
920 } else {
921 Some((cur_pos, start_pos))
922 }
923 } else {
924 if cur_pos >= self.text.borrow().len() {
925 return None;
926 }
927 Some((cur_pos, cur_pos))
928 }
929 }
930
931 pub fn visual_selected_text(&self) -> String {
932 let text = self.text.borrow();
933 let Some((start_pos, end_pos)) = self.visual_selection_pos() else {
934 return String::new();
935 };
936 if !*self.visual_mode.borrow() {
937 return text[start_pos].to_string();
938 }
939 text[start_pos..=end_pos].iter().collect()
940 }
941
942 pub fn delete_visual_selection(&self, ctx: &Context) -> EventResponses {
943 if !*self.visual_mode.borrow() {
944 return EventResponses::default();
945 }
946
947 let mut rs = self.text.borrow().clone();
949
950 let Some((start_pos, end_pos)) = self.visual_selection_pos() else {
951 return EventResponses::default();
952 };
953 rs.drain(start_pos..=end_pos);
954 self.set_cursor_pos(start_pos);
955
956 *self.text.borrow_mut() = rs;
957 *self.visual_mode.borrow_mut() = false;
958 let w = self.get_wrapped(None);
959 self.pane.set_content_from_string(w.wrapped_string());
960 self.is_dirty.replace(true);
961 let resps = if let Some(hook) = &mut *self.text_changed_hook.borrow_mut() {
962 hook(ctx.clone(), self.get_text())
963 } else {
964 EventResponses::default()
965 };
966 resps
967 }
968
969 pub fn copy_to_clipboard(&self) {
970 let text = self.visual_selected_text();
971 let Ok(mut cb) = arboard::Clipboard::new() else {
972 log_err!("failed to get clipboard");
973 return;
974 };
975 if let Err(e) = cb.set_text(text) {
976 log_err!("failed to set text to clipboard: {}", e);
977 }
978 }
979
980 pub fn cut_to_clipboard(&self, ctx: &Context) -> EventResponses {
981 self.copy_to_clipboard();
982 self.delete_visual_selection(ctx)
983 }
984
985 pub fn paste_from_clipboard(&self, ctx: &Context) -> EventResponses {
986 let mut resps = self.delete_visual_selection(ctx);
987
988 let Ok(mut cb) = arboard::Clipboard::new() else {
989 log_err!("failed to get clipboard");
990 return EventResponses::default();
991 };
992 let Ok(cliptext) = cb.get_text() else {
993 log_err!("failed to get text from clipboard");
994 return EventResponses::default();
995 };
996 if cliptext.is_empty() {
997 return resps;
998 }
999 let cliprunes = cliptext.chars().collect::<Vec<char>>();
1000 let mut rs = self.text.borrow().clone();
1001 let cur_pos = self.get_cursor_pos();
1002 rs.splice(cur_pos..cur_pos, cliprunes.iter().cloned());
1003 *self.text.borrow_mut() = rs;
1004
1005 self.incr_cursor_pos(cliprunes.len() as isize);
1006 let w = self.get_wrapped(None);
1007 self.pane.set_content_from_string(w.wrapped_string()); self.is_dirty.replace(true);
1010
1011 if let Some(hook) = &mut *self.text_changed_hook.borrow_mut() {
1012 resps.extend(hook(ctx.clone(), self.get_text()));
1013 }
1014 resps
1015 }
1016
1017 pub fn receive_key_event(&self, ctx: &Context, ev: Vec<KeyEvent>) -> (bool, EventResponses) {
1018 if *self.selectedness.borrow() != Selectability::Selected || ev.is_empty() {
1019 return (false, EventResponses::default());
1020 }
1021
1022 self.is_dirty.replace(true);
1023
1024 if !*self.ch_cursor.borrow() {
1025 match true {
1026 _ if ev[0] == KB::KEY_LEFT || ev[0] == KB::KEY_H => {
1027 self.pane.scroll_left(None);
1028 }
1029 _ if ev[0] == KB::KEY_RIGHT || ev[0] == KB::KEY_L => {
1030 self.pane.scroll_right(None);
1031 }
1032 _ if ev[0] == KB::KEY_DOWN || ev[0] == KB::KEY_J => {
1033 self.pane.scroll_down(None);
1034 }
1035 _ if ev[0] == KB::KEY_UP || ev[0] == KB::KEY_K => {
1036 self.pane.scroll_up(None);
1037 }
1038 _ => {}
1039 }
1040
1041 return (true, EventResponses::default());
1042 }
1043
1044 let mut visual_mode_event = false;
1045 let visual_mode = *self.visual_mode.borrow();
1046 let cursor_pos = self.get_cursor_pos();
1047
1048 let mut resps = EventResponses::default();
1049 match true {
1050 _ if ev[0] == KB::KEY_SHIFT_LEFT => {
1051 visual_mode_event = true;
1052 if !visual_mode {
1053 *self.visual_mode.borrow_mut() = true;
1054 *self.visual_mode_start_pos.borrow_mut() = cursor_pos;
1055 }
1056 if cursor_pos > 0 {
1057 self.incr_cursor_pos(-1);
1058 }
1059 }
1060
1061 _ if ev[0] == KB::KEY_SHIFT_RIGHT => {
1062 visual_mode_event = true;
1063 if !visual_mode {
1064 *self.visual_mode.borrow_mut() = true;
1065 *self.visual_mode_start_pos.borrow_mut() = cursor_pos;
1066 }
1067 if cursor_pos < self.text.borrow().len() {
1068 self.incr_cursor_pos(1);
1069 }
1070 }
1071
1072 _ if ev[0] == KB::KEY_SHIFT_UP => {
1073 visual_mode_event = true;
1074 if !visual_mode {
1075 *self.visual_mode.borrow_mut() = true;
1076 *self.visual_mode_start_pos.borrow_mut() = cursor_pos;
1077 }
1078 let w = self.get_wrapped(None);
1079 if let Some(new_pos) = w.get_cursor_above_position(cursor_pos) {
1080 self.set_cursor_pos(new_pos);
1081 }
1082 }
1083
1084 _ if ev[0] == KB::KEY_SHIFT_DOWN => {
1085 visual_mode_event = true;
1086 if !visual_mode {
1087 *self.visual_mode.borrow_mut() = true;
1088 *self.visual_mode_start_pos.borrow_mut() = cursor_pos;
1089 }
1090 let w = self.get_wrapped(None);
1091 if let Some(new_pos) = w.get_cursor_below_position(cursor_pos) {
1092 self.set_cursor_pos(new_pos);
1093 }
1094 }
1095
1096 _ if ev[0] == KB::KEY_LEFT || (!*self.editable.borrow() && ev[0] == KB::KEY_H) => {
1097 if cursor_pos > 0 && cursor_pos <= self.text.borrow().len() {
1098 if self.text.borrow()[cursor_pos - 1] != '\n' {
1100 self.incr_cursor_pos(-1);
1101 }
1102 }
1103 }
1104
1105 _ if ev[0] == KB::KEY_RIGHT || (!*self.editable.borrow() && ev[0] == KB::KEY_L) => {
1106 if cursor_pos < self.text.borrow().len() && self.text.borrow()[cursor_pos] != '\n' {
1108 self.incr_cursor_pos(1);
1109 }
1110 }
1111
1112 _ if ev[0] == KB::KEY_UP || (!*self.editable.borrow() && ev[0] == KB::KEY_K) => {
1113 let w = self.get_wrapped(None);
1114 if let Some(new_pos) = w.get_cursor_above_position(cursor_pos) {
1115 self.set_cursor_pos(new_pos);
1116 }
1117 }
1118
1119 _ if ev[0] == KB::KEY_DOWN || (!*self.editable.borrow() && ev[0] == KB::KEY_J) => {
1120 let w = self.get_wrapped(None);
1121 if let Some(new_pos) = w.get_cursor_below_position(cursor_pos) {
1122 self.set_cursor_pos(new_pos);
1123 }
1124 }
1125
1126 _ if *self.editable.borrow() && (ev[0] == KB::KEY_BACKSPACE) => {
1127 if visual_mode {
1128 resps = self.delete_visual_selection(ctx);
1129 } else if cursor_pos > 0 {
1130 let mut rs = self.text.borrow().clone();
1131 rs.remove(cursor_pos - 1);
1132 self.incr_cursor_pos(-1);
1133 *self.text.borrow_mut() = rs;
1134 let w = self.get_wrapped(None);
1135 self.pane.set_content_from_string(w.wrapped_string()); if let Some(hook) = &mut *self.text_changed_hook.borrow_mut() {
1137 resps = hook(ctx.clone(), self.get_text());
1138 }
1139 }
1140 }
1141
1142 _ if *self.editable.borrow() && ev[0] == KB::KEY_ENTER => {
1143 let mut rs = self.text.borrow().clone();
1144 rs.splice(cursor_pos..cursor_pos, std::iter::once('\n'));
1145 *self.text.borrow_mut() = rs;
1146 self.incr_cursor_pos(1);
1147 let w = self.get_wrapped(None);
1148 self.pane.set_content_from_string(w.wrapped_string()); if let Some(hook) = &mut *self.text_changed_hook.borrow_mut() {
1150 resps = hook(ctx.clone(), self.get_text());
1151 }
1152 }
1153
1154 _ if *self.editable.borrow() && KeyPossibility::Chars.matches_key(&ev[0]) => {
1155 if let crossterm::event::KeyCode::Char(r) = ev[0].code {
1156 let mut rs = self.text.borrow().clone();
1157 rs.insert(cursor_pos, r);
1158 *self.text.borrow_mut() = rs;
1159 self.incr_cursor_pos(1);
1160 let w = self.get_wrapped(None);
1161
1162 self.pane.set_content_from_string(w.wrapped_string());
1167 if let Some(hook) = &mut *self.text_changed_hook.borrow_mut() {
1168 resps = hook(ctx.clone(), self.get_text());
1169 }
1170 }
1171 }
1172
1173 _ => {}
1174 }
1175
1176 if *self.visual_mode.borrow() && !visual_mode_event {
1177 *self.visual_mode.borrow_mut() = false;
1178 }
1179
1180 (true, resps)
1181 }
1182
1183 fn changes_made(
1185 &self, start_cur_pos: usize, start_offset_x: usize, start_offset_y: usize,
1186 ) -> bool {
1187 let end_cur_pos = self.get_cursor_pos();
1188 let end_offset_x = self.pane.get_content_x_offset();
1189 let end_offset_y = self.pane.get_content_y_offset();
1190 if start_cur_pos != end_cur_pos
1191 || start_offset_x != end_offset_x
1192 || start_offset_y != end_offset_y
1193 {
1194 return true;
1195 }
1196 false
1197 }
1198
1199 pub fn receive_mouse_event(&self, _: &Context, ev: MouseEvent) -> (bool, EventResponses) {
1200 if let Some(rcm) = &*self.right_click_menu.borrow() {
1202 if let Some(resps) = rcm.create_menu_if_right_click(&ev) {
1203 return (true, resps);
1204 }
1205 }
1206
1207 if !matches!(ev.kind, MouseEventKind::Moved) {
1208 self.is_dirty.replace(true);
1209 }
1210
1211 let selectedness = *self.selectedness.borrow();
1212 let cursor_pos = self.get_cursor_pos();
1213 let start_offset_x = self.pane.get_content_x_offset();
1214 let start_offset_y = self.pane.get_content_y_offset();
1215 match ev.kind {
1216 MouseEventKind::ScrollDown
1217 if ev.modifiers == KeyModifiers::NONE
1218 && selectedness == Selectability::Selected =>
1219 {
1220 let w = self.get_wrapped(Some(ev.dr.size));
1221 let Some(new_pos) = w.get_cursor_below_position(cursor_pos) else {
1222 return (
1223 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1224 EventResponses::default(),
1225 );
1226 };
1227 self.set_cursor_pos(new_pos);
1228 return (
1229 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1230 EventResponses::default(),
1231 );
1232 }
1233 MouseEventKind::ScrollUp
1234 if ev.modifiers == KeyModifiers::NONE
1235 && selectedness == Selectability::Selected =>
1236 {
1237 let w = self.get_wrapped(Some(ev.dr.size));
1238 let Some(new_pos) = w.get_cursor_above_position(cursor_pos) else {
1239 return (
1240 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1241 EventResponses::default(),
1242 );
1243 };
1244 self.set_cursor_pos(new_pos);
1245 return (
1246 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1247 EventResponses::default(),
1248 );
1249 }
1250 MouseEventKind::ScrollLeft
1251 if ev.modifiers == KeyModifiers::NONE
1252 && selectedness == Selectability::Selected =>
1253 {
1254 let w = self.get_wrapped(Some(ev.dr.size));
1255 let Some(new_pos) = w.get_cursor_left_position(cursor_pos) else {
1256 return (
1257 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1258 EventResponses::default(),
1259 );
1260 };
1261 self.set_cursor_pos(new_pos);
1262 return (
1263 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1264 EventResponses::default(),
1265 );
1266 }
1267 MouseEventKind::ScrollDown
1268 if ev.modifiers == KeyModifiers::SHIFT
1269 && selectedness == Selectability::Selected =>
1270 {
1271 let w = self.get_wrapped(Some(ev.dr.size));
1272 let Some(new_pos) = w.get_cursor_left_position(cursor_pos) else {
1273 return (
1274 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1275 EventResponses::default(),
1276 );
1277 };
1278 self.set_cursor_pos(new_pos);
1279 return (
1280 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1281 EventResponses::default(),
1282 );
1283 }
1284 MouseEventKind::ScrollRight
1285 if ev.modifiers == KeyModifiers::NONE
1286 && selectedness == Selectability::Selected =>
1287 {
1288 let w = self.get_wrapped(Some(ev.dr.size));
1289 let Some(new_pos) = w.get_cursor_right_position(cursor_pos) else {
1290 return (
1291 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1292 EventResponses::default(),
1293 );
1294 };
1295 self.set_cursor_pos(new_pos);
1296 return (
1297 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1298 EventResponses::default(),
1299 );
1300 }
1301 MouseEventKind::ScrollUp
1302 if ev.modifiers == KeyModifiers::SHIFT
1303 && selectedness == Selectability::Selected =>
1304 {
1305 let w = self.get_wrapped(Some(ev.dr.size));
1306 let Some(new_pos) = w.get_cursor_right_position(cursor_pos) else {
1307 return (
1308 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1309 EventResponses::default(),
1310 );
1311 };
1312 self.set_cursor_pos(new_pos);
1313 return (
1314 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1315 EventResponses::default(),
1316 );
1317 }
1318 MouseEventKind::ScrollDown
1319 if ev.modifiers == KeyModifiers::NONE && selectedness == Selectability::Ready =>
1320 {
1321 self.pane.scroll_down(Some(&ev.dr));
1322 return (
1323 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1324 EventResponses::default(),
1325 );
1326 }
1327 MouseEventKind::ScrollUp
1328 if ev.modifiers == KeyModifiers::NONE && selectedness == Selectability::Ready =>
1329 {
1330 self.pane.scroll_up(Some(&ev.dr));
1331 return (
1332 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1333 EventResponses::default(),
1334 );
1335 }
1336 MouseEventKind::ScrollLeft
1337 if ev.modifiers == KeyModifiers::NONE && selectedness == Selectability::Ready =>
1338 {
1339 self.pane.scroll_left(Some(&ev.dr));
1340 return (
1341 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1342 EventResponses::default(),
1343 );
1344 }
1345 MouseEventKind::ScrollDown
1346 if ev.modifiers == KeyModifiers::SHIFT && selectedness == Selectability::Ready =>
1347 {
1348 self.pane.scroll_left(Some(&ev.dr));
1349 return (
1350 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1351 EventResponses::default(),
1352 );
1353 }
1354 MouseEventKind::ScrollRight
1355 if ev.modifiers == KeyModifiers::NONE && selectedness == Selectability::Ready =>
1356 {
1357 self.pane.scroll_right(Some(&ev.dr));
1358 return (
1359 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1360 EventResponses::default(),
1361 );
1362 }
1363 MouseEventKind::ScrollUp
1364 if ev.modifiers == KeyModifiers::SHIFT && selectedness == Selectability::Ready =>
1365 {
1366 self.pane.scroll_right(Some(&ev.dr));
1367 return (
1368 self.changes_made(cursor_pos, start_offset_x, start_offset_y),
1369 EventResponses::default(),
1370 );
1371 }
1372
1373 MouseEventKind::Moved | MouseEventKind::Up(_) => {
1374 *self.mouse_dragging.borrow_mut() = false;
1375
1376 return (true, EventResponses::default());
1378 }
1379
1380 MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Drag(MouseButton::Left)
1382 if selectedness == Selectability::Ready =>
1383 {
1384 return (true, EventResponses::default());
1385 }
1386
1387 MouseEventKind::Down(MouseButton::Left) | MouseEventKind::Drag(MouseButton::Left)
1389 if selectedness == Selectability::Selected =>
1390 {
1391 let x = ev.column as usize + self.pane.get_content_x_offset();
1392 let y = ev.row as usize + self.pane.get_content_y_offset();
1393 let w = self.get_wrapped(Some(ev.dr.size));
1394
1395 let mouse_dragging = *self.mouse_dragging.borrow();
1396 let visual_mode = *self.visual_mode.borrow();
1397 if mouse_dragging && !visual_mode {
1398 *self.visual_mode.borrow_mut() = true;
1399 *self.visual_mode_start_pos.borrow_mut() = cursor_pos;
1400 }
1401 if !mouse_dragging && visual_mode {
1402 *self.visual_mode.borrow_mut() = false;
1403 }
1404 *self.mouse_dragging.borrow_mut() = true;
1405 if let Some(new_pos) = w.get_nearest_valid_cursor_from_position(x, y) {
1406 self.set_cursor_pos(new_pos);
1407 return (true, EventResponses::default());
1408 }
1409 return (true, EventResponses::default());
1410 }
1411 _ => {}
1412 }
1413
1414 (false, EventResponses::default())
1415 }
1416
1417 pub fn update_content(&self, ctx: &Context, dr: &DrawRegion) {
1419 let w = self.get_wrapped(Some(dr.size));
1420 let wrapped = w.wrapped_string();
1421 let _ = self.correct_offsets(dr, &w);
1422
1423 let curr_sty = self.current_sty.borrow().clone();
1424 let mut sty = curr_sty.clone();
1425 if wrapped.len() == 1
1426 && *self.ch_cursor.borrow()
1427 && *self.selectedness.borrow() != Selectability::Selected
1428 {
1429 let text = self.text_when_empty.borrow();
1431 sty.set_fg(self.text_when_empty_fg.borrow().clone());
1432 self.pane.set_content_from_string_with_style(dr, &text, sty);
1433 return;
1434 } else {
1435 self.pane
1436 .set_content_from_string_with_style(dr, &wrapped, sty);
1437 }
1438
1439 if let Some(hook) = &mut *self.position_style_hook.borrow_mut() {
1441 for wr_ch in w.chs.iter() {
1442 let existing_sty = curr_sty.clone();
1443 if wr_ch.abs_pos.is_none() {
1444 continue;
1445 }
1446 if let Some(abs_pos) = wr_ch.abs_pos {
1447 let sty = hook(ctx.clone(), abs_pos, existing_sty);
1448 self.pane
1449 .get_content_mut()
1450 .change_style_at_xy(wr_ch.x_pos, wr_ch.y_pos, sty);
1451 }
1452 }
1453 }
1454
1455 if *self.selectedness.borrow() == Selectability::Selected && *self.ch_cursor.borrow() {
1457 let (cur_x, cur_y) = w.cursor_x_and_y(self.get_cursor_pos());
1458 if let (Some(cur_x), Some(cur_y)) = (cur_x, cur_y) {
1459 self.pane.get_content_mut().change_style_at_xy(
1460 cur_x,
1461 cur_y,
1462 self.cursor_style.borrow().clone(),
1463 );
1464 }
1465 }
1466 if *self.visual_mode.borrow() {
1467 let start_pos = self.get_visual_mode_start_pos();
1468 let cur_pos = self.get_cursor_pos();
1469
1470 let start = if start_pos < cur_pos { start_pos } else { cur_pos };
1471 let end = if start_pos < cur_pos { cur_pos } else { start_pos };
1472 for i in start..=end {
1473 if let (Some(cur_x), Some(cur_y)) = w.cursor_x_and_y(i) {
1474 self.pane.get_content_mut().change_style_at_xy(
1475 cur_x,
1476 cur_y,
1477 self.cursor_style.borrow().clone(),
1478 );
1479 }
1480 }
1481 }
1482 }
1483}
1484
1485#[yeehaw_derive::impl_element_from(pane)]
1486impl Element for TextBoxInner {
1487 fn receive_event(&self, ctx: &Context, ev: Event) -> (bool, EventResponses) {
1488 match ev {
1489 Event::KeyCombo(ke) => self.receive_key_event(ctx, ke),
1490 Event::Mouse(me) => self.receive_mouse_event(ctx, me),
1491 _ => (false, EventResponses::default()),
1492 }
1493 }
1494
1495 fn drawing(&self, ctx: &Context, dr: &DrawRegion, force_update: bool) -> Vec<DrawUpdate> {
1496 if self.is_dirty.replace(false) || *self.last_size.borrow() != dr.size || force_update {
1497 self.update_content(ctx, dr);
1498 self.last_size.replace(dr.size);
1499 }
1500 self.pane.drawing(ctx, dr, force_update)
1501 }
1502}
1503
1504#[derive(Clone, Default)]
1506pub struct WrCh {
1507 ch: char,
1509
1510 abs_pos: Option<usize>,
1514
1515 x_pos: usize,
1517 y_pos: usize,
1519}
1520
1521impl WrCh {
1522 pub fn new(ch: char, abs_pos: Option<usize>, x_pos: usize, y_pos: usize) -> Self {
1523 WrCh {
1524 ch,
1525 abs_pos,
1526 x_pos,
1527 y_pos,
1528 }
1529 }
1530}
1531
1532#[derive(Clone, Default)]
1534pub struct WrChs {
1535 chs: Vec<WrCh>,
1536 max_x: usize,
1538}
1539
1540impl WrChs {
1541 pub fn wrapped_string(&self) -> String {
1542 self.chs.iter().map(|wr_ch| wr_ch.ch).collect()
1543 }
1544
1545 pub fn cursor_x_and_y(&self, cur_abs: usize) -> (Option<usize>, Option<usize>) {
1548 self.chs
1549 .iter()
1550 .find(|wr_ch| wr_ch.abs_pos == Some(cur_abs))
1551 .map(|wr_ch| (Some(wr_ch.x_pos), Some(wr_ch.y_pos)))
1552 .unwrap_or_default()
1553 }
1554
1555 pub fn get_line(&self, y: usize) -> Vec<char> {
1557 self.chs
1558 .iter()
1559 .filter(|wr_ch| wr_ch.y_pos == y)
1560 .map(|wr_ch| wr_ch.ch)
1561 .collect()
1562 }
1563
1564 pub fn max_y(&self) -> usize {
1566 self.chs.last().cloned().unwrap_or_default().y_pos
1567 }
1568
1569 pub fn max_x(&self) -> usize {
1570 self.max_x
1571 }
1572
1573 pub fn get_cursor_above_position(&self, cur_abs: usize) -> Option<usize> {
1576 let (cur_i, cur_x, cur_y) = self
1577 .chs
1578 .iter()
1579 .enumerate()
1580 .find(|(_, wr_ch)| wr_ch.abs_pos == Some(cur_abs))
1581 .map(|(i, wr_ch)| (i, wr_ch.x_pos, wr_ch.y_pos))?;
1582
1583 if cur_y == 0 {
1584 return None;
1585 }
1586 self.chs
1587 .iter()
1588 .take(cur_i)
1589 .rev()
1590 .find(|wr_ch| wr_ch.y_pos == cur_y - 1 && wr_ch.x_pos <= cur_x)
1591 .map(|wr_ch| wr_ch.abs_pos)
1592 .unwrap_or(None)
1593 }
1594
1595 pub fn get_cursor_below_position(&self, cur_abs: usize) -> Option<usize> {
1598 let (cur_i, cur_x, cur_y) = self
1599 .chs
1600 .iter()
1601 .enumerate()
1602 .find(|(_, wr_ch)| wr_ch.abs_pos == Some(cur_abs))
1603 .map(|(i, wr_ch)| (i, wr_ch.x_pos, wr_ch.y_pos))?;
1604
1605 if cur_y == self.max_y() {
1606 return None;
1607 }
1608
1609 self.chs
1613 .iter()
1614 .skip(cur_i)
1615 .take_while(|wr_ch| wr_ch.y_pos <= cur_y + 1) .filter(|wr_ch| wr_ch.y_pos == cur_y + 1 && wr_ch.x_pos <= cur_x)
1617 .last()
1618 .map(|wr_ch| wr_ch.abs_pos)
1619 .unwrap_or(None)
1620 }
1621
1622 pub fn get_cursor_left_position(&self, cur_abs: usize) -> Option<usize> {
1624 let (cur_i, cur_x, cur_y) = self
1625 .chs
1626 .iter()
1627 .enumerate()
1628 .find(|(_, wr_ch)| wr_ch.abs_pos == Some(cur_abs))
1629 .map(|(i, wr_ch)| (i, wr_ch.x_pos, wr_ch.y_pos))?;
1630
1631 if cur_x == 0 {
1632 return None;
1633 }
1634 self.chs
1635 .iter()
1636 .take(cur_i)
1637 .rev()
1638 .find(|wr_ch| wr_ch.y_pos == cur_y && wr_ch.x_pos < cur_x)
1639 .map(|wr_ch| wr_ch.abs_pos)
1640 .unwrap_or(None)
1641 }
1642
1643 pub fn get_cursor_right_position(&self, cur_abs: usize) -> Option<usize> {
1644 let (cur_i, cur_x, cur_y) = self
1645 .chs
1646 .iter()
1647 .enumerate()
1648 .find(|(_, wr_ch)| wr_ch.abs_pos == Some(cur_abs))
1649 .map(|(i, wr_ch)| (i, wr_ch.x_pos, wr_ch.y_pos))?;
1650
1651 if cur_x >= self.max_x {
1652 return None;
1653 }
1654 self.chs
1655 .iter()
1656 .skip(cur_i)
1657 .find(|wr_ch| wr_ch.y_pos == cur_y && wr_ch.x_pos > cur_x)
1658 .map(|wr_ch| wr_ch.abs_pos)
1659 .unwrap_or(None)
1660 }
1661
1662 pub fn get_nearest_valid_cursor_from_position(&self, x: usize, y: usize) -> Option<usize> {
1663 let mut nearest_abs = None; let mut nearest_abs_y_pos = None; let mut nearest_abs_x_pos = None; for wr_ch in &self.chs {
1667 if wr_ch.abs_pos.is_none() {
1668 continue;
1669 }
1670 if wr_ch.y_pos == y && wr_ch.x_pos == x {
1671 return wr_ch.abs_pos;
1672 }
1673
1674 let y_diff = (wr_ch.y_pos as isize - y as isize).abs();
1675 let nearest_y_diff = match nearest_abs_y_pos {
1676 Some(nearest_abs_y_pos) => (nearest_abs_y_pos as isize - y as isize).abs(),
1677 None => y_diff,
1678 };
1679
1680 let x_diff = (wr_ch.x_pos as isize - x as isize).abs();
1681 let nearest_x_diff = match nearest_abs_x_pos {
1682 Some(nearest_abs_x_pos) => (nearest_abs_x_pos as isize - x as isize).abs(),
1683 None => x_diff,
1684 };
1685
1686 if y_diff < nearest_y_diff || (y_diff == nearest_y_diff && x_diff < nearest_x_diff) {
1687 nearest_abs_y_pos = Some(wr_ch.y_pos);
1688 nearest_abs_x_pos = Some(wr_ch.x_pos);
1689 nearest_abs = wr_ch.abs_pos;
1690 }
1691 }
1692 nearest_abs
1693 }
1694}