1use std::collections::HashMap;
4
5use chrono::{DateTime, Local};
6use ratatui::{
7 Frame,
8 layout::{Alignment, Rect},
9 style::{Color, Style},
10 text::{Line, Span},
11 widgets::{Block, Borders, Padding, Paragraph},
12};
13
14use crate::themes::theme as app_theme;
15use crate::markdown::{render_markdown_with_prefix, wrap_with_prefix};
16
17pub mod defaults {
19 pub const USER_PREFIX: &str = "> ";
21 pub const SYSTEM_PREFIX: &str = "* ";
23 pub const TIMESTAMP_PREFIX: &str = " - ";
25 pub const CONTINUATION: &str = " ";
27 pub const SPINNER_CHARS: &[char] = &['\u{280B}', '\u{2819}', '\u{2839}', '\u{2838}', '\u{283C}', '\u{2834}', '\u{2826}', '\u{2827}', '\u{2807}', '\u{280F}'];
29 pub const DEFAULT_TITLE: &str = "Chat";
31 pub const DEFAULT_EMPTY_MESSAGE: &str = " Type a message to start chatting...";
33 pub const TOOL_ICON: &str = "\u{2692}";
35 pub const TOOL_EXECUTING_ARROW: &str = "\u{2192}";
37 pub const TOOL_COMPLETED_CHECKMARK: &str = "\u{2713}";
39 pub const TOOL_FAILED_ICON: &str = "\u{26A0}";
41}
42
43#[derive(Clone)]
55pub struct ChatViewConfig {
56 pub user_prefix: String,
58 pub system_prefix: String,
60 pub timestamp_prefix: String,
62 pub continuation: String,
64 pub spinner_chars: Vec<char>,
66 pub default_title: String,
68 pub empty_message: String,
70 pub tool_icon: String,
72 pub tool_executing_arrow: String,
74 pub tool_completed_checkmark: String,
76 pub tool_failed_icon: String,
78}
79
80impl Default for ChatViewConfig {
81 fn default() -> Self {
82 Self::new()
83 }
84}
85
86impl ChatViewConfig {
87 pub fn new() -> Self {
89 Self {
90 user_prefix: defaults::USER_PREFIX.to_string(),
91 system_prefix: defaults::SYSTEM_PREFIX.to_string(),
92 timestamp_prefix: defaults::TIMESTAMP_PREFIX.to_string(),
93 continuation: defaults::CONTINUATION.to_string(),
94 spinner_chars: defaults::SPINNER_CHARS.to_vec(),
95 default_title: defaults::DEFAULT_TITLE.to_string(),
96 empty_message: defaults::DEFAULT_EMPTY_MESSAGE.to_string(),
97 tool_icon: defaults::TOOL_ICON.to_string(),
98 tool_executing_arrow: defaults::TOOL_EXECUTING_ARROW.to_string(),
99 tool_completed_checkmark: defaults::TOOL_COMPLETED_CHECKMARK.to_string(),
100 tool_failed_icon: defaults::TOOL_FAILED_ICON.to_string(),
101 }
102 }
103
104 pub fn with_user_prefix(mut self, prefix: impl Into<String>) -> Self {
106 self.user_prefix = prefix.into();
107 self
108 }
109
110 pub fn with_system_prefix(mut self, prefix: impl Into<String>) -> Self {
112 self.system_prefix = prefix.into();
113 self
114 }
115
116 pub fn with_timestamp_prefix(mut self, prefix: impl Into<String>) -> Self {
118 self.timestamp_prefix = prefix.into();
119 self
120 }
121
122 pub fn with_continuation(mut self, continuation: impl Into<String>) -> Self {
124 self.continuation = continuation.into();
125 self
126 }
127
128 pub fn with_spinner_chars(mut self, chars: &[char]) -> Self {
130 self.spinner_chars = chars.to_vec();
131 self
132 }
133
134 pub fn with_default_title(mut self, title: impl Into<String>) -> Self {
136 self.default_title = title.into();
137 self
138 }
139
140 pub fn with_empty_message(mut self, message: impl Into<String>) -> Self {
142 self.empty_message = message.into();
143 self
144 }
145
146 pub fn with_tool_icon(mut self, icon: impl Into<String>) -> Self {
148 self.tool_icon = icon.into();
149 self
150 }
151
152 pub fn with_tool_status_icons(
154 mut self,
155 executing_arrow: impl Into<String>,
156 completed_checkmark: impl Into<String>,
157 failed_icon: impl Into<String>,
158 ) -> Self {
159 self.tool_executing_arrow = executing_arrow.into();
160 self.tool_completed_checkmark = completed_checkmark.into();
161 self.tool_failed_icon = failed_icon.into();
162 self
163 }
164}
165
166#[derive(Debug, Clone, Copy, PartialEq)]
168pub enum MessageRole {
169 User,
170 Assistant,
171 System,
172 Tool,
173}
174
175#[derive(Debug, Clone, PartialEq)]
177pub enum ToolStatus {
178 Executing,
179 WaitingForUser,
180 Completed,
181 Failed(String),
182}
183
184#[derive(Debug, Clone)]
187pub struct ToolMessageData {
188 #[allow(dead_code)]
191 pub tool_use_id: String,
192 pub display_name: String,
194 pub display_title: String,
196 pub status: ToolStatus,
198}
199
200struct Message {
201 role: MessageRole,
202 content: String,
203 timestamp: DateTime<Local>,
204 cached_lines: Option<Vec<Line<'static>>>,
206 cached_width: usize,
208 tool_data: Option<ToolMessageData>,
210}
211
212impl Message {
213 fn new(role: MessageRole, content: String) -> Self {
214 Self {
215 role,
216 content,
217 timestamp: Local::now(),
218 cached_lines: None,
219 cached_width: 0,
220 tool_data: None,
221 }
222 }
223
224 fn new_tool(tool_data: ToolMessageData) -> Self {
225 Self {
226 role: MessageRole::Tool,
227 content: String::new(),
228 timestamp: Local::now(),
229 cached_lines: None,
230 cached_width: 0,
231 tool_data: Some(tool_data),
232 }
233 }
234
235 fn get_rendered_lines(&mut self, available_width: usize, config: &ChatViewConfig) -> &[Line<'static>] {
237 if self.cached_width != available_width {
239 self.cached_lines = None;
240 }
241
242 if self.cached_lines.is_none() {
244 let lines = self.render_lines(available_width, config);
245 self.cached_lines = Some(lines);
246 self.cached_width = available_width;
247 }
248
249 self.cached_lines.as_ref().unwrap()
250 }
251
252 fn render_lines(&self, available_width: usize, config: &ChatViewConfig) -> Vec<Line<'static>> {
254 let mut lines = Vec::new();
255 let t = app_theme();
256
257 match self.role {
258 MessageRole::User => {
259 let rendered = wrap_with_prefix(
260 &self.content,
261 &config.user_prefix,
262 t.user_prefix,
263 &config.continuation,
264 available_width,
265 &t,
266 );
267 lines.extend(rendered);
268 }
269 MessageRole::System => {
270 let rendered = wrap_with_prefix(
271 &self.content,
272 &config.system_prefix,
273 t.system_prefix,
274 &config.continuation,
275 available_width,
276 &t,
277 );
278 lines.extend(rendered);
279 }
280 MessageRole::Assistant => {
281 let rendered = render_markdown_with_prefix(&self.content, available_width, &t);
282 lines.extend(rendered);
283 }
284 MessageRole::Tool => {
285 if let Some(ref data) = self.tool_data {
286 lines.extend(render_tool_message(data, config, available_width));
287 }
288 }
289 }
290
291 if self.role != MessageRole::Assistant && self.role != MessageRole::Tool {
294 let time_str = self.timestamp.format("%I:%M:%S %p").to_string();
295 let timestamp_text = format!("{}{}", config.timestamp_prefix, time_str);
296 lines.push(Line::from(vec![Span::styled(
297 timestamp_text,
298 app_theme().timestamp,
299 )]));
300 }
301
302 lines.push(Line::from(""));
304
305 lines
306 }
307}
308
309pub use super::chat_helpers::RenderFn;
311
312use crate::themes::Theme;
313
314pub type TitleRenderFn = Box<dyn Fn(&str, &Theme) -> (Line<'static>, Line<'static>) + Send + Sync>;
317
318pub struct ChatView {
320 messages: Vec<Message>,
321 scroll_offset: u16,
322 streaming_buffer: Option<String>,
324 streaming_cache: Option<Vec<Line<'static>>>,
326 streaming_cache_len: usize,
328 streaming_cache_width: usize,
330 last_max_scroll: u16,
332 auto_scroll_enabled: bool,
334 tool_index: HashMap<String, usize>,
336 spinner_index: usize,
338 title: String,
340 render_initial_content: Option<RenderFn>,
342 render_title: Option<TitleRenderFn>,
344 config: ChatViewConfig,
346}
347
348impl ChatView {
349 pub fn new() -> Self {
351 Self::with_config(ChatViewConfig::new())
352 }
353
354 pub fn with_config(config: ChatViewConfig) -> Self {
356 let title = config.default_title.clone();
357 Self {
358 messages: Vec::new(),
359 scroll_offset: 0,
360 streaming_buffer: None,
361 streaming_cache: None,
362 streaming_cache_len: 0,
363 streaming_cache_width: 0,
364 last_max_scroll: 0,
365 auto_scroll_enabled: true,
366 tool_index: HashMap::new(),
367 spinner_index: 0,
368 title,
369 render_initial_content: None,
370 render_title: None,
371 config,
372 }
373 }
374
375 pub fn config(&self) -> &ChatViewConfig {
377 &self.config
378 }
379
380 pub fn set_config(&mut self, config: ChatViewConfig) {
382 self.config = config;
383 for msg in &mut self.messages {
385 msg.cached_lines = None;
386 }
387 }
388
389 pub fn with_title(mut self, title: impl Into<String>) -> Self {
391 self.title = title.into();
392 self
393 }
394
395 pub fn with_initial_content(mut self, render: RenderFn) -> Self {
400 self.render_initial_content = Some(render);
401 self
402 }
403
404 pub fn with_title_renderer<F>(mut self, render: F) -> Self
409 where
410 F: Fn(&str, &Theme) -> (Line<'static>, Line<'static>) + Send + Sync + 'static,
411 {
412 self.render_title = Some(Box::new(render));
413 self
414 }
415
416 pub fn set_title(&mut self, title: impl Into<String>) {
418 self.title = title.into();
419 }
420
421 pub fn title(&self) -> &str {
423 &self.title
424 }
425
426 pub fn step_spinner(&mut self) {
428 let len = self.config.spinner_chars.len().max(1);
429 self.spinner_index = (self.spinner_index + 1) % len;
430 }
431
432 pub fn add_user_message(&mut self, content: String) {
434 if !content.trim().is_empty() {
435 self.messages.push(Message::new(MessageRole::User, content));
436 if self.auto_scroll_enabled {
438 self.scroll_offset = u16::MAX;
439 }
440 }
441 }
442
443 pub fn add_assistant_message(&mut self, content: String) {
445 if !content.trim().is_empty() {
446 self.messages
447 .push(Message::new(MessageRole::Assistant, content));
448 if self.auto_scroll_enabled {
450 self.scroll_offset = u16::MAX;
451 }
452 }
453 }
454
455 pub fn add_system_message(&mut self, content: String) {
457 if content.trim().is_empty() {
458 return;
459 }
460 self.messages
461 .push(Message::new(MessageRole::System, content));
462 if self.auto_scroll_enabled {
464 self.scroll_offset = u16::MAX;
465 }
466 }
467
468 pub fn add_tool_message(
470 &mut self,
471 tool_use_id: &str,
472 display_name: &str,
473 display_title: &str,
474 ) {
475 let index = self.messages.len();
476
477 let tool_data = ToolMessageData {
478 tool_use_id: tool_use_id.to_string(),
479 display_name: display_name.to_string(),
480 display_title: display_title.to_string(),
481 status: ToolStatus::Executing,
482 };
483
484 self.messages.push(Message::new_tool(tool_data));
485 self.tool_index.insert(tool_use_id.to_string(), index);
486
487 if self.auto_scroll_enabled {
489 self.scroll_offset = u16::MAX;
490 }
491 }
492
493 pub fn update_tool_status(&mut self, tool_use_id: &str, status: ToolStatus) {
495 if let Some(&index) = self.tool_index.get(tool_use_id) {
496 if let Some(msg) = self.messages.get_mut(index) {
497 if let Some(ref mut data) = msg.tool_data {
498 data.status = status;
499 msg.cached_lines = None; }
501 }
502 }
503 }
504
505 pub fn enable_auto_scroll(&mut self) {
507 self.auto_scroll_enabled = true;
508 self.scroll_offset = u16::MAX;
509 }
510
511 pub fn append_streaming(&mut self, text: &str) {
513 match &mut self.streaming_buffer {
514 Some(buffer) => buffer.push_str(text),
515 None => self.streaming_buffer = Some(text.to_string()),
516 }
517 self.streaming_cache = None;
519 self.streaming_cache_len = 0;
520 self.streaming_cache_width = 0;
521 if self.auto_scroll_enabled {
523 self.scroll_offset = u16::MAX;
524 }
525 }
526
527 pub fn complete_streaming(&mut self) {
529 if let Some(content) = self.streaming_buffer.take() {
530 if !content.trim().is_empty() {
531 self.messages
532 .push(Message::new(MessageRole::Assistant, content));
533 }
534 }
535 self.streaming_cache = None;
537 self.streaming_cache_len = 0;
538 self.streaming_cache_width = 0;
539 }
540
541 pub fn discard_streaming(&mut self) {
543 self.streaming_buffer = None;
544 self.streaming_cache = None;
546 self.streaming_cache_len = 0;
547 self.streaming_cache_width = 0;
548 }
549
550 pub fn is_streaming(&self) -> bool {
552 self.streaming_buffer.is_some()
553 }
554
555 pub fn scroll_up(&mut self) {
556 if self.scroll_offset == u16::MAX {
558 self.scroll_offset = self.last_max_scroll;
559 }
560 self.scroll_offset = self.scroll_offset.saturating_sub(3);
561 self.auto_scroll_enabled = false;
563 }
564
565 pub fn scroll_down(&mut self) {
566 if self.scroll_offset == u16::MAX {
568 return;
569 }
570 self.scroll_offset = self.scroll_offset.saturating_add(3);
571 if self.scroll_offset >= self.last_max_scroll {
573 self.scroll_offset = u16::MAX;
574 self.auto_scroll_enabled = true; }
576 }
577
578 pub fn render_chat(&mut self, frame: &mut Frame, area: Rect, pending_status: Option<&str>) {
579 let theme = app_theme();
580
581 let content_block = if let Some(ref render_fn) = self.render_title {
583 let (left_title, right_title) = render_fn(&self.title, &theme);
584 Block::default()
585 .title(left_title)
586 .title_alignment(Alignment::Left)
587 .title(right_title.alignment(Alignment::Right))
588 .borders(Borders::TOP)
589 .border_style(theme.border)
590 .padding(Padding::new(1, 0, 1, 0))
591 } else {
592 Block::default()
593 .borders(Borders::TOP)
594 .border_style(theme.border)
595 .padding(Padding::new(1, 0, 1, 0))
596 };
597
598 let is_initial_state = self.messages.is_empty() && self.streaming_buffer.is_none() && pending_status.is_none();
600
601 if is_initial_state {
603 if let Some(ref render_fn) = self.render_initial_content {
604 let inner = content_block.inner(area);
605 frame.render_widget(content_block, area);
606 render_fn(frame, inner, &theme);
607 return;
608 }
609 }
610
611 let available_width = area.width.saturating_sub(2) as usize; let mut message_lines: Vec<Line> = Vec::new();
616
617 if is_initial_state {
619 message_lines.push(Line::from(""));
620 message_lines.push(Line::from(Span::styled(
621 self.config.empty_message.clone(),
622 Style::default().fg(Color::DarkGray),
623 )));
624 }
625
626 for msg in &mut self.messages {
627 let cached = msg.get_rendered_lines(available_width, &self.config);
629 message_lines.extend(cached.iter().cloned());
630 }
631
632 if let Some(ref buffer) = self.streaming_buffer {
634 let buffer_len = buffer.len();
635
636 let cache_valid = self.streaming_cache.is_some()
638 && self.streaming_cache_len == buffer_len
639 && self.streaming_cache_width == available_width;
640
641 if !cache_valid {
642 let rendered = render_markdown_with_prefix(buffer, available_width, &theme);
644 self.streaming_cache = Some(rendered);
645 self.streaming_cache_len = buffer_len;
646 self.streaming_cache_width = available_width;
647 }
648
649 if let Some(ref cached) = self.streaming_cache {
651 message_lines.extend(cached.iter().cloned());
652 }
653
654 if let Some(last) = message_lines.last_mut() {
656 last.spans
657 .push(Span::styled("\u{2588}", theme.cursor));
658 }
659 } else if let Some(status) = pending_status {
660 let spinner_char = self.config.spinner_chars.get(self.spinner_index).copied().unwrap_or(' ');
662 message_lines.push(Line::from(vec![
663 Span::styled(format!("{} ", spinner_char), theme.throbber_spinner),
664 Span::styled(status, theme.throbber_label),
665 ]));
666 }
667
668 let available_height = area.height.saturating_sub(2) as usize; let total_lines = message_lines.len();
671 let max_scroll = total_lines.saturating_sub(available_height) as u16;
672 self.last_max_scroll = max_scroll;
673
674 let scroll_offset = if self.scroll_offset == u16::MAX {
682 max_scroll
683 } else {
684 let clamped = self.scroll_offset.min(max_scroll);
685 if clamped != self.scroll_offset {
686 self.scroll_offset = clamped;
687 }
688 clamped
689 };
690
691 let messages_widget = Paragraph::new(message_lines)
692 .block(content_block)
693 .style(theme.background.patch(theme.text))
694 .scroll((scroll_offset, 0));
695 frame.render_widget(messages_widget, area);
696 }
697}
698
699fn render_tool_message(
701 data: &ToolMessageData,
702 config: &ChatViewConfig,
703 available_width: usize,
704) -> Vec<Line<'static>> {
705 let mut lines = Vec::new();
706 let theme = app_theme();
707
708 let header = if data.display_title.is_empty() {
710 format!("{} {}", config.tool_icon, data.display_name)
711 } else {
712 format!("{} {}({})", config.tool_icon, data.display_name, data.display_title)
713 };
714 lines.push(Line::from(Span::styled(header, theme.tool_header)));
715
716 match &data.status {
718 ToolStatus::Executing => {
719 lines.push(Line::from(Span::styled(
720 format!(" {} executing...", config.tool_executing_arrow),
721 theme.tool_executing,
722 )));
723 }
724 ToolStatus::WaitingForUser => {
725 lines.push(Line::from(Span::styled(
726 format!(" {} waiting for user...", config.tool_executing_arrow),
727 theme.tool_executing,
728 )));
729 }
730 ToolStatus::Completed => {
731 lines.push(Line::from(Span::styled(
732 format!(" {} Completed", config.tool_completed_checkmark),
733 theme.tool_completed,
734 )));
735 }
736 ToolStatus::Failed(err) => {
737 let prefix = format!(" {} ", config.tool_failed_icon);
739 let cont_prefix = " "; let wrapped = wrap_with_prefix(
741 err,
742 &prefix,
743 theme.tool_failed,
744 cont_prefix,
745 available_width,
746 &theme,
747 );
748 lines.extend(wrapped);
749 }
750 }
751
752 lines
753}
754
755impl Default for ChatView {
756 fn default() -> Self {
757 Self::new()
758 }
759}
760
761use super::ConversationView;
764
765#[derive(Clone)]
767struct ChatViewState {
768 messages: Vec<MessageSnapshot>,
769 scroll_offset: u16,
770 streaming_buffer: Option<String>,
771 last_max_scroll: u16,
772 auto_scroll_enabled: bool,
773 tool_index: HashMap<String, usize>,
774 spinner_index: usize,
775}
776
777#[derive(Clone)]
779struct MessageSnapshot {
780 role: MessageRole,
781 content: String,
782 timestamp: DateTime<Local>,
783 tool_data: Option<ToolMessageData>,
784}
785
786impl From<&Message> for MessageSnapshot {
787 fn from(msg: &Message) -> Self {
788 Self {
789 role: msg.role,
790 content: msg.content.clone(),
791 timestamp: msg.timestamp,
792 tool_data: msg.tool_data.clone(),
793 }
794 }
795}
796
797impl From<MessageSnapshot> for Message {
798 fn from(snapshot: MessageSnapshot) -> Self {
799 Self {
800 role: snapshot.role,
801 content: snapshot.content,
802 timestamp: snapshot.timestamp,
803 cached_lines: None,
804 cached_width: 0,
805 tool_data: snapshot.tool_data,
806 }
807 }
808}
809
810impl ConversationView for ChatView {
811 fn add_user_message(&mut self, content: String) {
812 ChatView::add_user_message(self, content);
813 }
814
815 fn add_assistant_message(&mut self, content: String) {
816 ChatView::add_assistant_message(self, content);
817 }
818
819 fn add_system_message(&mut self, content: String) {
820 ChatView::add_system_message(self, content);
821 }
822
823 fn append_streaming(&mut self, text: &str) {
824 ChatView::append_streaming(self, text);
825 }
826
827 fn complete_streaming(&mut self) {
828 ChatView::complete_streaming(self);
829 }
830
831 fn discard_streaming(&mut self) {
832 ChatView::discard_streaming(self);
833 }
834
835 fn is_streaming(&self) -> bool {
836 ChatView::is_streaming(self)
837 }
838
839 fn add_tool_message(&mut self, tool_use_id: &str, display_name: &str, display_title: &str) {
840 ChatView::add_tool_message(self, tool_use_id, display_name, display_title);
841 }
842
843 fn update_tool_status(&mut self, tool_use_id: &str, status: ToolStatus) {
844 ChatView::update_tool_status(self, tool_use_id, status);
845 }
846
847 fn scroll_up(&mut self) {
848 ChatView::scroll_up(self);
849 }
850
851 fn scroll_down(&mut self) {
852 ChatView::scroll_down(self);
853 }
854
855 fn enable_auto_scroll(&mut self) {
856 ChatView::enable_auto_scroll(self);
857 }
858
859 fn render(&mut self, frame: &mut Frame, area: Rect, _theme: &Theme, pending_status: Option<&str>) {
860 self.render_chat(frame, area, pending_status);
861 }
862
863 fn step_spinner(&mut self) {
864 ChatView::step_spinner(self);
865 }
866
867 fn save_state(&self) -> Box<dyn Any + Send> {
868 let state = ChatViewState {
869 messages: self.messages.iter().map(MessageSnapshot::from).collect(),
870 scroll_offset: self.scroll_offset,
871 streaming_buffer: self.streaming_buffer.clone(),
872 last_max_scroll: self.last_max_scroll,
873 auto_scroll_enabled: self.auto_scroll_enabled,
874 tool_index: self.tool_index.clone(),
875 spinner_index: self.spinner_index,
876 };
877 Box::new(state)
878 }
879
880 fn restore_state(&mut self, state: Box<dyn Any + Send>) {
881 if let Ok(chat_state) = state.downcast::<ChatViewState>() {
882 self.messages = chat_state.messages.into_iter().map(Message::from).collect();
883 self.scroll_offset = chat_state.scroll_offset;
884 self.streaming_buffer = chat_state.streaming_buffer;
885 self.streaming_cache = None;
887 self.streaming_cache_len = 0;
888 self.streaming_cache_width = 0;
889 self.last_max_scroll = chat_state.last_max_scroll;
890 self.auto_scroll_enabled = chat_state.auto_scroll_enabled;
891 self.tool_index = chat_state.tool_index;
892 self.spinner_index = chat_state.spinner_index;
893 }
894 }
895
896 fn clear(&mut self) {
897 self.messages.clear();
898 self.streaming_buffer = None;
899 self.streaming_cache = None;
900 self.streaming_cache_len = 0;
901 self.streaming_cache_width = 0;
902 self.tool_index.clear();
903 self.scroll_offset = 0;
904 self.last_max_scroll = 0;
905 self.auto_scroll_enabled = true;
906 self.spinner_index = 0;
907 }
909}
910
911use std::any::Any;
914use crossterm::event::KeyEvent;
915use super::{widget_ids, Widget, WidgetKeyContext, WidgetKeyResult};
916
917impl Widget for ChatView {
918 fn id(&self) -> &'static str {
919 widget_ids::CHAT_VIEW
920 }
921
922 fn priority(&self) -> u8 {
923 50 }
925
926 fn is_active(&self) -> bool {
927 true }
929
930 fn handle_key(&mut self, _key: KeyEvent, _ctx: &WidgetKeyContext) -> WidgetKeyResult {
931 WidgetKeyResult::NotHandled
934 }
935
936 fn render(&mut self, frame: &mut Frame, area: Rect, _theme: &Theme) {
937 self.render_chat(frame, area, None);
940 }
941
942 fn required_height(&self, _available: u16) -> u16 {
943 0 }
945
946 fn blocks_input(&self) -> bool {
947 false
948 }
949
950 fn is_overlay(&self) -> bool {
951 false
952 }
953
954 fn as_any(&self) -> &dyn Any {
955 self
956 }
957
958 fn as_any_mut(&mut self) -> &mut dyn Any {
959 self
960 }
961
962 fn into_any(self: Box<Self>) -> Box<dyn Any> {
963 self
964 }
965}
966