diff --git a/src/modules/ui/app.rs b/src/modules/ui/app.rs index 6ed03d1..0366de4 100644 --- a/src/modules/ui/app.rs +++ b/src/modules/ui/app.rs @@ -1,16 +1,16 @@ -use crate::modules::source::{FileDatabase, WordSource}; +use crate::modules::source::{FileDatabase, Word, WordSource}; use crossterm::event::{ self, - Event::{self, Key}, + Event::{self}, }; use ratatui::{ DefaultTerminal, Frame, layout::{Constraint, Direction, Flex, Layout}, style::{Color, Style}, text::Text, - widgets::{Block, Borders, Padding, Paragraph}, + widgets::{Block, Borders, Paragraph}, }; -use std::{io, ops::Index}; +use std::io; #[derive(PartialEq)] pub enum UiState { @@ -26,6 +26,7 @@ pub struct App { state: UiState, file_database: FileDatabase, scroll_position: usize, + word_list: Vec, word_filter: Option, lang_filter: Option, exit: bool, @@ -33,10 +34,13 @@ pub struct App { impl App { pub fn new(db: FileDatabase) -> Self { + let wl = db.get_all_words(); + Self { state: UiState::LIST, file_database: db, scroll_position: 0, + word_list: wl, word_filter: None, lang_filter: None, exit: false, @@ -105,25 +109,13 @@ impl App { filters_layout[1], ); - // FIXME: this is wrong, the list must reside in memory and only be updated when required, - // this makes a new call every frame, insane - let word_list = { - if let Some(w_filter) = &self.word_filter - && let Some(l_filter) = &self.lang_filter - { - self.file_database - .find_by_word_and_lang(&word_filter, &l_filter) - } else if let Some(l_filter) = &self.lang_filter { - self.file_database.find_by_lang(&l_filter) - } else { - self.file_database.get_all_words() - } - }; frame.render_widget( Paragraph::new("Word list").block(Block::new().borders(Borders::ALL).title("Entries")), words_layout[0], ); - let selected_word = word_list.into_iter().nth(self.scroll_position); + + // PERF: do not clone + let selected_word = self.word_list.clone().into_iter().nth(self.scroll_position); if let Some(w) = selected_word { frame.render_widget( Paragraph::new(w.description).block(Block::new().borders(Borders::ALL)), @@ -141,6 +133,25 @@ impl App { frame.render_widget(help_text, help_area); } + fn update_word_list(&mut self) { + let word_list = { + if let Some(w_filter) = &self.word_filter + && let Some(l_filter) = &self.lang_filter + { + self.file_database + .find_by_word_and_lang(&w_filter, &l_filter) + } else if let Some(w_filter) = &self.word_filter { + self.file_database.find_by_word(&w_filter) + } else if let Some(l_filter) = &self.lang_filter { + self.file_database.find_by_lang(&l_filter) + } else { + self.file_database.get_all_words() + } + }; + self.scroll_position = 0; + self.word_list = word_list; + } + fn handle_events(&mut self) -> io::Result<()> { if let Event::Key(key) = event::read()? { match key.code { @@ -153,6 +164,7 @@ impl App { } else { self.word_filter = Some(c.to_string()); } + self.update_word_list(); } UiState::FILTERLANG => { if let Some(mut prev_lang) = self.lang_filter.clone() { @@ -161,6 +173,7 @@ impl App { } else { self.lang_filter = Some(c.to_string()); } + self.update_word_list(); } _ => { // Quit @@ -185,14 +198,34 @@ impl App { } // Movement if c == 'j' { - self.scroll_position -= 1; + if self.scroll_position > 0 { + self.scroll_position -= 1; + } } if c == 'k' { - self.scroll_position += 1; + if self.scroll_position < self.word_list.len() - 1 { + self.scroll_position += 1; + } } } } } + event::KeyCode::Backspace => match self.state { + UiState::FILTERWORD => { + if let Some(w) = &self.word_filter { + if w.len() <= 1 { + self.word_filter = None; + } else { + let mut new_w = w.clone(); + new_w.pop(); + self.word_filter = Some(new_w); + } + self.update_word_list(); + } + } + UiState::FILTERLANG => {} + _ => {} + }, event::KeyCode::Esc => { self.state = UiState::LIST; }