Skip to content

Commit

Permalink
feat: multiple dictionaries & screens
Browse files Browse the repository at this point in the history
  • Loading branch information
ravsii committed Nov 27, 2024
1 parent 8eaad98 commit 4685b8c
Show file tree
Hide file tree
Showing 11 changed files with 94 additions and 78 deletions.
3 changes: 2 additions & 1 deletion .gitignore
Original file line number Diff line number Diff line change
@@ -1,2 +1,3 @@
/target
.vscode
.vscode
**/.DS_Store
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.

2 changes: 1 addition & 1 deletion Cargo.toml
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@
name = "qtype"
description = "Terminal-based typing speed practice app"
authors = ["ravsii"]
version = "0.1.2"
version = "0.2.0"
edition = "2021"
readme = "README.md"
homepage = "https://github.com/ravsii/qType"
Expand Down
2 changes: 2 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -41,6 +41,8 @@ cargo install qtype

### **Planned**

- **Difficulties**: Using different samples from dictionaries (based on usage),
allowing to practice on the most common words.
- **Custom Dictionaries**: Support for user-defined dictionaries and additional
languages.
- **Accurate WPM/CPM Calculation**: Improved metrics for better accuracy and
Expand Down
Binary file modified img/demo.gif
Loading
Sorry, something went wrong. Reload?
Sorry, we cannot display this file.
Sorry, this file is invalid so it cannot be displayed.
25 changes: 15 additions & 10 deletions src/app.rs
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
use std::{cell::RefCell, io, rc::Rc};

use crate::{
dict::Dictionary,
dict::{Dictionary, Language},
event::Event,
screens::{select_dict::SelectDictScreen, typing::TypingScreen, Opts, Screen},
screens::{select_dict::SelectDictScreen, typing::TypingScreen, Screen},
};
use crossterm::event::{self, KeyCode, KeyEventKind, KeyModifiers};
use ratatui::{
Expand All @@ -14,21 +14,20 @@ use ratatui::{
};

pub struct App {
dict: Rc<RefCell<Dictionary>>,
screen: Screen,
typing_screen: TypingScreen,
select_dict_screen: SelectDictScreen,
}

impl App {
pub fn new(dict: Dictionary) -> Result<Self, std::io::Error> {
pub fn new() -> Result<Self, std::io::Error> {
// TODO: I don't know if this is the right way to do it, but it works for now.
let dict = Rc::new(RefCell::new(dict));
let dict = Rc::new(RefCell::new(Dictionary::new(Language::English)?));

let typing_screen = TypingScreen::new(&dict);
let select_dict_screen = SelectDictScreen::new(&dict);

Ok(Self {
dict,
screen: Screen::Typing,
typing_screen,
select_dict_screen,
Expand All @@ -41,7 +40,13 @@ impl App {
match self.handle_key()? {
Event::DoNothing => continue,
Event::Quit => return Ok(()),
Event::Switch(_) => todo!(),
Event::Switch(to) => {
if to == Screen::Typing {
self.typing_screen.randomize_input();
}

self.screen = to
}
}
}
}
Expand All @@ -54,11 +59,11 @@ impl App {
match self.screen {
Screen::Typing => {
frame.render_widget(&self.typing_screen, screen_area);
bottom_bar_opts.extend(TypingScreen::custom_options());
bottom_bar_opts.extend(TypingScreen::actions());
}
Screen::Dicts => {
frame.render_widget(&mut self.select_dict_screen, screen_area);
bottom_bar_opts.extend(SelectDictScreen::custom_options());
bottom_bar_opts.extend(SelectDictScreen::actions());
}
}

Expand Down Expand Up @@ -87,7 +92,7 @@ impl App {

match self.screen {
Screen::Typing => Ok(self.typing_screen.handle_key(key)),
Screen::Dicts => todo!(),
Screen::Dicts => Ok(self.select_dict_screen.handle_key(key)),
}
} else {
Ok(Event::DoNothing)
Expand Down
17 changes: 9 additions & 8 deletions src/dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -2,17 +2,16 @@ use rand::seq::SliceRandom;
use std::io::{self};
use strum::EnumIter;

#[derive(Debug, Clone, Copy, EnumIter)]
#[derive(Debug, Clone, Copy, EnumIter, Default)]
pub enum Language {
None,
#[default]
English,
Russian,
}

impl Language {
pub fn as_str(&self) -> &'static str {
match self {
Language::None => "None",
Language::English => "English",
Language::Russian => "Russian",
}
Expand All @@ -26,18 +25,20 @@ pub struct Dictionary {
}

impl Dictionary {
pub fn new() -> Self {
Dictionary {
current: Language::None,
pub fn new(lang: Language) -> io::Result<Self> {
let mut d = Dictionary {
current: lang,
words: vec![],
}
};

d.load(lang)?;
Ok(d)
}

/// load_dict_file accepts path for a text file, where words are
/// separated by a newline (\n) char.
pub fn load(&mut self, lang: Language) -> io::Result<()> {
let dict_raw = match lang {
Language::None => "",
Language::English => include_str!("../dict/en.dict"),
Language::Russian => include_str!("../dict/ru.dict"),
};
Expand Down
7 changes: 1 addition & 6 deletions src/main.rs
Original file line number Diff line number Diff line change
@@ -1,6 +1,4 @@
use crate::dict::Dictionary;
use app::App;
use dict::Language;
use std::io;

mod app;
Expand All @@ -10,13 +8,10 @@ mod screens;
mod wpm;

fn main() -> Result<(), io::Error> {
let mut dict = Dictionary::new();
dict.load(Language::English).expect("dict loaded");

let mut terminal = ratatui::init();
terminal.clear()?;

let mut typer = App::new(dict)?;
let mut typer = App::new()?;
typer.run(terminal)?;

ratatui::restore();
Expand Down
18 changes: 1 addition & 17 deletions src/screens.rs
Original file line number Diff line number Diff line change
@@ -1,24 +1,8 @@
use ratatui::{
buffer::Buffer,
layout::{Constraint, Layout, Rect},
text::{Line, Span},
widgets::Widget,
};
use select_dict::SelectDictScreen;
use typing::TypingScreen;

pub mod select_dict;
pub mod typing;

#[derive(PartialEq)]
pub enum Screen {
Typing,
Dicts,
}

pub trait Opts {
fn custom_options() -> Vec<(&'static str, &'static str)>;
}

impl Screen {
pub fn render_screen<T>(&self, area: Rect, buf: &mut Buffer, screen: &Screen) {}
}
80 changes: 54 additions & 26 deletions src/screens/select_dict.rs
Original file line number Diff line number Diff line change
Expand Up @@ -3,64 +3,92 @@ use std::{cell::RefCell, rc::Rc};
use crossterm::event::{KeyCode, KeyEvent};
use ratatui::{
prelude::*,
widgets::{List, ListItem, ListState},
widgets::{Block, Borders, List, ListItem, ListState},
};
use strum::IntoEnumIterator;

use crate::dict::{Dictionary, Language};

use super::{Opts, Screen};
use crate::{
dict::{Dictionary, Language},
event::Event,
};

#[derive(Clone, Copy, Debug)]
enum UserInput {
Pass,
Miss(char),
Hit(char),
}
use super::Screen;

#[derive(Clone, Debug)]
pub struct SelectDictScreen {
dict: Rc<RefCell<Dictionary>>,
items: Vec<Language>,
languages: Vec<Language>,
state: ListState,
}

impl SelectDictScreen {
pub fn new(dict: &Rc<RefCell<Dictionary>>) -> Self {
Self {
dict: dict.clone(),
items: Language::iter().collect(),
state: ListState::default(),
languages: Language::iter().collect(),
state: ListState::default().with_selected(Some(0)),
}
}
pub fn handle_key(&mut self, key: KeyEvent) -> bool {

pub fn handle_key(&mut self, key: KeyEvent) -> Event {
match key.code {
KeyCode::Up => self.state.select_previous(),
KeyCode::Down => self.state.select_next(),
KeyCode::Esc => return true,
KeyCode::Up => match self.state.selected() {
Some(0) => self.state.select_last(),
Some(_) => self.state.select_previous(),
None => {}
},
KeyCode::Down => {
if let Some(i) = self.state.selected() {
if i == self.languages.len() - 1 {
self.state.select_first();
} else {
self.state.select_next()
}
}
}
KeyCode::Esc => return Event::Switch(Screen::Typing),
KeyCode::Enter => {
self.change_dict();
return Event::Switch(Screen::Typing);
}
_ => {}
};

false
Event::DoNothing
}

fn change_dict(&mut self) {
if let Some(i) = self.state.selected() {
let lang = self.languages.get(i).unwrap_or(&Language::English);

// TODO: Handle err
self.dict.borrow_mut().load(*lang).unwrap();
}
}

pub fn actions() -> Vec<(&'static str, &'static str)> {
vec![("Esc", "Back"), ("▲ ▼", "Move"), ("Enter", "Select")]
}
}

impl Widget for &mut SelectDictScreen {
fn render(self, area: Rect, buf: &mut Buffer) {
let block = Block::new()
.title(Line::raw("Available dictionaties").centered())
.borders(Borders::ALL)
.border_set(symbols::border::ROUNDED);

let list_items = self
.items
.languages
.iter()
.map(|item| ListItem::from(item.as_str()))
.collect::<Vec<ListItem>>();

let list = List::new(list_items).highlight_symbol(">");
let list = List::new(list_items)
.block(block)
.highlight_symbol("> ")
.highlight_spacing(ratatui::widgets::HighlightSpacing::Always);

StatefulWidget::render(list, area, buf, &mut self.state);
}
}

impl Opts for SelectDictScreen {
fn custom_options() -> Vec<(&'static str, &'static str)> {
vec![("Esc", "Back"), ("▲ ▼", "Move"), ("Enter", "Select")]
}
}
16 changes: 8 additions & 8 deletions src/screens/typing.rs
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@ use ratatui::{prelude::*, widgets::Paragraph};

use crate::{dict::Dictionary, event::Event, wpm::WpmCounter};

use super::{Opts, Screen};
use super::Screen;

#[derive(Clone, Copy, Debug)]
enum UserInput {
Expand Down Expand Up @@ -67,18 +67,23 @@ impl TypingScreen {
self.wpm.tick_char(&c);
}

if self.current_pos as usize >= self.current_string.len() {
// we respect utf-8
if self.current_pos as usize >= self.current_string.chars().count() {
self.randomize_input();
}

Event::DoNothing
}

fn randomize_input(&mut self) {
pub fn randomize_input(&mut self) {
self.current_string = self.dict.borrow().random_words(5).join(" ");
self.user_string.clear();
self.current_pos = 0;
}

pub fn actions() -> Vec<(&'static str, &'static str)> {
vec![("<C-r>", "New random words"), ("<C-d>", "Select Dict")]
}
}

impl Widget for &TypingScreen {
Expand Down Expand Up @@ -122,8 +127,3 @@ impl Widget for &TypingScreen {
}
}
}
impl Opts for TypingScreen {
fn custom_options() -> Vec<(&'static str, &'static str)> {
vec![("<C-r>", "New random words"), ("<C-d>", "Select Dict")]
}
}

0 comments on commit 4685b8c

Please sign in to comment.