1use std::time::{Duration, Instant};
2
3use iced::{
4 Rectangle, Size, Vector,
5 advanced::{text::Paragraph, widget::operation::Focusable},
6 mouse::ScrollDelta,
7 widget::{container, row},
8};
9
10use crate::{
11 Style,
12 scrollbar::Scrollbar,
13 terminal_grid::{PreRenderer, TerminalGrid, VisiblePosition},
14 wezterm::{WeztermGrid, prerenderer::WeztermPreRenderer},
15};
16
17pub mod style;
18use style::CursorShape;
19
20#[derive(Debug, Clone)]
21pub struct Message(InnerMessage);
22
23#[derive(Debug, Clone)]
24enum InnerMessage {
25 Resize(crate::terminal_grid::Size),
26 KeyPress {
27 modified_key: iced::keyboard::key::Key,
28 modifiers: iced::keyboard::Modifiers,
29 },
30 Input(Vec<u8>),
31 Paste(Option<String>),
32 Scrolled(ScrollDelta),
33 ScrollTo(usize),
34 ScrollDone,
35 StartSelection(VisiblePosition),
36 MoveSelection(VisiblePosition),
37 EndSelection,
38 ShowContextMenu(iced::Point),
39 HideContextMenu,
40 ContextMenuCopy,
41 ContextMenuPaste,
42 IdChanged,
43}
44
45pub enum Action {
46 None,
47 Run(iced::Task<Message>),
48 Resize(crate::terminal_grid::Size),
49 Input(Vec<u8>),
50 IdChanged,
51}
52
53pub struct Terminal {
54 grid: WeztermGrid,
55 id: Id,
56 key_filter: Option<Box<dyn Fn(&iced::keyboard::Key, &iced::keyboard::Modifiers) -> bool>>,
57 context_menu_position: Option<iced::Point>,
59 style: Style,
60 _handle: iced::task::Handle,
61}
62
63impl Terminal {
64 pub fn new() -> (Self, iced::Task<Message>) {
65 let (grid, stream) = WeztermGrid::new();
66 let (task, handle) = iced::Task::run(stream, InnerMessage::Input)
67 .map(Message)
68 .abortable();
69
70 let handle = handle.abort_on_drop();
71
72 (
73 Self {
74 grid,
75 id: Id(iced::advanced::widget::Id::unique()),
76 key_filter: None,
77 context_menu_position: None,
78 style: Style::default(),
79 _handle: handle,
80 },
81 task,
82 )
83 }
84
85 pub fn id(mut self, id: impl Into<Id>) -> Self {
86 self.id = id.into();
87 self
88 }
89
90 pub fn style(mut self, style: Style) -> Self {
91 self.set_style(style);
92 self
93 }
94
95 pub fn set_style(&mut self, style: Style) {
96 self.style = style;
97 }
98
99 pub fn key_filter(
102 mut self,
103 key_filter: impl 'static + Fn(&iced::keyboard::Key, &iced::keyboard::Modifiers) -> bool,
104 ) -> Self {
105 self.set_key_filter(key_filter);
106 self
107 }
108
109 pub fn set_key_filter(
110 &mut self,
111 key_filter: impl 'static + Fn(&iced::keyboard::Key, &iced::keyboard::Modifiers) -> bool,
112 ) {
113 self.key_filter = Some(Box::new(key_filter));
114 }
115
116 pub fn focus<T>(&self) -> iced::Task<T>
117 where
118 T: Send + 'static,
119 {
120 Self::focus_with_id(self.id.clone())
121 }
122
123 pub fn focus_with_id<T>(id: impl Into<Id>) -> iced::Task<T>
124 where
125 T: Send + 'static,
126 {
127 iced::advanced::widget::operate(iced::advanced::widget::operation::focusable::focus(
128 id.into().0,
129 ))
130 }
131
132 pub fn get_title(&self) -> &str {
133 self.grid.get_title()
134 }
135
136 pub fn advance_bytes<B>(&mut self, bytes: B)
137 where
138 B: AsRef<[u8]>,
139 {
140 self.grid.advance_bytes(bytes.as_ref());
141 }
142
143 #[must_use]
144 pub fn update(&mut self, message: Message) -> Action {
145 match message.0 {
146 InnerMessage::Resize(size) => {
147 self.grid.resize(size);
148 Action::Resize(size)
149 }
150 InnerMessage::KeyPress {
151 modified_key,
152 modifiers,
153 } => {
154 if modified_key == iced::keyboard::Key::Character("V".into())
155 && modifiers.control()
156 && modifiers.shift()
157 {
158 return self.paste();
159 }
160
161 if modified_key == iced::keyboard::Key::Character("C".into())
162 && modifiers.control()
163 && modifiers.shift()
164 {
165 return self.copy();
166 }
167
168 if let Some(input) = self.grid.press_key(modified_key, modifiers) {
169 Action::Input(input)
170 } else {
171 Action::None
172 }
173 }
174 InnerMessage::Input(input) => Action::Input(input),
175 InnerMessage::Paste(paste) => {
176 if let Some(paste) = paste {
177 if let Some(input) = self.grid.paste(&paste) {
178 return Action::Input(input);
179 }
180 }
181 Action::None
182 }
183 InnerMessage::Scrolled(scrolled) => {
184 match scrolled {
185 ScrollDelta::Lines { y, .. } => {
186 self.grid.scroll(y as isize);
187 }
188 ScrollDelta::Pixels { y, .. } => {
189 self.grid.scroll(y as isize);
190 }
191 };
192
193 Action::None
194 }
195 InnerMessage::ScrollTo(y) => {
196 self.grid.scroll_to(y);
197 Action::None
198 }
199 InnerMessage::ScrollDone => Action::Run(self.focus()),
200 InnerMessage::StartSelection(start) => {
201 self.grid.start_selection(start);
202 Action::None
203 }
204 InnerMessage::MoveSelection(position) => {
205 self.grid.move_selection(position);
206 Action::None
207 }
208 InnerMessage::EndSelection => {
209 self.grid.end_selection();
210 Action::None
211 }
212 InnerMessage::ShowContextMenu(position) => {
213 self.context_menu_position = Some(position);
214 Action::None
215 }
216 InnerMessage::HideContextMenu => {
217 self.context_menu_position = None;
218 Action::None
219 }
220 InnerMessage::ContextMenuCopy => {
221 self.context_menu_position = None;
222 self.copy()
223 }
224 InnerMessage::ContextMenuPaste => {
225 self.context_menu_position = None;
226 self.paste()
227 }
228 InnerMessage::IdChanged => Action::IdChanged,
229 }
230 }
231
232 fn copy(&self) -> Action {
233 if let Some(selected_text) = self.grid.selected_text() {
234 Action::Run(iced::clipboard::write(selected_text).chain(self.focus()))
235 } else {
236 Action::Run(self.focus())
237 }
238 }
239
240 fn paste(&self) -> Action {
241 Action::Run(
242 iced::clipboard::read()
243 .map(InnerMessage::Paste)
244 .map(Message)
245 .chain(self.focus()),
246 )
247 }
248
249 pub fn view<'a, Theme, Renderer>(&'a self) -> iced::Element<'a, Message, Theme, Renderer>
250 where
251 Renderer: iced::advanced::text::Renderer<Font = iced::Font> + 'static,
252 Theme: iced::widget::text::Catalog + 'static,
253 Theme: iced::widget::container::Catalog + iced::widget::button::Catalog,
254 <Theme as iced::widget::text::Catalog>::Class<'static>:
255 From<iced::widget::text::StyleFn<'static, Theme>>,
256 <Theme as iced::widget::container::Catalog>::Class<'static>:
257 From<iced::widget::container::StyleFn<'static, Theme>>,
258 Theme: iced::widget::scrollable::Catalog,
259 {
260 self.view_internal().map(Message)
261 }
262
263 fn view_internal<'a, Theme, Renderer>(
264 &'a self,
265 ) -> iced::Element<'a, InnerMessage, Theme, Renderer>
266 where
267 Renderer: iced::advanced::text::Renderer<Font = iced::Font> + 'static,
268 Theme: iced::widget::text::Catalog + 'static,
269 Theme: iced::widget::container::Catalog + iced::widget::button::Catalog,
270 <Theme as iced::widget::text::Catalog>::Class<'static>:
271 From<iced::widget::text::StyleFn<'static, Theme>>,
272 <Theme as iced::widget::container::Catalog>::Class<'static>:
273 From<iced::widget::container::StyleFn<'static, Theme>>,
274 Theme: iced::widget::scrollable::Catalog,
275 {
276 let total_rows = self.grid.available_lines();
277
278 let terminal_widget = row![
279 iced::Element::new(TerminalWidget::new(self)),
280 Scrollbar::new(
281 total_rows as u32,
282 self.grid.get_scroll() as u32,
283 self.grid.get_size().rows as u32
284 )
285 .on_scroll(move |scroll| {
286 let new_scroll = (total_rows as f32 * scroll) as usize;
287 InnerMessage::ScrollTo(new_scroll)
288 })
289 .on_scroll_done(InnerMessage::ScrollDone)
290 ];
291
292 if let Some(position) = self.context_menu_position {
293 let copy_button = iced::widget::button(iced::widget::text("Copy").size(14))
294 .padding([4, 8])
295 .width(iced::Length::Fill)
296 .on_press(InnerMessage::ContextMenuCopy);
297
298 let paste_button = iced::widget::button(iced::widget::text("Paste").size(14))
299 .padding([4, 8])
300 .width(iced::Length::Fill)
301 .on_press(InnerMessage::ContextMenuPaste);
302
303 let context_menu = iced::widget::column![copy_button, paste_button].spacing(2);
304
305 let positioned_menu = iced::widget::container(context_menu)
306 .style(|_theme| iced::widget::container::Style {
307 background: Some(iced::Background::Color(iced::Color::from_rgb(
308 0.2, 0.2, 0.2,
309 ))),
310 border: iced::Border {
311 color: iced::Color::from_rgb(0.5, 0.5, 0.5),
312 width: 1.0,
313 radius: 4.0.into(),
314 },
315 ..Default::default()
316 })
317 .padding(4)
318 .width(100)
319 .height(iced::Length::Shrink);
320
321 let positioned_container = container(positioned_menu)
323 .width(iced::Length::Fill)
324 .height(iced::Length::Fill)
325 .padding(iced::Padding {
326 top: position.y,
327 right: 0.0,
328 bottom: 0.0,
329 left: position.x,
330 });
331
332 iced::widget::stack![terminal_widget, positioned_container].into()
333 } else {
334 iced::widget::stack![terminal_widget].into()
335 }
336 }
337}
338
339#[derive(Debug, Clone, PartialEq, Eq, Hash)]
340pub struct Id(iced::advanced::widget::Id);
341
342impl Id {
343 pub fn new(id: &'static str) -> Self {
345 Self(iced::advanced::widget::Id::new(id))
346 }
347
348 pub fn unique() -> Self {
352 Self(iced::advanced::widget::Id::unique())
353 }
354}
355
356impl From<Id> for iced::advanced::widget::Id {
357 fn from(id: Id) -> Self {
358 id.0
359 }
360}
361
362impl From<&'static str> for Id {
363 fn from(id: &'static str) -> Self {
364 Self::new(id)
365 }
366}
367
368struct TerminalWidget<'a> {
375 term: &'a Terminal,
376}
377
378struct State<R: iced::advanced::text::Renderer> {
379 prerenderer: WeztermPreRenderer<R>,
380 focused: bool,
381 last_cursor_blink: Instant,
382 cursor_blink_currently_shown: bool,
383 now: Instant,
384 last_widget_width: f32,
385 last_widget_height: f32,
386 last_id: Option<Id>,
387}
388
389const CHAR_WIDTH: f32 = 0.6;
390const CURSOR_BLINK_INTERVAL_MILLIS: u128 = 500;
391
392impl<Renderer> iced::advanced::widget::operation::Focusable for State<Renderer>
393where
394 Renderer: iced::advanced::text::Renderer,
395{
396 fn is_focused(&self) -> bool {
397 self.focused
398 }
399
400 fn focus(&mut self) {
401 self.focused = true;
402 self.last_cursor_blink = Instant::now();
403 self.cursor_blink_currently_shown = true;
404 }
405
406 fn unfocus(&mut self) {
407 self.focused = false;
408 self.cursor_blink_currently_shown = false;
409 }
410}
411
412impl<Theme, Renderer> iced::advanced::widget::Widget<InnerMessage, Theme, Renderer>
413 for TerminalWidget<'_>
414where
415 Renderer: iced::advanced::text::Renderer<Font = iced::Font>,
416 Renderer: 'static,
417{
418 fn tag(&self) -> iced::advanced::widget::tree::Tag {
419 iced::advanced::widget::tree::Tag::of::<State<Renderer>>()
420 }
421
422 fn state(&self) -> iced::advanced::widget::tree::State {
423 iced::advanced::widget::tree::State::new(State::<Renderer> {
424 prerenderer: WeztermPreRenderer::new(self.term.style.clone()),
425 focused: false,
426 last_cursor_blink: Instant::now(),
427 cursor_blink_currently_shown: false,
428 now: Instant::now(),
429 last_id: None,
431 last_widget_height: 0.0,
432 last_widget_width: 0.0,
433 })
434 }
435
436 fn size(&self) -> iced::Size<iced::Length> {
437 iced::Size::new(iced::Length::Fill, iced::Length::Fill)
438 }
439
440 fn operate(
441 &mut self,
442 tree: &mut iced::advanced::widget::Tree,
443 layout: iced::advanced::Layout<'_>,
444 _renderer: &Renderer,
445 operation: &mut dyn iced::advanced::widget::Operation,
446 ) {
447 let state = tree.state.downcast_mut::<State<Renderer>>();
448
449 operation.focusable(Some(&self.term.id.0), layout.bounds(), state);
450 }
451
452 fn update(
453 &mut self,
454 state: &mut iced::advanced::widget::Tree,
455 event: &iced::Event,
456 layout: iced::advanced::Layout<'_>,
457 cursor: iced::advanced::mouse::Cursor,
458 renderer: &Renderer,
459 _clipboard: &mut dyn iced::advanced::Clipboard,
460 shell: &mut iced::advanced::Shell<'_, InnerMessage>,
461 _viewport: &iced::Rectangle,
462 ) {
463 match event {
464 iced::Event::Window(iced::window::Event::RedrawRequested(now)) => {
465 let state = state.state.downcast_mut::<State<Renderer>>();
466
467 let widget_width = layout.bounds().width - self.term.style.padding.x();
468 let widget_height = layout.bounds().height - self.term.style.padding.y();
469
470 let id_changed = state
472 .last_id
473 .as_ref()
474 .map(|last_id| last_id != &self.term.id)
475 .unwrap_or(true);
476
477 if id_changed {
478 state.prerenderer.clear_cache();
479 state.last_id = Some(self.term.id.clone());
480 shell.publish(InnerMessage::IdChanged);
481 }
482
483 if state.last_widget_width != widget_width
485 || state.last_widget_height != widget_height
486 || id_changed
487 {
488 state.last_widget_width = widget_width;
489 state.last_widget_height = widget_height;
490
491 let text_size = self.term.style.text_size.unwrap_or(renderer.default_size());
492 let line_height = self.term.style.line_height.to_absolute(text_size);
493 let char_width = text_size * CHAR_WIDTH;
494
495 let target_line_count = (widget_height / line_height.0) as usize;
496 let target_col_count = (widget_width / char_width.0) as usize;
497 let size = self.term.grid.get_size();
498
499 if size.rows != target_line_count || size.cols != target_col_count {
500 let size = crate::terminal_grid::Size {
501 cols: target_col_count,
502 rows: target_line_count,
503 };
504 shell.publish(InnerMessage::Resize(size));
505 }
506 }
507
508 if state.is_focused() {
510 state.now = *now;
511 let millis_until_redraw = CURSOR_BLINK_INTERVAL_MILLIS
512 .saturating_sub((*now - state.last_cursor_blink).as_millis());
513
514 if millis_until_redraw == 0 {
515 state.cursor_blink_currently_shown = !state.cursor_blink_currently_shown;
516 state.last_cursor_blink = *now;
517 }
518
519 shell.request_redraw_at(
520 *now + Duration::from_millis(millis_until_redraw as u64),
521 );
522 } else if state.cursor_blink_currently_shown == true {
523 state.cursor_blink_currently_shown = false;
524 shell.request_redraw();
525 }
526 }
527 iced::Event::Mouse(iced::mouse::Event::WheelScrolled { delta }) => {
528 if cursor.position_over(layout.bounds()).is_some() {
529 shell.publish(InnerMessage::Scrolled(delta.clone()));
530 shell.capture_event();
531 }
532 }
533 iced::Event::Mouse(iced::mouse::Event::ButtonPressed(button)) => {
534 let state = state.state.downcast_mut::<State<Renderer>>();
535 let newly_focused = cursor.position_over(layout.bounds()).is_some();
536
537 if newly_focused {
538 state.focus();
539
540 if *button == iced::mouse::Button::Left {
542 if self.term.context_menu_position.is_some() {
544 shell.publish(InnerMessage::HideContextMenu);
545 }
546
547 if let Some(cursor_position) = cursor.position() {
548 if let Some(char_pos) =
549 self.screen_to_visible_position(cursor_position, layout, renderer)
550 {
551 shell.publish(InnerMessage::StartSelection(char_pos));
552 }
553 }
554 }
555
556 if *button == iced::mouse::Button::Right {
558 if let Some(cursor_position) = cursor.position() {
559 shell.publish(InnerMessage::ShowContextMenu(cursor_position));
560 }
561 }
562
563 shell.capture_event();
564 } else {
565 state.unfocus();
566 }
567 }
568 iced::Event::Mouse(iced::mouse::Event::CursorMoved { position }) => {
569 if self.term.grid.currently_selecting() {
570 if let Some(char_pos) =
571 self.screen_to_visible_position(*position, layout, renderer)
572 {
573 shell.publish(InnerMessage::MoveSelection(char_pos));
574 }
575 shell.capture_event();
576 }
577 }
578 iced::Event::Mouse(iced::mouse::Event::ButtonReleased(button)) => {
579 if *button == iced::mouse::Button::Left {
580 if self.term.grid.currently_selecting() {
581 shell.publish(InnerMessage::EndSelection);
582 shell.capture_event();
583 }
584 }
585 }
586 iced::Event::Touch(iced::touch::Event::FingerPressed { .. }) => {
587 let state = state.state.downcast_mut::<State<Renderer>>();
588 let newly_focused = cursor.position_over(layout.bounds()).is_some();
589
590 if newly_focused {
591 state.focus();
592 shell.capture_event();
593 } else {
594 state.unfocus();
595 }
596 }
597 iced::Event::Keyboard(iced::keyboard::Event::KeyPressed {
598 modified_key,
599 modifiers,
600 ..
601 }) => {
602 let state = state.state.downcast_mut::<State<Renderer>>();
603
604 if state.is_focused() {
605 if let Some(filter) = &self.term.key_filter {
606 if filter(&modified_key, &modifiers) {
607 return;
608 }
609 }
610
611 state.last_cursor_blink = Instant::now();
612 state.cursor_blink_currently_shown = true;
613
614 let message = InnerMessage::KeyPress {
615 modified_key: modified_key.clone(),
616 modifiers: modifiers.clone(),
617 };
618 shell.publish(message);
619
620 shell.capture_event();
621 }
622 }
623 iced::Event::Window(iced::window::Event::Focused) => {
624 let state = state.state.downcast_mut::<State<Renderer>>();
625 state.focus();
626 shell.request_redraw();
627 }
628 _ => (),
629 }
630 }
631
632 fn layout(
633 &mut self,
634 tree: &mut iced::advanced::widget::Tree,
635 renderer: &Renderer,
636 limits: &iced::advanced::layout::Limits,
637 ) -> iced::advanced::layout::Node {
638 let state = tree.state.downcast_mut::<State<Renderer>>();
639
640 state.prerenderer.update(&self.term.grid, renderer);
641
642 iced::advanced::layout::Node::new(limits.max())
643 }
644
645 fn draw(
646 &self,
647 tree: &iced::advanced::widget::Tree,
648 renderer: &mut Renderer,
649 _theme: &Theme,
650 _style: &iced::advanced::renderer::Style,
651 layout: iced::advanced::Layout<'_>,
652 _cursor: iced::advanced::mouse::Cursor,
653 viewport: &iced::Rectangle,
654 ) {
655 let Some(bounds) = layout.bounds().intersection(viewport) else {
656 return;
657 };
658
659 let state = tree.state.downcast_ref::<State<Renderer>>();
660 let padding_offset =
661 iced::Vector::new(self.term.style.padding.left, self.term.style.padding.top);
662 let translation = layout.position() - iced::Point::ORIGIN + padding_offset;
663
664 renderer.fill_quad(
666 iced::advanced::renderer::Quad {
667 bounds: layout.bounds(),
668 ..Default::default()
669 },
670 self.term.style.background_color,
671 );
672
673 let size = self
674 .term
675 .style
676 .text_size
677 .unwrap_or_else(|| renderer.default_size());
678
679 let y_multiplier = self.term.style.line_height.to_absolute(size).0;
680
681 for (row_index, render_data) in state.prerenderer.visible_rows().enumerate() {
683 let Some((paragraph, spans)) = render_data else {
684 continue;
685 };
686 let y_offset = y_multiplier * row_index as f32;
687
688 for (index, span) in spans.iter().enumerate() {
689 if let Some(highlight) = span.highlight {
690 let regions = paragraph.span_bounds(index);
691
692 for bounds in ®ions {
693 let position =
694 bounds.position() - Vector::new(span.padding.left, span.padding.top);
695
696 let size = bounds.size() + Size::new(span.padding.x(), span.padding.y());
697
698 let position = position + translation + iced::Vector::new(0.0, y_offset);
699 let bounds = Rectangle::new(position, size);
700
701 renderer.fill_quad(
702 iced::advanced::renderer::Quad {
703 bounds,
704 border: highlight.border,
705 ..Default::default()
706 },
707 highlight.background,
708 );
709 }
710 }
711 }
712
713 renderer.fill_paragraph(
714 ¶graph,
715 bounds.position() + padding_offset + iced::Vector::new(0.0, y_offset),
716 self.term.style.foreground_color,
717 bounds,
718 );
719 }
720
721 self.draw_cursor(renderer, &state, translation);
722 }
723}
724
725impl<'a> TerminalWidget<'a> {
726 pub fn new(term: &'a Terminal) -> Self {
727 Self { term }
728 }
729
730 fn screen_to_visible_position<Renderer>(
731 &self,
732 screen_pos: iced::Point,
733 layout: iced::advanced::Layout<'_>,
734 renderer: &Renderer,
735 ) -> Option<VisiblePosition>
736 where
737 Renderer: iced::advanced::text::Renderer,
738 {
739 let padding_offset =
740 iced::Vector::new(self.term.style.padding.left, self.term.style.padding.top);
741 let translation = layout.position() - iced::Point::ORIGIN + padding_offset;
742
743 let relative_pos = screen_pos - translation;
745
746 if relative_pos.x < 0.0 || relative_pos.y < 0.0 {
748 return None;
749 }
750
751 let text_size = self
753 .term
754 .style
755 .text_size
756 .unwrap_or_else(|| renderer.default_size());
757 let line_height = self.term.style.line_height.to_absolute(text_size).0;
758 let text_size = text_size.0;
759 let char_width = text_size * CHAR_WIDTH;
760
761 let char_x = (relative_pos.x / char_width) as usize;
763 let char_y = (relative_pos.y / line_height) as usize;
764
765 let absolute_y = char_y;
767
768 Some(VisiblePosition {
769 x: char_x,
770 y: absolute_y,
771 })
772 }
773
774 fn draw_cursor<Renderer>(
775 &self,
776 renderer: &mut Renderer,
777 state: &State<Renderer>,
778 translation: iced::Vector,
779 ) where
780 Renderer: iced::advanced::text::Renderer,
781 {
782 let Some(cursor) = self.term.grid.get_cursor() else {
783 return;
784 };
785 if !state.cursor_blink_currently_shown {
786 return;
787 }
788
789 let cursor_absolute_y = cursor.y as i64;
792 let scroll_offset = 0 as i64;
793 let visible_cursor_y = cursor_absolute_y + scroll_offset;
794
795 let text_size = self
797 .term
798 .style
799 .text_size
800 .unwrap_or_else(|| renderer.default_size());
801
802 let line_height = self.term.style.line_height.to_absolute(text_size).0;
803 let text_size = text_size.0;
804 let char_width = text_size * CHAR_WIDTH;
805
806 let base_cursor_position = iced::Point::new(
807 cursor.x as f32 * char_width,
808 visible_cursor_y as f32 * line_height,
809 );
810
811 let padding = 1.0;
812
813 let cursor_bounds = match self.term.style.cursor_shape {
814 CursorShape::Underline => iced::Rectangle::new(
815 base_cursor_position
816 + translation
817 + iced::Vector::new(0.0, renderer.default_size().0 * 1.2),
818 iced::Size::new(renderer.default_size().0 * CHAR_WIDTH, 1.0),
819 ),
820 CursorShape::Block => iced::Rectangle::new(
821 base_cursor_position + translation + iced::Vector::new(padding, padding),
822 iced::Size::new(
823 renderer.default_size().0 * CHAR_WIDTH - padding,
824 renderer.default_size().0 * 1.3 - padding,
825 ),
826 ),
827 CursorShape::Bar => iced::Rectangle::new(
828 base_cursor_position + translation + iced::Vector::new(padding, padding),
829 iced::Size::new(1.0, renderer.default_size().0 * 1.3 - padding),
830 ),
831 };
832
833 renderer.fill_quad(
834 iced::advanced::renderer::Quad {
835 bounds: cursor_bounds,
836 border: iced::Border::default(),
837 ..Default::default()
838 },
839 iced::Color::WHITE,
840 );
841 }
842}