wiki_tui/components/
search_bar.rs1use std::sync::Arc;
2
3use crossterm::event::KeyEvent;
4use ratatui::{
5 prelude::Rect,
6 style::{Color, Modifier, Style},
7 text::Text,
8};
9use tui_input::{backend::crossterm::EventHandler, Input};
10
11use crate::{
12 action::{Action, ActionResult, SearchAction},
13 config::{Config, Theme},
14 terminal::Frame,
15 ui::centered_rect,
16};
17
18use super::Component;
19
20const EMPTY_PROMPT: &str = "Search Wikipedia";
21const SEARCH_BAR_X: u16 = 50;
22
23pub const SEARCH_BAR_HEIGTH: u16 = 3;
24
25#[derive(Default)]
26pub struct SearchBarComponent {
27 input: Input,
28 config: Arc<Config>,
29 theme: Arc<Theme>,
30 pub is_focussed: bool,
31}
32
33impl SearchBarComponent {
34 pub fn clear(&mut self) {
35 self.input = Input::default();
36 }
37
38 pub fn submit(&self) -> Action {
39 Action::Search(SearchAction::StartSearch(self.input.value().to_string()))
40 }
41}
42
43impl Component for SearchBarComponent {
44 fn init(
45 &mut self,
46 _: tokio::sync::mpsc::UnboundedSender<Action>,
47 config: Arc<Config>,
48 theme: Arc<Theme>,
49 ) -> anyhow::Result<()> {
50 self.config = config;
51 self.theme = theme;
52 Ok(())
53 }
54
55 fn handle_key_events(&mut self, key: KeyEvent) -> ActionResult {
56 if self.config.bindings.global.submit.matches_event(key) {
57 return Action::SubmitSearchBar.into();
58 }
59
60 if self
61 .config
62 .bindings
63 .global
64 .exit_search_bar
65 .matches_event(key)
66 {
67 return Action::ExitSearchBar.into();
68 }
69
70 self.input.handle_event(&crossterm::event::Event::Key(key));
71 ActionResult::consumed()
72 }
73
74 fn render(&mut self, f: &mut Frame<'_>, area: Rect) {
75 let scroll = self.input.visual_scroll(area.width as usize);
76 let value = self.input.value();
77
78 let mut block = self.theme.default_block();
79 if self.is_focussed {
80 block = block.border_style(
81 Style::default()
82 .fg(self.theme.border_highlight_fg)
83 .bg(self.theme.border_highlight_bg),
84 )
85 }
86
87 let input = if value.is_empty() {
88 self.theme.default_paragraph(Text::styled(
89 EMPTY_PROMPT,
90 Style::default()
91 .fg(Color::Gray)
92 .add_modifier(Modifier::ITALIC),
93 ))
94 } else {
95 self.theme
96 .default_paragraph(self.input.value())
97 .scroll((0, scroll as u16))
98 }
99 .block(block);
100
101 let input_area = centered_rect(area, SEARCH_BAR_X, 100);
102 f.render_widget(input, input_area);
103 if self.is_focussed {
104 f.set_cursor_position((
105 input_area.x + ((self.input.visual_cursor()).max(scroll) - scroll) as u16 + 1,
107 input_area.y + 1,
109 ));
110 }
111 }
112}