prepare for stats screen, initial drafts of the displayed component
This commit is contained in:
parent
8c00a8d8c9
commit
bc40033c50
8 changed files with 117 additions and 65 deletions
|
@ -1,4 +1,4 @@
|
|||
from .cli import initialize, get_parser
|
||||
from .cli import initialize, get_parser, RuntimeConfig
|
||||
import sys
|
||||
import os
|
||||
|
||||
|
@ -23,7 +23,13 @@ def main():
|
|||
|
||||
rbuffer = "".join(input_lines)
|
||||
|
||||
initialize(args.config, rbuffer, args.unclutter_backspace, args.no_cursor)
|
||||
initialize(
|
||||
args.config,
|
||||
rbuffer,
|
||||
args.unclutter_backspace,
|
||||
args.no_cursor,
|
||||
RuntimeConfig(mode=RuntimeConfig.TEXT),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -22,22 +22,19 @@ class Application:
|
|||
def action(self, screen):
|
||||
try:
|
||||
action, key = self.listener.listen(screen)
|
||||
self.buffer.handle_action(action, key)
|
||||
if self.running():
|
||||
self.buffer.handle_action(action, key)
|
||||
except StoppingSignal as e:
|
||||
if e.silent:
|
||||
self.silent_exit = True
|
||||
self.buffer.stats.signal_stop()
|
||||
if self.running():
|
||||
self.buffer.stats.signal_stop()
|
||||
|
||||
def summarize(self):
|
||||
if self.finished:
|
||||
self.buffer.stats.summarize(self.config.get("summary_template"))
|
||||
self.buffer.stats.export_to_datafile(self.config.get("summary_datafile"))
|
||||
try:
|
||||
c = readchar.readchar()
|
||||
if ord(c) == 3:
|
||||
raise KeyboardInterrupt
|
||||
except KeyboardInterrupt:
|
||||
sys.exit(1)
|
||||
return True
|
||||
return False
|
||||
|
||||
def exit(self):
|
||||
if not self.finished and not self.silent_exit:
|
||||
|
|
|
@ -2,6 +2,7 @@ from .application import Application
|
|||
from .interface import Interface
|
||||
from .components import (
|
||||
TextBox,
|
||||
StatsBox,
|
||||
)
|
||||
from .listener import Listener
|
||||
from .buffer import Buffer
|
||||
|
@ -13,7 +14,17 @@ import argparse
|
|||
import json
|
||||
|
||||
|
||||
def initialize(config_path, rbuffer, backspace_debug, no_cursor):
|
||||
class RuntimeConfig:
|
||||
WORDS = "words"
|
||||
TEXT = "text"
|
||||
|
||||
def __init__(self, words=None, language=None, mode=None):
|
||||
self.words = words
|
||||
self.language = language
|
||||
self.mode = mode
|
||||
|
||||
|
||||
def initialize(config_path, rbuffer, backspace_debug, no_cursor, runtime_config):
|
||||
try:
|
||||
with open(os.path.expanduser(config_path)) as f:
|
||||
configmap = json.load(f)
|
||||
|
@ -23,7 +34,8 @@ def initialize(config_path, rbuffer, backspace_debug, no_cursor):
|
|||
config = Config(configmap)
|
||||
|
||||
text_box = TextBox(config)
|
||||
stats = Stats()
|
||||
stats_box = StatsBox(config)
|
||||
stats = Stats(runtime_config)
|
||||
|
||||
buffer = Buffer(rbuffer, text_box, stats)
|
||||
|
||||
|
@ -35,11 +47,13 @@ def initialize(config_path, rbuffer, backspace_debug, no_cursor):
|
|||
[
|
||||
text_box,
|
||||
],
|
||||
[
|
||||
stats_box,
|
||||
],
|
||||
no_cursor,
|
||||
)
|
||||
wrapper(interface)
|
||||
|
||||
application.summarize()
|
||||
application.exit()
|
||||
|
||||
|
||||
|
|
|
@ -68,6 +68,7 @@ class BorderedBox(WindowComponent):
|
|||
self.pos_x = config.get("left_margin_percentage") / 100
|
||||
self.height = config.get("lines_on_screen")
|
||||
self.width = None
|
||||
self.application = None
|
||||
|
||||
def paint_text(self, row, col, text, color):
|
||||
super().paint_text(row + 1, col + 1, text, color)
|
||||
|
@ -76,6 +77,7 @@ class BorderedBox(WindowComponent):
|
|||
super().move(x + 1, y + 1)
|
||||
|
||||
def init(self, screen, application):
|
||||
self.application = application
|
||||
self.width = int(self.maxx * (1 - 2 * self.pos_x))
|
||||
self.update_size(
|
||||
self.height + 2,
|
||||
|
@ -89,7 +91,30 @@ class BorderedBox(WindowComponent):
|
|||
screen.refresh()
|
||||
|
||||
|
||||
class BufferDependentComponent(BorderedBox):
|
||||
class BorderWithImprintedStats(BorderedBox):
|
||||
"""
|
||||
Imprints stats on one of borders
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
self.stats_template = config.get("stats_template").replace("\n", " ")
|
||||
self.stats_color = config.get("stats_color")
|
||||
self.stats_row = -1 if config.get("stats_position") == "top" else self.height
|
||||
|
||||
def paint_stats(self):
|
||||
text = self.stats_template.format(stats=self.application.buffer.stats)
|
||||
if len(text) < self.width - 2:
|
||||
self.paint_text(
|
||||
self.stats_row,
|
||||
2,
|
||||
text + " " * (self.width - 2 - len(text)),
|
||||
self.stats_color,
|
||||
)
|
||||
|
||||
|
||||
class BufferDependentComponent(BorderWithImprintedStats):
|
||||
"""
|
||||
Adds content source from buffer
|
||||
"""
|
||||
|
@ -276,30 +301,7 @@ class BufferDependentComponent(BorderedBox):
|
|||
self.update_cursor()
|
||||
|
||||
|
||||
class BorderWithImprintedStats(BufferDependentComponent):
|
||||
"""
|
||||
Imprints stats on one of borders
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
|
||||
self.stats_template = config.get("stats_template").replace("\n", " ")
|
||||
self.stats_color = config.get("stats_color")
|
||||
self.stats_row = -1 if config.get("stats_position") == "top" else self.height
|
||||
|
||||
def paint_stats(self):
|
||||
text = self.stats_template.format(stats=self.buffer.stats)
|
||||
if len(text) < self.width - 2:
|
||||
self.paint_text(
|
||||
self.stats_row,
|
||||
2,
|
||||
text + " " * (self.width - 2 - len(text)),
|
||||
self.stats_color,
|
||||
)
|
||||
|
||||
|
||||
class TextBox(BorderWithImprintedStats):
|
||||
class TextBox(BufferDependentComponent):
|
||||
"""
|
||||
Calls all inherited paint functions and inits windows
|
||||
"""
|
||||
|
@ -322,3 +324,21 @@ class TextBox(BorderWithImprintedStats):
|
|||
|
||||
self.move(self.cursor_x, self.cursor_y)
|
||||
self.refresh()
|
||||
|
||||
|
||||
class StatsBox(BorderWithImprintedStats):
|
||||
"""
|
||||
Displays stats
|
||||
"""
|
||||
|
||||
def __init__(self, config):
|
||||
super().__init__(config)
|
||||
self.wpm_color = config.get("wpm_grapth_color")
|
||||
self.raw_wpm_color = config.get("raw_wpm_grapth_color")
|
||||
self.errors = config.get("errors_graph_color")
|
||||
|
||||
def paint(self, screen, application):
|
||||
self.maxy, self.maxx = screen.getmaxyx()
|
||||
self.init(screen, application)
|
||||
self.paint_stats()
|
||||
self.refresh()
|
||||
|
|
|
@ -6,25 +6,13 @@ class Config:
|
|||
"stats_template": "wpm: {stats.wpm:0.2f}, time: {stats.total_seconds:0.2f}s",
|
||||
"stats_color": 5,
|
||||
"stats_position": "top",
|
||||
"summary_template": (
|
||||
"WPM: {stats.wpm:0.2f}\n"
|
||||
"CPM: {stats.cpm:0.2f}\n"
|
||||
"RAW WPM: {stats.raw_wpm:0.2f}\n"
|
||||
"RAW CPM: {stats.raw_cpm:0.2f}\n"
|
||||
"total seconds: {stats.total_seconds:0.2f}\n"
|
||||
"total minutes: {stats.total_minutes:0.2f}\n"
|
||||
"correct words: {stats.correct_words}\n"
|
||||
"correct chars: {stats.correct_chars}\n"
|
||||
"incorrect words: {stats.incorrect_words}\n"
|
||||
"incorrect chars: {stats.incorrect_chars}\n"
|
||||
"total words: {stats.total_words}\n"
|
||||
"total chars: {stats.total_chars}\n"
|
||||
"accuracy: {stats.accuracy:0.2f}%"
|
||||
),
|
||||
"summary_datafile": "~/.cache/fasttyper/datafile.csv",
|
||||
"top_margin_percentage": 40,
|
||||
"left_margin_percentage": 35,
|
||||
"lines_on_screen": 3,
|
||||
"wpm_graph_color": 3,
|
||||
"raw_wpm_graph_color": 9,
|
||||
"errors_graph_color": 2,
|
||||
}
|
||||
|
||||
def __init__(self, configmap):
|
||||
|
|
|
@ -2,9 +2,10 @@ import curses
|
|||
|
||||
|
||||
class Interface:
|
||||
def __init__(self, application, components, no_cursor=False):
|
||||
def __init__(self, application, components, end_components, no_cursor=False):
|
||||
self.application = application
|
||||
self.components = components
|
||||
self.end_components = end_components
|
||||
self.no_cursor = no_cursor
|
||||
self.colors = True
|
||||
|
||||
|
@ -29,6 +30,10 @@ class Interface:
|
|||
for component in self.components:
|
||||
component.paint(screen, self.application)
|
||||
|
||||
def draw_end(self, screen):
|
||||
for component in self.end_components:
|
||||
component.paint(screen, self.application)
|
||||
|
||||
def __call__(self, screen):
|
||||
"""
|
||||
Main running loop
|
||||
|
@ -42,3 +47,9 @@ class Interface:
|
|||
while self.application.running():
|
||||
self.update(screen)
|
||||
self.application.action(screen)
|
||||
|
||||
if self.application.summarize():
|
||||
screen.clear()
|
||||
curses.curs_set(0)
|
||||
self.draw_end(screen)
|
||||
self.application.action(screen)
|
||||
|
|
|
@ -1,4 +1,4 @@
|
|||
from .cli import initialize, get_parser
|
||||
from .cli import initialize, get_parser, RuntimeConfig
|
||||
from random import choice
|
||||
import os
|
||||
import requests
|
||||
|
@ -48,7 +48,15 @@ def runner():
|
|||
|
||||
while True:
|
||||
rbuffer = " ".join([choice(words) for _ in range(args.amount)])
|
||||
initialize(args.config, rbuffer, args.unclutter_backspace, args.no_cursor)
|
||||
initialize(
|
||||
args.config,
|
||||
rbuffer,
|
||||
args.unclutter_backspace,
|
||||
args.no_cursor,
|
||||
RuntimeConfig(
|
||||
mode=RuntimeConfig.WORDS, words=args.amount, language=args.language
|
||||
),
|
||||
)
|
||||
|
||||
|
||||
if __name__ == "__main__":
|
||||
|
|
|
@ -6,7 +6,9 @@ import pathlib
|
|||
|
||||
|
||||
class Stats:
|
||||
def __init__(self):
|
||||
def __init__(self, runtime_config):
|
||||
self.runtime_config = runtime_config
|
||||
|
||||
self.correct_chars = 0
|
||||
self.incorrect_chars = 0
|
||||
|
||||
|
@ -16,6 +18,8 @@ class Stats:
|
|||
self.buffer = None
|
||||
self.finished = False
|
||||
|
||||
self.snaps = []
|
||||
|
||||
def set_buffer(self, buffer):
|
||||
self.buffer = buffer
|
||||
|
||||
|
@ -29,14 +33,20 @@ class Stats:
|
|||
def signal_valid(self):
|
||||
self.signal_running()
|
||||
self.correct_chars += 1
|
||||
self.take_snap()
|
||||
|
||||
def signal_invalid(self):
|
||||
self.signal_running()
|
||||
self.incorrect_chars += 1
|
||||
self.take_snap()
|
||||
|
||||
def signal_stop(self, finished=False):
|
||||
self.stop_dtime = datetime.now()
|
||||
self.finished = finished
|
||||
self.take_snap()
|
||||
|
||||
def take_snap(self):
|
||||
self.snaps.append(self.produce_record())
|
||||
|
||||
@property
|
||||
def correct_words(self):
|
||||
|
@ -86,13 +96,10 @@ class Stats:
|
|||
return self.correct_chars / self.total_chars * 100
|
||||
return 100
|
||||
|
||||
def summarize(self, template):
|
||||
print(template.format(stats=self))
|
||||
|
||||
def produce_record(self):
|
||||
return {
|
||||
"start_dtime": self.start_dtime.isoformat(),
|
||||
"stop_dtime": self.stop_dtime.isoformat(),
|
||||
"start_dtime": self.start_dtime.isoformat() if self.start_dtime else None,
|
||||
"stop_dtime": self.stop_dtime.isoformat() if self.stop_dtime else None,
|
||||
"total_seconds": self.total_seconds,
|
||||
"total_minutes": self.total_minutes,
|
||||
"total_chars": self.total_chars,
|
||||
|
@ -106,6 +113,9 @@ class Stats:
|
|||
"raw_wpm": self.raw_wpm,
|
||||
"raw_cpm": self.raw_cpm,
|
||||
"accuracy": self.accuracy,
|
||||
"mode": self.runtime_config.mode,
|
||||
"language": self.runtime_config.language,
|
||||
"words": self.runtime_config.words,
|
||||
}
|
||||
|
||||
def export_to_datafile(self, datafile):
|
||||
|
@ -129,5 +139,3 @@ class Stats:
|
|||
with open(datafile, "a") as f:
|
||||
writter = csv.DictWriter(f, fieldnames=record.keys())
|
||||
writter.writerow(record)
|
||||
|
||||
print(f"\nwrote stats to {datafile}")
|
||||
|
|
Loading…
Reference in a new issue