diff options
Diffstat (limited to 'src/gui/question.rs')
-rw-r--r-- | src/gui/question.rs | 188 |
1 files changed, 110 insertions, 78 deletions
diff --git a/src/gui/question.rs b/src/gui/question.rs index 2aa6e65..512ca49 100644 --- a/src/gui/question.rs +++ b/src/gui/question.rs @@ -1,17 +1,16 @@ use crate::{ gui::util, model::{difficulty, difficulty::Difficulty, Card}, - util::event::{Event, Events}, util::serialization, }; use anyhow::Result; -use termion::event::Key; +use crossterm::event::{self, Event, KeyCode, KeyModifiers}; use tui::{ backend::Backend, layout::{Alignment, Constraint, Direction, Layout}, style::{Color, Modifier, Style}, - text::{Span, Spans, Text}, - widgets::{Block, Borders, Paragraph, Wrap}, + text::{Line, Span, Text}, + widgets::{Paragraph, Wrap}, Terminal, }; @@ -30,12 +29,7 @@ pub enum Response { Answered { difficulty: Difficulty }, } -pub fn ask<B: Backend>( - terminal: &mut Terminal<B>, - events: &Events, - title: &str, - card: &Card, -) -> Result<Response> { +pub fn ask<B: Backend>(terminal: &mut Terminal<B>, title: &str, card: &Card) -> Result<Response> { let mut state = State { input: String::new(), answer: Answer::Write, @@ -62,16 +56,6 @@ pub fn ask<B: Backend>( f.render_widget(d1, chunks[0]); let question = Paragraph::new(util::center_vertically(chunks[1], &card.question)) - .style(match state.answer { - Answer::Write => { - if state.input.trim().is_empty() { - Style::default().fg(Color::Yellow) - } else { - Style::default() - } - } - _ => Style::default(), - }) .alignment(Alignment::Center); f.render_widget(question, chunks[1]); @@ -83,15 +67,15 @@ pub fn ask<B: Backend>( .style(match state.answer { Answer::Write => Style::default(), Answer::Difficulty { difficulty: _ } => { - if is_correct(&state.input, &card.responses) { - Style::default().fg(Color::Green) - } else { - Style::default().fg(Color::Red) + match check_response(&state.input, &card.responses) { + CheckResponse::Correct { phonetics: _ } => { + Style::default().fg(Color::Green) + } + CheckResponse::Incorrect => Style::default().fg(Color::Red), } } }) .alignment(Alignment::Center) - .block(Block::default().borders(Borders::ALL).title("RĂ©ponse")) .wrap(Wrap { trim: true }); f.render_widget(answer, chunks[2]); @@ -99,12 +83,17 @@ pub fn ask<B: Backend>( difficulty: selected, } = state.answer { - if !is_correct(&state.input, &card.responses) { - let paragraph = Paragraph::new(util::center_vertically( - chunks[3], - &serialization::words_to_line(&card.responses), - )) - .alignment(Alignment::Center); + let maybe_indication: Option<String> = + match check_response(&state.input, &card.responses) { + CheckResponse::Correct { phonetics } => phonetics, + CheckResponse::Incorrect => { + Some(serialization::words_to_line(&card.responses)) + } + }; + + if let Some(indication) = maybe_indication { + let paragraph = Paragraph::new(util::center_vertically(chunks[3], &indication)) + .alignment(Alignment::Center); f.render_widget(paragraph, chunks[3]); }; @@ -131,73 +120,84 @@ pub fn ask<B: Backend>( }) .collect::<Vec<Vec<Span>>>() .concat(); - let p = Paragraph::new(Text::from(Spans::from(tabs))).alignment(Alignment::Center); + let p = Paragraph::new(Text::from(Line::from(tabs))).alignment(Alignment::Center); f.render_widget(p, chunks[4]); } })?; - if let Event::Input(key) = events.next()? { + if let Event::Key(key) = event::read()? { match state.answer { - Answer::Write => match key { - Key::Char('\n') => { - let difficulty = if is_correct(&state.input, &card.responses) { + Answer::Write => match key.code { + KeyCode::Enter => { + let difficulty = if state.input.is_empty() { + // Encourage solving without typing by defaulting to good answer Difficulty::Good } else { - Difficulty::Again + match check_response(&state.input, &card.responses) { + CheckResponse::Correct { phonetics: _ } => Difficulty::Good, + CheckResponse::Incorrect => Difficulty::Again, + } }; state.answer = Answer::Difficulty { difficulty } } - Key::Char(c) => { - state.input.push(c); - if is_correct(&state.input, &card.responses) { - state.answer = Answer::Difficulty { - difficulty: Difficulty::Good, + KeyCode::Char(c) => { + if key.modifiers.contains(KeyModifiers::CONTROL) { + if c == 'u' { + state.input.clear(); + } else if c == 'w' { + let mut words = + state.input.split_whitespace().collect::<Vec<&str>>(); + if !words.is_empty() { + words.truncate(words.len() - 1); + let joined_words = words.join(" "); + let space = if !words.is_empty() { " " } else { "" }; + state.input = format!("{joined_words}{space}"); + } + } else if c == 'c' { + return Ok(Response::Aborted); + } + } else { + state.input.push(c); + if let CheckResponse::Correct { phonetics: _ } = + check_response(&state.input, &card.responses) + { + state.answer = Answer::Difficulty { + difficulty: Difficulty::Good, + } } } } - Key::Backspace => { + KeyCode::Backspace => { state.input.pop(); } - Key::Ctrl('u') => { - state.input.clear(); - } - Key::Ctrl('w') => { - let mut words = state.input.split_whitespace().collect::<Vec<&str>>(); - if !words.is_empty() { - words.truncate(words.len() - 1); - state.input = format!( - "{}{}", - words.join(" "), - if !words.is_empty() { " " } else { "" } - ); - } - } - Key::Ctrl('c') => { - return Ok(Response::Aborted); - } _ => {} }, Answer::Difficulty { difficulty: selected, - } => match key { - Key::Left => { - for d in relative_element(&card.state.difficulties(), &selected, -1).iter() + } => match key.code { + KeyCode::Left => { + if let Some(difficulty) = + relative_element(&card.state.difficulties(), &selected, -1) { - state.answer = Answer::Difficulty { difficulty: *d } + state.answer = Answer::Difficulty { difficulty } } } - Key::Right => { - for d in relative_element(&card.state.difficulties(), &selected, 1).iter() { - state.answer = Answer::Difficulty { difficulty: *d } + KeyCode::Right => { + if let Some(difficulty) = + relative_element(&card.state.difficulties(), &selected, 1) + { + state.answer = Answer::Difficulty { difficulty } } } - Key::Char('\n') => { + KeyCode::Enter => { return Ok(Response::Answered { difficulty: selected, }) } - Key::Ctrl('c') => { - return Ok(Response::Aborted); + KeyCode::Char('c') => { + if key.modifiers.contains(KeyModifiers::CONTROL) { + return Ok(Response::Aborted); + } } _ => {} }, @@ -206,18 +206,50 @@ pub fn ask<B: Backend>( } } -fn is_correct(input: &str, responses: &[String]) -> bool { - // Remove whitespaces - let input = input +enum CheckResponse { + Incorrect, + Correct { phonetics: Option<String> }, +} + +fn check_response(input: &str, responses: &[String]) -> CheckResponse { + let input = remove_whitespaces(input); + + responses + .iter() + .find(|r| remove_indications_and_phonetics(r) == input) + .map(|r| CheckResponse::Correct { + phonetics: extract_phonetics(r), + }) + .unwrap_or(CheckResponse::Incorrect) +} + +fn remove_whitespaces(input: &str) -> String { + input .split_whitespace() .map(|word| word.trim()) .collect::<Vec<&str>>() - .join(" "); + .join(" ") +} - responses - .iter() - .map(|r| r.split('(').collect::<Vec<&str>>()[0].trim()) - .any(|x| x == input) +fn remove_indications_and_phonetics(response: &str) -> &str { + response + .split(|c| c == '(' || c == '[') + .collect::<Vec<&str>>()[0] + .trim() +} + +fn extract_phonetics(response: &str) -> Option<String> { + let s1 = response.split('[').collect::<Vec<&str>>(); + if s1.len() == 2 { + let s2 = s1[1].split(']').collect::<Vec<&str>>(); + if s2.len() > 1 { + Some(format!("[{}]", s2[0])) + } else { + None + } + } else { + None + } } fn relative_element<T: Clone + PartialEq>(xs: &[T], x: &T, ri: i32) -> Option<T> { |