aboutsummaryrefslogtreecommitdiff
path: root/src/gui/question.rs
diff options
context:
space:
mode:
Diffstat (limited to 'src/gui/question.rs')
-rw-r--r--src/gui/question.rs188
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> {