cleaned up some stuff and added Documentation in README.md

This commit is contained in:
icaema 2023-11-16 20:00:06 -05:00
parent 7984b55856
commit 03be2c5f3a
8 changed files with 286 additions and 232 deletions

1
.gitignore vendored
View File

@ -53,3 +53,4 @@ env.bak
venv.bak
pyvenv.cfg
bin
__pycache__

125
README.md
View File

@ -1,7 +1,124 @@
# Pyterm Render
A quick to use framework for making terminal based graphics in python.
# Python Term Render
A quick to use and dead simple framework for making terminal based graphics in python.
```
python3 -m venv .
example_music.py
################################################################
# FPS: 29.866 Frame: 251 Sample: Wavtapper - Frums #
# [3, 3, 7, 14, 29, 59] #
================================================================
1234567890123456789012345678901234567890123456789012345678901234
0 1 | 2 3 | 4 | 5 6
$
X X X X X X
X Y Z Z
# #
################################################################
```
## Quick start
```
python3 -m venv venv
source venv/bin/activate
pip install -r requirements.txt
```
python3 example.py
```
## Usage
Programs take the following form,
```
from pytermrender import *
from colorama import Fore, Back, Style
Screen = TermScreen(height=24, width=80, framerate=30) # Screen must be defined
def setup():
print("Setup")
print("Clearing")
clearScreen()
def teardown():
clearScreen()
print("Teardown")
print("Teardown Complete")
def tick():
Screen.frame_no+=1
def draw():
clearScreen()
clearAllBuffers()
frame = Screen.frame_no
drawBox(0,0,Screen.width,Screen.height,'#')
printBuffer(1,1,str(Screen.framerate)+'fps -'+str(frame)+'-')
for ix, c in enumerate(str(frame)):
putBuffer(8+ix,1, Fore.BLUE, buffer="color")
run()
```
import everything from the pytermrender lib and define setup, teardown, tick, and draw. then just call run().
The setup function is called on program start, and teardown is called on Ctrl+c.
The tick function is called every frame before the draw function.
It can be used to do the program logic.
The draw function is called after the tick function and is where the program should actually manipulate the screen buffers.
As of now, there are only two buffers, "char" and "color".
Both of these buffers get printed to the screen every frame.
Use the char buffer for printable characters and the color buffer for non-printable characters, like ANSI char sequences.
## Library Reference
Pytermrender provides several library functions for quick usage:
```
clearScreen():
| Clear the terminal
clearAllBuffers():
| Clear all buffers
clearBuffer(buffer="char"):
| Clear a specific buffer
| buffer can be one of [ "char", "color" ]
clearLine(y, buffer="char"):
| Clear a line of a buffer
| buffer can be one of [ "char", "color" ]
putBuffer(x, y, char, *, buffer="char"):
| Put a char into a specific location in a buffer
| buffer can be one of [ "char", "color" ]
| NOTE: multi-char strings can be used, but can cause unintended results
| If you want to put multi-char strings, use printBuffer
printBuffer(x, y, string):
| Print a string into the char buffer
drawBox(x, y, width, height, char):
| Draw a box in the char buffer with the supplied char
```
Pytermrender also provides a convienent way to store state inside the Screen object.
```
Screen.tickers
| a dictionary to store variables that change every frame.
Screen.state
| a dictionary to store variaiables that are more static.
```
Example usage as follows:
```
tick():
global Screen
Screen.tickers["music"][0]=(Screen.frame_no//BEAT_SUB)//4
Screen.state["cursorActive"]=True
draw():
bar=Screen.tickers["music"][0]
cursorActive=Screen.state["cursorActive"]
if cursorActive:
putBuffer(0, 0, "$")
```

View File

@ -1,28 +1,38 @@
from pytermrender import *
from colorama import Fore, Back, Style
Screen = TermScreen(height=24, width=80, framerate=30)
#not strictly needed, but useful
def tick():
Screen.frame_no+=1
def setup():
print("Setup")
print("Clearing")
clearScreen()
pass
return
def teardown():
clearScreen()
print("Teardown")
print("Teardown Complete")
pass
return
#not strictly needed, but useful
def tick():
Screen.frame_no+=1
return
def draw():
clearScreen()
clearAllBuffers()
frame = Screen.frame_no
drawBox(0,0,Screen.width,Screen.height,'#')
printBuffer(1,1,str(Screen.framerate)+'fps '+str(Screen.frame_no))
printBuffer(1,1,str(Screen.framerate)+'fps -'+str(frame)+'-')
for ix, c in enumerate(str(frame)):
putBuffer(8+ix,1, Fore.BLUE, buffer="color")
return
run()

View File

@ -1,59 +0,0 @@
from pytermrender import *
from colorama import Fore, Back, Style
BPM=112
BEAT_SUB=16
FRAMERATE=(BPM*BEAT_SUB/60)
Screen = TermScreen(height=24, width=64, framerate=FRAMERATE)
cursorX=1
cooldown=0
def tick():
global cursorX,cooldown
Screen.frame_no+=1
cursorX=cursorX+1
if cursorX >= Screen.width-1:
cursorX=0
if cursorX % 12 == 0:
cooldown=8
else:
cooldown-=1
return
def setup():
print("Setup")
print("Clearing")
clearScreen()
return
def teardown():
print("Teardown")
print("Teardown Complete")
return
def draw():
clearScreen()
clearAllBuffers()
for y in range(Screen.height):
for x in range(Screen.width):
if x == 0 or x == Screen.width-1:
Screen.buffer_char[y][x]='#'
if y == 0 or y == Screen.height-1:
Screen.buffer_char[y][x]='#'
printBuffer(0,3,"1234567890123456789012345678901234567890123456789012345678901234")
printBuffer(0,4,"0 1 2 3 4 5 6 ")
#printBuffer(0,4,"2 6 8 6 4 6 8 6 2 6 8 6 4 6 8 6 ")
clearLine(5)
Screen.buffer_char[5][cursorX] = "x"
if cooldown > 0:
Screen.buffer_color[5][cursorX] = Fore.RED
printBuffer(1,1,Screen.frame_no)
printBuffer(8,1,Screen.framerate)
run()

95
example_music.py Normal file
View File

@ -0,0 +1,95 @@
from pytermrender import *
from colorama import Fore, Back, Style
from just_playback import Playback
import math
BPM=112
BEAT_SUB=16
FRAMERATE=(BPM*BEAT_SUB/60)
Screen = TermScreen(height=20, width=64, framerate=FRAMERATE)
playback = Playback()
def setup():
global Screen
print("Setup")
playback.load_file('media/WavetapperSample.wav')
playback.loop_at_end(True)
playback.play()
playback.set_volume(0.5)
Screen.tickers["music"] = {}
print("Clearing Screen")
clearScreen()
return
def teardown():
clearScreen();
print("Teardown")
print("Stopping Playback")
playback.stop()
print("Teardown Complete")
return
def tick():
global Screen
Screen.frame_no+=1
Screen.tickers["music"][0]=(Screen.frame_no//BEAT_SUB)//4 # bar
Screen.tickers["music"][1]=(Screen.frame_no//BEAT_SUB//1)%4 # beat
Screen.tickers["music"][2]=(Screen.frame_no//(BEAT_SUB//2))%8 # 8th
Screen.tickers["music"][3]=(Screen.frame_no//(BEAT_SUB//4))%16 # 16th
Screen.tickers["music"][4]=(Screen.frame_no//(BEAT_SUB//8))%32 # 32nd
Screen.tickers["music"][5]=(Screen.frame_no//(BEAT_SUB//16))%64 # 64th
Screen.state["cursorX"]=Screen.tickers["music"][5]
if Screen.tickers["music"][1] in [0,2]:
Screen.state["cursorActive"]=True
else:
Screen.state["cursorActive"]=False
return
def draw():
clearScreen()
clearAllBuffers()
cursorX=Screen.state["cursorX"]
cursorActive=Screen.state["cursorActive"]
bar=Screen.tickers["music"][0]
drawBox(0,0, Screen.width,Screen.height, '#')
printBuffer(2, 1, "FPS: "+str(Screen.framerate)[:6])
printBuffer(15, 1, "Frame: "+str(Screen.frame_no))
printBuffer(37, 1, "Sample: Wavtapper - Frums")
printBuffer(2, 2, [x for x in Screen.tickers["music"].values()])
printBuffer(0, 3, "="*Screen.width)
printBuffer(0, 4, "1234567890123456789012345678901234567890123456789012345678901234")
printBuffer(0, 5, "0 1 | 2 3 | 4 | 5 6 ")
clearLine(6)
putBuffer(cursorX, 6, "$")
if cursorActive:
putBuffer(cursorX, 6, Fore.GREEN, buffer="color")
clearLine(7)
printBuffer(0,7,"X X X X X X")
clearLine(8)
match bar % 4:
case 0:
printBuffer(52,8,"X")
case 1:
printBuffer(52,8,"X Y")
case 2:
printBuffer(52,8,"X")
case 3:
printBuffer(52,8,"X Y Z Z")
for ix in range(cursorX):
putBuffer(ix, 7, Fore.RED, buffer="color")
putBuffer(ix, 8, Fore.BLUE, buffer="color")
return
run()

BIN
media/WavetapperSample.wav Normal file

Binary file not shown.

View File

@ -1,17 +1,12 @@
import __main__
import time, math
from colorama import Fore, Back, Style
import functools
#from dataclasses import dataclass
printr = functools.partial(print, end="")
import __main__, time, math, functools
from colorama import Style
## Definitions
printr = functools.partial(print, end="")
class RenderException(Exception):
pass
class TermScreen:
width: int
height: int
@ -19,14 +14,17 @@ class TermScreen:
frame_no: int = 0
buffers: list = ["char", "color"]
tickers: dict
state: dict
def __init__(self, *, width: int = 80, height: int = 24, framerate: float = 30.0):
self.width=width
self.height=height
self.framerate=framerate
self.buffer_char = [[" "]*self.width for i in range(self.height)]
self.buffer_color = [[Style.RESET_ALL]*self.width for i in range(self.height)]
self.tickers = {}
self.state = {}
return
def reset_buffer(self, buffer="char"):
if buffer not in self.buffers:
raise RenderException("Buffer Does not exist")
@ -37,6 +35,31 @@ class TermScreen:
self.buffer_color = [[Style.RESET_ALL]*self.width for i in range(self.height)]
return
def reset_buffer_line(self, line=0, buffer="char"):
if buffer not in self.buffers:
raise RenderException("Buffer Does not exist")
if line >= self.height:
raise RenderException("Line is out of range of height")
match buffer:
case "char":
self.buffer_char[line] = [" "]*self.width
case "color":
self.buffer_color[line] = [Style.RESET_ALL]*self.width
return
def put_buffer(self, x, y, val, buffer="char"):
if buffer not in self.buffers:
raise RenderException("Buffer Does not exist")
if x >= self.width or y >= self.height:
raise RenderException("Point is out of range of the buffer")
char=str(val)
match buffer:
case "char":
self.buffer_char[y][x] = char
case "color":
self.buffer_color[y][x] = char
return
def render(self):
for y in range(self.height):
for x in range(self.width):
@ -45,6 +68,9 @@ class TermScreen:
print() #print \n
return
def framerateMaker(func, perSec):
interval = 1.0 / perSec
while True:
@ -66,11 +92,11 @@ def renderLoop():
## Library Functions
def clearScreen():
printr('\033[2J')
printr('\033[2J\r')
def clearBuffer(buf="char"):
def clearBuffer(buffer=TermScreen.buffers[0]):
global Screen
Screen.reset_buffer(buf)
Screen.reset_buffer(buffer)
return
def clearAllBuffers():
@ -79,19 +105,28 @@ def clearAllBuffers():
Screen.reset_buffer(buf)
return
def clearLine(y, buffer=TermScreen.buffers[0]):
global Screen
Screen.reset_buffer_line(y, buffer)
return
def putBuffer(x, y, val, *, buffer=TermScreen.buffers[0]):
global Screen
Screen.put_buffer(x, y, val, buffer)
return
def printBuffer(x, y, val):
""" Print a string into the char buffer """
global Screen
string=str(val)
if len(string) > Screen.width-x:
raise RenderException("Too Long to fit in screen")
for i, char in enumerate(string):
Screen.buffer_char[y][x+i]=char
def clearLine(y):
global Screen
Screen.buffer_char[y]=[" "]*Screen.width
return
def drawBox(x,y,width,height,char):
""" Draw a box in the char buffer """
global Screen
if x+width > Screen.width or y+height > Screen.height:
raise RenderException("Box too BIG")
@ -99,17 +134,16 @@ def drawBox(x,y,width,height,char):
for w_y in range(height):
if (w_x==0 or w_x==width-1) or (w_y==0 or w_y==height-1):
Screen.buffer_char[y+w_y][x+w_x] = char
return
####################
def tick():
global Screen
Screen.frame_no+=1
def setup():
print("Setup")
print("Default Setup")
clearScreen()
clearAllBuffers()
return

View File

@ -1,144 +0,0 @@
import time
import math
import functools
printr = functools.partial(print, end="")
from just_playback import Playback
playback = Playback()
from colorama import Fore, Back, Style
###############
BPM=112
BEAT_SUB=16
FRAMERATE=(BPM*BEAT_SUB/60)
HEIGHT=24
WIDTH=64
###############
FRAME_NO=0
MUSIC_TIME=[0 for i in range(int(math.log2(BEAT_SUB)))]
###############
def clearBuffer():
global screen_buffer,screen_buffer_color
screen_buffer = [[" "]*WIDTH for y in range(HEIGHT)]
def clearBufferColor():
global screen_buffer,screen_buffer_color
screen_buffer_color = [[Style.RESET_ALL]*WIDTH for y in range(HEIGHT)]
def clearAllBuffers():
clearBuffer()
clearBufferColor()
def clearScreen():
printr('\033[2J')
def render(clear=True):
if clear:
clearScreen()
for y in range(HEIGHT):
for x in range(WIDTH):
printr(screen_buffer_color[y][x])
printr(screen_buffer[y][x])
print() #print \n
def renderLoop():
trackers()
draw()
render()
def framerateMaker(func, perSec):
interval = 1.0 / perSec
while True:
start_time = time.time()
func()
elapsed = time.time() - start_time
if elapsed < interval:
time.sleep(interval-elapsed)
################
def printBuffer(x, y, val):
global screen_buffer
string=str(val)
if len(string) > WIDTH-x:
return
for i, l in enumerate(string):
screen_buffer[y][x+i]=l
pass
def clearLine(y):
global screen_buffer
screen_buffer[y]=[" "]*WIDTH
pass
####################
def trackers():
global FRAME_NO
global MUSIC_TIME
FRAME_NO+=1
MUSIC_TIME[0]=(FRAME_NO//BEAT_SUB)//4
MUSIC_TIME[1]=(FRAME_NO//BEAT_SUB//1)%4
MUSIC_TIME[2]=(FRAME_NO//(BEAT_SUB//2))%8
MUSIC_TIME[3]=(FRAME_NO//(BEAT_SUB//4))%16
#MUSIC_TIME[1]=(FRAME_NO//(BEAT_SUB*int(math.log2(BEAT_SUB//8))))%4
# MUSIC_TIME[2]=(FRAME_NO//8)%8
# for i in range(len(MUSIC_TIME)):
# MUSIC_TIME[i]=(FRAME_NO//2**(i+1)) % 2**(i)
def main():
setup()
try:
framerateMaker(renderLoop, FRAMERATE)
pass
except KeyboardInterrupt:
pass
teardown()
def setup():
global playback
print("Setup")
print("Clearing")
clearScreen()
clearAllBuffers()
#playback.load_file('')
#playback.play()
def teardown():
print("Teardown")
print("Stopping Playback")
#playback.stop()
print("Teardown Complete")
pass
cursorX=1
cooldown=[0]*4*4
def draw():
global screen_buffer,screen_buffer_color
global MUSIC_TIME
global cursorX,cooldown
clearAllBuffers()
for y in range(HEIGHT):
for x in range(WIDTH):
if x == 0 or x == WIDTH-1:
screen_buffer[y][x]='#'
if y == 0 or y == HEIGHT-1:
screen_buffer[y][x]='#'
printBuffer(0,3,"1234567890123456789012345678901234567890123456789012345678901234")
printBuffer(0,4,"0 1 2 3 4 5 6 ")
clearLine(5)
if cursorX >= WIDTH:
cursorX=0
screen_buffer[5][cursorX] = "x"
printBuffer(1,1,FRAME_NO)
printBuffer(6,1,FRAMERATE)
printBuffer(1,2,MUSIC_TIME)
cursorX=cursorX+1
if __name__ == "__main__":
main()