Lots of fun stuff, including virtual dimensions to help reduce warping of grid when printed to a terminal
This commit is contained in:
parent
3e3b3543b2
commit
978c6d9d8c
159
TextCanvas.py
159
TextCanvas.py
@ -11,25 +11,66 @@ import time
|
|||||||
|
|
||||||
class TextCanvas:
|
class TextCanvas:
|
||||||
|
|
||||||
__DEFAULT_TERMINAL_WIDTH = 80
|
__DEFAULT_WIDTH = 80
|
||||||
__DEFAULT_TERMINAL_HEIGHT = 22
|
__DEFAULT_HEIGHT = 22
|
||||||
__TERMINAL_UPDATE_INTERVAL = 1
|
__TERMINAL_UPDATE_INTERVAL = 1
|
||||||
|
|
||||||
|
__BACKGROUND_CHARACTER = " "
|
||||||
|
|
||||||
__DRAW_RESOLUTION = 3
|
__DRAW_RESOLUTION = 3
|
||||||
|
__DEFAULT_TEXT_HEIGHT_TO_WIDTH_RATIO = 2.0
|
||||||
|
|
||||||
def __init__(self):
|
def __init__(
|
||||||
|
self,
|
||||||
|
output_width=None, output_height=None, text_to_height_width=None,
|
||||||
|
auto_adjust_to_terminal=True,
|
||||||
|
print_upward=True
|
||||||
|
):
|
||||||
|
|
||||||
self.__terminal_width = self.__DEFAULT_TERMINAL_WIDTH
|
self.__lock = threading.RLock()
|
||||||
self.__terminal_height = self.__DEFAULT_TERMINAL_HEIGHT
|
|
||||||
|
# Real dimensions
|
||||||
|
if output_width is None:
|
||||||
|
output_width = self.__DEFAULT_WIDTH
|
||||||
|
if output_height is None:
|
||||||
|
output_height = self.__DEFAULT_HEIGHT
|
||||||
|
self.__grid_width = output_width
|
||||||
|
self.__grid_height = output_height
|
||||||
|
|
||||||
|
# Virtual dimensions
|
||||||
|
if text_to_height_width is None:
|
||||||
|
text_to_height_width = self.__DEFAULT_TEXT_HEIGHT_TO_WIDTH_RATIO
|
||||||
|
self.__text_to_height_width = text_to_height_width
|
||||||
|
self.__virtual_width = None
|
||||||
|
self.__virtual_height = None
|
||||||
|
self._auto_calculate_virtual_dimensions(acquire_locks=False)
|
||||||
|
self._update_virtual_ratios(acquire_locks=False)
|
||||||
|
|
||||||
|
# Terminal stuff
|
||||||
|
self.__terminal_width = 0
|
||||||
|
self.__terminal_height = 0
|
||||||
|
self.__auto_adjust_to_terminal = auto_adjust_to_terminal
|
||||||
self.__last_terminal_dimensions_update = time.time()
|
self.__last_terminal_dimensions_update = time.time()
|
||||||
self._update_terminal_dimensions(force=True)
|
if self.__auto_adjust_to_terminal:
|
||||||
|
self._update_terminal_dimensions(force=True)
|
||||||
|
|
||||||
|
#
|
||||||
|
self.__print_upward = print_upward
|
||||||
|
|
||||||
|
# Actual grid
|
||||||
self.__grid = []
|
self.__grid = []
|
||||||
self.__render_string = ""
|
self.__render_string = ""
|
||||||
self.__is_dirty = True
|
self.__is_dirty = True
|
||||||
self.__last_render_time = time.time()
|
self.__last_render_time = time.time()
|
||||||
|
|
||||||
self.__lock = threading.RLock()
|
def _update_virtual_ratios(self, acquire_locks=True):
|
||||||
|
|
||||||
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
|
self.__virtual_height_per_grid_height = self.__virtual_height / self.__grid_height
|
||||||
|
self.__virtual_width_per_grid_width = self.__virtual_width / self.__grid_width
|
||||||
|
|
||||||
|
print("Virtual ratios:", self.__virtual_width_per_grid_width, self.__virtual_height_per_grid_height)
|
||||||
|
|
||||||
def _update_render_if_dirty(self, acquire_locks=True):
|
def _update_render_if_dirty(self, acquire_locks=True):
|
||||||
|
|
||||||
@ -42,11 +83,9 @@ class TextCanvas:
|
|||||||
|
|
||||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
self.clear(acquire_locks=acquire_locks)
|
self._update_render_string(acquire_locks=False)
|
||||||
self._update_render_string(acquire_locks=acquire_locks)
|
|
||||||
|
|
||||||
self.__last_render_time = time.time()
|
self.__last_render_time = time.time()
|
||||||
|
|
||||||
self.__is_dirty = False
|
self.__is_dirty = False
|
||||||
|
|
||||||
def _update_render_string(self, acquire_locks=True):
|
def _update_render_string(self, acquire_locks=True):
|
||||||
@ -62,21 +101,31 @@ class TextCanvas:
|
|||||||
# Don't need that last line feed
|
# Don't need that last line feed
|
||||||
s = s[:-1]
|
s = s[:-1]
|
||||||
|
|
||||||
self.__grid_string = s
|
self.__render_string = s
|
||||||
|
|
||||||
def get(self, acquire_locks=True):
|
def get(self, acquire_locks=True):
|
||||||
|
|
||||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
self._update_render_if_dirty(acquire_locks=acquire_locks)
|
self._update_render_if_dirty(acquire_locks=False)
|
||||||
|
|
||||||
return self.__grid_string
|
return self.__render_string
|
||||||
|
|
||||||
def print(self, acquire_locks=True):
|
def print(self, acquire_locks=True):
|
||||||
|
|
||||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
s = self.get(acquire_locks=acquire_locks)
|
self._update_render_if_dirty(acquire_locks=False)
|
||||||
|
|
||||||
|
s = ""
|
||||||
|
|
||||||
|
# Move the cursor up to compensate
|
||||||
|
if self.__print_upward:
|
||||||
|
up_count = self.__grid_height
|
||||||
|
s += ("\033[F" * up_count)
|
||||||
|
|
||||||
|
# Grab render
|
||||||
|
s += self.get(acquire_locks=False)
|
||||||
|
|
||||||
print(s)
|
print(s)
|
||||||
|
|
||||||
@ -95,7 +144,7 @@ class TextCanvas:
|
|||||||
x = int(x)
|
x = int(x)
|
||||||
|
|
||||||
for c in text:
|
for c in text:
|
||||||
self.write(x=x, y=y, character=c, acquire_locks=acquire_locks)
|
self.write(x=x, y=y, character=c, acquire_locks=False)
|
||||||
x += 1
|
x += 1
|
||||||
|
|
||||||
def draw_line(self, start_x, start_y, end_x, end_y, character="*", acquire_locks=True):
|
def draw_line(self, start_x, start_y, end_x, end_y, character="*", acquire_locks=True):
|
||||||
@ -129,7 +178,7 @@ class TextCanvas:
|
|||||||
pos_x_rounded = round(pos_x)
|
pos_x_rounded = round(pos_x)
|
||||||
pos_y_rounded = round(pos_y)
|
pos_y_rounded = round(pos_y)
|
||||||
|
|
||||||
self.write(x=pos_x_rounded, y=pos_y_rounded, character=character, acquire_locks=acquire_locks)
|
self.write(x=pos_x_rounded, y=pos_y_rounded, character=character, acquire_locks=False)
|
||||||
|
|
||||||
pos_x += increment_x
|
pos_x += increment_x
|
||||||
pos_y += increment_y
|
pos_y += increment_y
|
||||||
@ -138,8 +187,7 @@ class TextCanvas:
|
|||||||
|
|
||||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
x = round(x)
|
x, y = self._translate_virtual_to_internal_coordinates(x=x, y=y)
|
||||||
y = round(y)
|
|
||||||
|
|
||||||
if 0 <= y < len(self.__grid):
|
if 0 <= y < len(self.__grid):
|
||||||
if 0 <= x < len(self.__grid[y]):
|
if 0 <= x < len(self.__grid[y]):
|
||||||
@ -152,17 +200,17 @@ class TextCanvas:
|
|||||||
|
|
||||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
self._update_terminal_dimensions(acquire_locks=acquire_locks)
|
self._auto_adjust_to_terminal(acquire_locks=False)
|
||||||
|
|
||||||
self.__grid = []
|
self.__grid = []
|
||||||
|
|
||||||
for y in range(self.__terminal_height):
|
for y in range(self.__grid_height):
|
||||||
|
|
||||||
self.__grid.append([])
|
self.__grid.append([])
|
||||||
|
|
||||||
for x in range(self.__terminal_width):
|
for x in range(self.__grid_width):
|
||||||
|
|
||||||
self.__grid[y].append(" ")
|
self.__grid[y].append(self.__BACKGROUND_CHARACTER)
|
||||||
|
|
||||||
self.__is_dirty = True
|
self.__is_dirty = True
|
||||||
|
|
||||||
@ -170,13 +218,63 @@ class TextCanvas:
|
|||||||
|
|
||||||
with RAIILock(self.__lock, defer=not acquire_locks):
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
height = len(self.__grid)
|
return self.get_virtual_dimensions()
|
||||||
if height == 0:
|
|
||||||
width = 0
|
|
||||||
else:
|
|
||||||
width = len(self.__grid[0])
|
|
||||||
|
|
||||||
return width, height
|
def get_virtual_dimensions(self, acquire_locks=True):
|
||||||
|
|
||||||
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
|
return self.__virtual_width, self.__virtual_height
|
||||||
|
|
||||||
|
def _get_internal_dimensions(self, acquire_locks=True):
|
||||||
|
|
||||||
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
|
return self.__grid_width, self.__grid_height
|
||||||
|
|
||||||
|
def _translate_virtual_to_internal_coordinates(self, x, y):
|
||||||
|
|
||||||
|
x_translated = x / self.__virtual_width_per_grid_width
|
||||||
|
y_translated = y / self.__virtual_height_per_grid_height
|
||||||
|
|
||||||
|
x_translated = round(x_translated)
|
||||||
|
y_translated = round(y_translated)
|
||||||
|
|
||||||
|
return x_translated, y_translated
|
||||||
|
|
||||||
|
def _is_time_to_update_terminal_dimensions(self):
|
||||||
|
|
||||||
|
if self.__auto_adjust_to_terminal:
|
||||||
|
|
||||||
|
now = time.time()
|
||||||
|
|
||||||
|
if now - self.__last_terminal_dimensions_update > self.__TERMINAL_UPDATE_INTERVAL:
|
||||||
|
return True
|
||||||
|
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _auto_adjust_to_terminal(self, force=False, acquire_locks=True):
|
||||||
|
|
||||||
|
if not force and not self.__auto_adjust_to_terminal:
|
||||||
|
return
|
||||||
|
|
||||||
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
|
self._update_terminal_dimensions(acquire_locks=False)
|
||||||
|
|
||||||
|
self.__grid_width = self.__terminal_width
|
||||||
|
self.__grid_height = self.__terminal_height
|
||||||
|
|
||||||
|
self._auto_calculate_virtual_dimensions(acquire_locks=False)
|
||||||
|
|
||||||
|
def _auto_calculate_virtual_dimensions(self, acquire_locks=True):
|
||||||
|
|
||||||
|
with RAIILock(self.__lock, defer=not acquire_locks):
|
||||||
|
|
||||||
|
self.__virtual_width = self.__grid_width
|
||||||
|
self.__virtual_height = self.__grid_height * self.__text_to_height_width
|
||||||
|
|
||||||
|
self._update_virtual_ratios(acquire_locks=False)
|
||||||
|
|
||||||
def _update_terminal_dimensions(self, force=False, acquire_locks=True):
|
def _update_terminal_dimensions(self, force=False, acquire_locks=True):
|
||||||
|
|
||||||
@ -184,7 +282,7 @@ class TextCanvas:
|
|||||||
|
|
||||||
now = time.time()
|
now = time.time()
|
||||||
|
|
||||||
if force is not False and now - self.__last_terminal_dimensions_update < self.__TERMINAL_UPDATE_INTERVAL:
|
if force is not False and not self._is_time_to_update_terminal_dimensions():
|
||||||
return
|
return
|
||||||
self.__last_terminal_dimensions_update = now
|
self.__last_terminal_dimensions_update = now
|
||||||
|
|
||||||
@ -193,10 +291,11 @@ class TextCanvas:
|
|||||||
self.__terminal_width = width
|
self.__terminal_width = width
|
||||||
self.__terminal_height = height
|
self.__terminal_height = height
|
||||||
|
|
||||||
|
self._auto_calculate_virtual_dimensions(acquire_locks=False)
|
||||||
|
|
||||||
@staticmethod
|
@staticmethod
|
||||||
def determine_terminal_dimensions():
|
def determine_terminal_dimensions():
|
||||||
|
|
||||||
columns, lines = os.get_terminal_size()
|
columns, lines = os.get_terminal_size()
|
||||||
|
|
||||||
return columns, lines
|
return columns, lines
|
||||||
|
|
||||||
|
Loading…
Reference in New Issue
Block a user