Skip to content

Commit

Permalink
Merge pull request #1 from onelikeandidie/word-history
Browse files Browse the repository at this point in the history
New word history feature
  • Loading branch information
onelikeandidie authored Oct 1, 2022
2 parents 9eaa91b + 3b97ea2 commit c4be006
Show file tree
Hide file tree
Showing 12 changed files with 272 additions and 93 deletions.
2 changes: 1 addition & 1 deletion Cargo.lock

Some generated files are not rendered by default. Learn more about how customized files appear on GitHub.

8 changes: 7 additions & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,8 +2,14 @@
name = "typo-eq"
description = "Typo-eq is a typing training app for other languages. All it needs is a dictionary for words and their translations."
license = "GPL-3.0"
version = "0.1.1"
version = "0.2.0"
edition = "2021"
repository = "https://github.com/onelikeandidie/typo-eq"

[lib]
name = "typo_eq"
path = "src/lib.rs"
crate-type = ["lib"]

# See more keys and their definitions at https://doc.rust-lang.org/cargo/reference/manifest.html

Expand Down
2 changes: 1 addition & 1 deletion README.md
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ cargo run -- --dict path/to/xdxf/file

A short loading screen should appear as your dictionary is loaded. Bigger
dictionaries typically take longer (_obvio_), the Svenska-English dictionary
from the Swedish People's Dictionary takes around 1.06 seconds.
from the Swedish People's Dictionary takes around 1.32 seconds.

## Screenshots

Expand Down
2 changes: 1 addition & 1 deletion TODO.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

- [x] Import dictionary from XDXF (XML Dictionary eXchange Format)
- [x] Render a random word to screen
- [ ] Show progress on said word
- [x] Show progress on said word
- [x] Handle Mistakes
- [ ] Difficulty Levels
- [ ] Challenges?
Expand Down
Binary file modified docs/screenshot01.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshot02.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
Binary file modified docs/screenshot03.png
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
212 changes: 141 additions & 71 deletions src/app/mod.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,10 +3,8 @@ use std::io::{stdout, Write};
use std::sync::mpsc;
use std::thread::{self, sleep};
use std::time::Duration;
use crossterm::cursor::{MoveLeft, MoveToColumn, MoveToPreviousLine, MoveToNextLine};
use crossterm::event::{read, Event, KeyCode, KeyEvent, KeyModifiers};
use crossterm::style::{SetForegroundColor, Color, ResetColor};
use crossterm::{self, execute, ExecutableCommand};
use crossterm::style::Color;
use crossterm::terminal::{Clear, ClearType};
use rand::distributions::Uniform;
use rand::prelude::Distribution;
Expand All @@ -30,10 +28,8 @@ pub const SKIP_CHARACTERS: [char; 2] = [
'/', '|'
];

fn zero() -> (u16, u16) {(0, 0)}

pub fn create_app(config: Config) {
let mut stdout = stdout();
let stdout = stdout();

let renderer = Renderer::init();

Expand Down Expand Up @@ -65,20 +61,24 @@ pub fn create_app(config: Config) {
(Utc::now().timestamp_millis() - load_time) as f64 / 1000.0
).as_str());
}
_ => {},
}
}
let dict = dict.expect("Could not load dictionary");

let mut state = State::default();

sleep(Duration::from_secs(1));
sleep(Duration::from_millis(500));
// Show dictionary loaded
renderer.print_at_center(
format!("{} -> {}", dict.from, dict.to).as_str(),
(0, -6), None, None, None, None,
);
let mut old_words: Vec<Word> = Vec::new();
let mut word = new_word(&dict);
renderer.print_at_center_default(format!(
"{} -> {}",
word.original,
word.translation
).as_str());
renderer.clear_line_at_center((0,0));
render_translations(&renderer, &word);
render_center(&renderer, &word, &state);
render_cursor(&renderer, &word, &state);

while let Ok(event) = read() {
match event {
Expand All @@ -96,14 +96,8 @@ pub fn create_app(config: Config) {
code: KeyCode::Backspace,
..
}) => {
if state.progress == 0 {
continue;
}
state.progress -= 1;
state.failed = false;
stdout.execute(MoveLeft(1)).unwrap();
print!(" ");
stdout.execute(MoveToColumn(state.progress as u16)).unwrap();
render_center(&renderer, &word, &state);
}
Event::Key(KeyEvent {
code: KeyCode::Char(c),
Expand All @@ -118,82 +112,43 @@ pub fn create_app(config: Config) {
if (SKIP_CHARACTERS.contains(current_char) && !c.is_alphanumeric())
// Progress if the character input was correct
|| current_char == &c {
if state.failed {
// Move cursor back and write the right char
stdout.execute(MoveLeft(1)).unwrap();
}
renderer.print_at_center(
format!("{}", c).as_str(),
(state.progress as i16 - (word.size / 2) as i16, 2),
None, None, None,
None
);
state.progress += 1;
state.failed = false;
} else {
if !state.failed {
renderer.print_at_center(
format!("{}", current_char).as_str(),
(state.progress as i16 - (word.size / 2) as i16, 2),
None, Some(Color::DarkRed), None,
None
);
}
state.failed = true;
state.stats.chars_failed += 1;
}
state.stats.chars_typed += 1;
stdout.lock().flush().unwrap();
}
// Update progress display
renderer.print_at_center(
format!("{}/{}", state.progress, word.size).as_str(),
(2, -2), Some(TextAlign::Left),
Some(Color::DarkGrey), None,
None
);
// Update wpm display
let current_timestamp = Utc::now().timestamp_millis();
let diff = current_timestamp - state.last_word_timestamp;
state.wpm = 1.0 / (diff as f64 / 1000.0 / 60.0);
renderer.print_at_center(
format!("{} wpm", state.wpm.round()).as_str(),
(-2, -2), Some(TextAlign::Right),
Some(Color::DarkYellow), None,
None
);
Cursor::move_to_center(((state.progress as i16 - (word.size / 2) as i16), 2));
render_center(&renderer, &word, &state);
if state.progress >= word.size {
// Update last word completed timestamp
state.last_word_timestamp = Utc::now().timestamp_millis();
state.stats.completed += 1;
// Show the completed word in grey
renderer.print_at_center(
format!(
"{} -> {}",
word.original,
word.translation
).as_str(), (0, -4),
None, Some(Color::DarkGrey), None,
Some(Clear(ClearType::CurrentLine)),
);
// Add last word to the book of words
old_words.push(word);
if old_words.len() >= 5 {
old_words.remove(0);
}
render_completed_words(&renderer, &old_words);
// Clear the user input
renderer.clear_line_at_center((0, 2));
// New word
word = new_word(&dict);
state.progress = 0;
renderer.print_at_center_default(format!(
"{} -> {}",
word.original,
word.translation
).as_str());
Cursor::move_to_center((0, 2));
renderer.clear_line_at_center((0,0));
render_center(&renderer, &word, &state);
render_translations(&renderer, &word);
}
}
_ => {},
}
render_cursor(&renderer, &word, &state);
}

// Update wpm
let current_timestamp = Utc::now().timestamp_millis();
let diff = current_timestamp - state.started_at;
Expand Down Expand Up @@ -223,17 +178,132 @@ pub fn create_app(config: Config) {
Cursor::move_to_center((0, 8));
}

pub fn render_center(renderer: &Renderer, word: &Word, state: &State) {
// Update progress display
renderer.print_at_center(
format!("{}/{}", state.progress, word.size).as_str(),
(word.size as i16 / 2 + 4, 0), Some(TextAlign::Left),
Some(Color::DarkGrey), None,
None
);
// Update wpm display
renderer.print_at_center(
format!("{} wpm", state.wpm.round()).as_str(),
(- (word.size as i16 / 2) - 4, 0), Some(TextAlign::Right),
Some(Color::DarkYellow), None,
None
);
// Update word shown
let left = word.original_chars[..state.progress].into_iter().collect::<String>();
let right = word.original_chars[state.progress..].into_iter().collect::<String>();
let fail_char = word.original_chars.get(state.progress).unwrap_or(&' ');
let left_x = - (word.size as i16 / 2);
let right_x = left_x + state.progress as i16;
renderer.print_at_center(
left.as_str(),
(left_x, 0), Some(TextAlign::Left),
Some(Color::DarkGreen), None, None
);
renderer.print_at_center(
right.as_str(),
(right_x, 0), Some(TextAlign::Left),
None, None, None
);
if state.failed {
renderer.print_at_center(
format!("{}", fail_char).as_str(),
(right_x, 0), Some(TextAlign::Left),
Some(Color::DarkRed), None, None
);
}
}

pub fn new_word(dict: &Dictionary) -> Word {
let mut rng = thread_rng();
let distribuition = Uniform::new(0, dict.entries.len());
let word_index = distribuition.sample(&mut rng);
let word = dict.entries.get(word_index);
let word = dict.words.get(word_index);
if let Some(word) = word {
return Word {
size: word.identifier.chars().count(),
original: word.identifier.clone(),
original_chars: word.identifier.chars().collect(),
translation: word.translation.clone(),
}
}
panic!("Word could not be selected, out of bounds");
}

pub fn render_completed_words(renderer: &Renderer, words: &Vec<Word>) {
for i in 0..(words.len()) {
let word = words.get((words.len() - 1) - i).unwrap();
renderer.clear_line_at_center((0, -2 - i as i16));
// Show the completed word in grey
renderer.print_at_center(
format!("{}", word.original).as_str(),
(-2, -2 - i as i16),
Some(TextAlign::Right), Some(Color::DarkGrey), None,
None,
);
renderer.print_at_center(
"->",
(0, -2 - i as i16),
None, Some(Color::DarkGrey), None,
None,
);
renderer.print_at_center(
format!(
"{}",
word.translation.first().unwrap_or(&"no translation".to_string())
).as_str(),
(2, -2 - i as i16),
Some(TextAlign::Left), Some(Color::DarkGrey), None,
None,
);
}
}

pub fn render_translations(renderer: &Renderer, word: &Word) {
renderer.clear_down_from_center_at(2);
for i in 0..(word.translation.len()) {
let translation = word.translation.get(i).unwrap();
// Show the completed word in grey
renderer.print_at_center(
format!(
"{}",
translation
).as_str(), (0, 2 + i as i16),
None, None, None,
None,
);
}
}

pub fn render_new_word(renderer: &Renderer, word: &Word) {
renderer.print_at_center(
format!(
"{}",
word.original,
).as_str(), (0,0), None,
None, None,
Some(Clear(ClearType::CurrentLine))
);
}

pub fn render_cursor(renderer: &Renderer, word: &Word, state: &State) {
renderer.print_at_center(
"^", (get_progress_cursor(word, state), 1),
None,
Some(Color::DarkYellow), None,
Some(Clear(ClearType::CurrentLine))
);
}

pub fn get_progress_cursor(word: &Word, state: &State) -> i16 {
state.progress as i16 - (word.size / 2) as i16
}

pub fn reset_cursor(word: &Word, state: &State) {
let new_cursor_pos_x = get_progress_cursor(word, state) + (if state.failed {1} else {0});
Cursor::move_to_center((new_cursor_pos_x, 0));
}
13 changes: 12 additions & 1 deletion src/app/render.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
use std::io::{stdout, Write};

use crossterm::{terminal::{self, EnterAlternateScreen, enable_raw_mode, disable_raw_mode, LeaveAlternateScreen, Clear, ClearType}, style::{Color, SetForegroundColor, ResetColor}, execute, cursor::{MoveTo, position}, event::{PushKeyboardEnhancementFlags, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags}};
use crossterm::{terminal::{EnterAlternateScreen, enable_raw_mode, disable_raw_mode, LeaveAlternateScreen, Clear, ClearType}, style::{Color, SetForegroundColor, ResetColor}, execute, cursor::{MoveTo, position, MoveToRow, Hide, Show}, event::{PushKeyboardEnhancementFlags, KeyboardEnhancementFlags, PopKeyboardEnhancementFlags}};

use super::util::term_center;

Expand All @@ -22,6 +22,7 @@ impl Renderer {
execute!(
stdout,
EnterAlternateScreen,
Hide,
PushKeyboardEnhancementFlags(
KeyboardEnhancementFlags::DISAMBIGUATE_ESCAPE_CODES
| KeyboardEnhancementFlags::REPORT_ALL_KEYS_AS_ESCAPE_CODES
Expand All @@ -35,6 +36,7 @@ impl Renderer {
execute!(
stdout,
PopKeyboardEnhancementFlags,
Show,
LeaveAlternateScreen,
).unwrap();

Expand Down Expand Up @@ -134,6 +136,15 @@ impl Renderer {
),
)
}
pub fn clear_down_from_center_at(self: &Self, offset_y: i16) {
let center = term_center();
let mut stdout = stdout();
execute!(
stdout,
MoveToRow((center.1 as i16 + offset_y) as u16),
Clear(ClearType::FromCursorDown)
).expect("Could not clear lines down from center")
}
pub fn clear_line_at(self: &Self, position: (u16, u16)) {
let mut stdout = stdout();
execute!(
Expand Down
Loading

0 comments on commit c4be006

Please sign in to comment.