Last active
August 31, 2020 13:10
-
-
Save giuliano-macedo/0eaabf19761e861365ef04bcbc9165bd to your computer and use it in GitHub Desktop.
Workout timer written in python using curses
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
[ | |
{ | |
"name":"REP", | |
"time_seconds":60 | |
}, | |
{ | |
"name":"SWITCH", | |
"time_minutes":3 | |
} | |
] |
This file contains hidden or bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
#!/usr/bin/env python | |
import curses | |
from time import time,sleep | |
import json | |
from dataclasses import dataclass | |
@dataclass | |
class Mode: | |
name:str | |
time:int | |
def load_modes(fname="modes.json"): | |
with open(fname,"r") as f: | |
return [ | |
Mode( | |
name=d["name"], | |
time=d.get("time_seconds",0)+(d.get("time_minutes",0)*60) | |
) | |
for d in json.load(f) | |
] | |
class TUI: | |
FPS=60 | |
MSG="[R]eset [SPACE] pause [ARROWS] to switch mode [Q]uit" | |
def __init__(self,screen): | |
self.screen=screen | |
self.stop_watch=StopWatch(screen,y=1) | |
self.modes=load_modes() | |
self.set_mode(0) | |
assert(self.modes) | |
screen.clear() | |
screen.nodelay(True) | |
curses.curs_set(0) | |
self.__quit=False | |
def set_mode(self,i): | |
if i<0:raise IndexError; | |
self.__current_mode=self.modes[i] | |
self.__current_index=i | |
self.stop_watch.set_time(self.__current_mode.time) | |
self.stop_watch.is_paused=True | |
def switch_mode(self,direction): | |
try: | |
self.set_mode(self.__current_index+direction) | |
except IndexError:pass | |
def quit(self): | |
self.__quit=True | |
def update(self,char): | |
self.screen.addstr(0,0,f"current mode:{self.__current_mode.name}") | |
self.stop_watch.update() | |
self.screen.addstr(2,0,TUI.MSG) | |
{ | |
ord("r"): self.stop_watch.reset, | |
ord("R"): self.stop_watch.reset, | |
ord("q"): self.quit, | |
ord("Q"): self.quit, | |
ord(" "): self.stop_watch.pause, | |
curses.KEY_LEFT: lambda :self.switch_mode(-1), | |
curses.KEY_RIGHT: lambda :self.switch_mode(+1) | |
}.get(char,lambda :None)() | |
def run(self): | |
while self.__quit!=True: | |
self.screen.erase() | |
start=time() | |
self.update(self.screen.getch()) | |
self.screen.refresh() | |
sleep(max(0,1/TUI.FPS - (time()-start))) | |
def format_time(time_seconds): | |
minutes=int((time_seconds+0.00001)//60) | |
time_seconds-=minutes*60 | |
time_seconds=abs(time_seconds) | |
return f"{minutes:02}:{abs(time_seconds):05.2f}" | |
class StopWatch: | |
def __init__(self,screen,y): | |
self.screen=screen | |
self.is_paused=False | |
self.set_time(0) | |
self.y=y | |
def __update_remaining(self): | |
self.remaining=max(0,self.__start-time()) | |
def set_time(self,time_seconds): | |
self.last_set_time=time_seconds | |
self.__start=time()+time_seconds | |
self.__update_remaining() | |
def reset(self): | |
self.set_time(self.last_set_time) | |
self.is_paused=True | |
def pause(self): | |
self.is_paused=not self.is_paused | |
def update(self): | |
if not self.is_paused: | |
self.__update_remaining() | |
else: | |
self.__start=time()+self.remaining | |
msg=f"{['▶️','⏹️'][self.is_paused]} {format_time(self.remaining)}" | |
self.screen.addstr(self.y,0,msg) | |
if __name__=="__main__": | |
curses.wrapper(lambda screen:TUI(screen).run()) |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment