Skip to main content

wiki_tui/components/
page_language_popup.rs

1use std::sync::Arc;
2
3use crossterm::event::KeyCode;
4use ratatui::{
5    layout::{Constraint, Direction, Layout, Rect},
6    style::{Modifier, Style, Stylize},
7    widgets::{Clear, List, ListItem},
8};
9use tui_input::{backend::crossterm::EventHandler, Input};
10use wiki_api::page::LanguageLink;
11
12use crate::{
13    action::{Action, ActionPacket, ActionResult},
14    config::{Config, Theme},
15    terminal::Frame,
16    ui::{centered_rect, StatefulList},
17};
18
19use super::Component;
20
21const FOCUS_INPUT: u8 = 0;
22const FOCUS_LIST: u8 = 1;
23
24pub struct PageLanguageSelectionComponent {
25    input: Input,
26    focus: u8,
27    list: StatefulList<LanguageLink>,
28    language_links: Vec<LanguageLink>,
29
30    config: Arc<Config>,
31    theme: Arc<Theme>,
32}
33
34impl PageLanguageSelectionComponent {
35    pub fn new(language_links: Vec<LanguageLink>, config: Arc<Config>, theme: Arc<Theme>) -> Self {
36        Self {
37            input: Input::default(),
38            list: StatefulList::with_items(language_links.clone()),
39            language_links,
40            focus: 0,
41
42            config,
43            theme,
44        }
45    }
46
47    fn update_list(&mut self) {
48        let input_value = self.input.value();
49        let sorted_languages = self
50            .language_links
51            .iter()
52            .filter(|lang_link| {
53                let lang = lang_link.language.name().to_lowercase();
54                let query = input_value.to_lowercase();
55                lang.contains(&query)
56            })
57            .map(|x| x.to_owned())
58            .collect::<Vec<LanguageLink>>();
59        self.list = StatefulList::with_items(sorted_languages);
60    }
61}
62
63impl Component for PageLanguageSelectionComponent {
64    fn handle_key_events(&mut self, key: crossterm::event::KeyEvent) -> ActionResult {
65        if self.config.bindings.global.submit.matches_event(key) {
66            if let Some(link) = self.list.selected() {
67                let mut packet = ActionPacket::single(Action::PopPopup);
68                if self.config.ui.popup_page_language_changed {
69                    packet = packet.action(Action::PopupMessage(
70                        "Information".to_string(),
71                        format!(
72                            "Changing the language of the page to '{}'",
73                            link.language.name()
74                        ),
75                    ));
76                }
77                return packet
78                    .action(Action::LoadLangaugeLink(link.to_owned()))
79                    .into();
80            }
81            return ActionResult::Ignored;
82        }
83
84        if self.config.bindings.global.pop_popup.matches_event(key)
85            || self
86                .config
87                .bindings
88                .page
89                .toggle_page_language_selection
90                .matches_event(key)
91        {
92            return Action::PopPopup.into();
93        }
94
95        match key.code {
96            KeyCode::Tab | KeyCode::BackTab => {
97                if self.focus == FOCUS_INPUT {
98                    self.focus = FOCUS_LIST;
99                } else if self.focus == FOCUS_LIST {
100                    self.focus = FOCUS_INPUT;
101                }
102
103                tracing::debug!("focus now: '{}'", self.focus);
104
105                ActionResult::consumed()
106            }
107            KeyCode::Char('i') if self.focus == FOCUS_LIST => {
108                self.focus = FOCUS_INPUT;
109                ActionResult::consumed()
110            }
111            _ if self.focus == FOCUS_INPUT => {
112                self.input.handle_event(&crossterm::event::Event::Key(key));
113                self.update_list();
114                ActionResult::consumed()
115            }
116            _ => ActionResult::Ignored,
117        }
118    }
119
120    fn update(&mut self, action: Action) -> ActionResult {
121        match action {
122            Action::ScrollUp(n) => {
123                for _ in 0..n {
124                    self.list.previous()
125                }
126                ActionResult::consumed()
127            }
128            Action::ScrollDown(n) => {
129                for _ in 0..n {
130                    self.list.next()
131                }
132                ActionResult::consumed()
133            }
134            Action::UnselectScroll => {
135                self.list.unselect();
136                ActionResult::consumed()
137            }
138            _ => ActionResult::Ignored,
139        }
140    }
141
142    fn render(&mut self, f: &mut Frame<'_>, area: Rect) {
143        let popup_block = self
144            .theme
145            .default_block()
146            .title("Switch Page Language")
147            .style(Style::default().bg(self.theme.bg));
148        let area = centered_rect(area, 25, 60);
149        f.render_widget(Clear, area);
150        f.render_widget(popup_block, area);
151
152        let (input_area, list_area) = {
153            let chunks = Layout::default()
154                .direction(Direction::Vertical)
155                .margin(1)
156                .constraints([Constraint::Length(1), Constraint::Percentage(100)])
157                .split(area);
158            (chunks[0], chunks[1])
159        };
160
161        let scroll = self.input.visual_scroll(input_area.width as usize);
162        let cursor = self.input.visual_cursor();
163        let value = self.input.value();
164
165        let input_widget = self
166            .theme
167            .default_paragraph(format!(
168                "{}{}",
169                value,
170                "_".repeat((input_area.width as usize).saturating_sub(value.len()))
171            ))
172            .scroll((0, scroll as u16));
173        f.render_widget(input_widget, input_area);
174
175        if self.focus == FOCUS_INPUT {
176            f.set_cursor_position((
177                input_area.x + (cursor.max(scroll) - scroll) as u16,
178                input_area.y,
179            ));
180        }
181
182        let list_items = self
183            .list
184            .get_items()
185            .iter()
186            .map(|x| ListItem::new(x.language.name().to_owned()).fg(self.theme.fg));
187        let list_widget = List::new(list_items).highlight_style(if self.focus == FOCUS_LIST {
188            Style::default()
189                .fg(self.theme.selected_fg)
190                .bg(self.theme.selected_bg)
191                .add_modifier(Modifier::ITALIC)
192        } else {
193            Style::default()
194        });
195        f.render_stateful_widget(list_widget, list_area, self.list.get_state_mut());
196    }
197}