import curses
import re
from typing import Optional, Tuple, Union
from epy_reader.models import Direction, InlineStyle, Key, NoUpdate
from epy_reader.settings import DoubleSpreadPadding
class InfiniBoard:
"""
Wrapper for curses screen to render infinite texts.
The idea is instead of pre render all the text before reading,
this will only renders part of text on demand by which available
page on screen.
And what this does is only drawing text/string on curses screen
without .clear() or .refresh() to optimize performance.
"""
def __init__(
self,
screen,
text: Tuple[str, ...],
textwidth: int = 80,
default_style: Tuple[InlineStyle, ...] = tuple(),
spread: int = 1,
):
self.screen = screen
self.screen_rows, self.screen_cols = self.screen.getmaxyx()
self.textwidth = textwidth
self.x = ((self.screen_cols - self.textwidth) // 2) + 1
self.text = text
self.total_lines = len(text)
self.default_style: Tuple[InlineStyle, ...] = default_style
self.temporary_style: Tuple[InlineStyle, ...] = ()
self.spread = spread
if self.spread == 2:
self.x = DoubleSpreadPadding.LEFT.value
self.x_alt = (
DoubleSpreadPadding.LEFT.value + self.textwidth + DoubleSpreadPadding.MIDDLE.value
)
def feed_temporary_style(self, styles: Optional[Tuple[InlineStyle, ...]] = None) -> None:
"""Reset styling if `styles` is None"""
self.temporary_style = styles if styles else ()
def render_styles(
self, row: int, styles: Tuple[InlineStyle, ...] = (), bottom_padding: int = 0
) -> None:
for i in styles:
if i.row in range(row, row + self.screen_rows - bottom_padding):
self.chgat(row, i.row, i.col, i.n_letters, self.screen.getbkgd() | i.attr)
if self.spread == 2 and i.row in range(
row + self.screen_rows - bottom_padding,
row + 2 * (self.screen_rows - bottom_padding),
):
self.chgat(
row,
i.row - (self.screen_rows - bottom_padding),
-self.x + self.x_alt + i.col,
i.n_letters,
self.screen.getbkgd() | i.attr,
)
def getch(self) -> Union[NoUpdate, Key]:
input = self.screen.getch()
if input == -1:
return NoUpdate()
return Key(input)
def getbkgd(self):
return self.screen.getbkgd()
def chgat(self, row: int, y: int, x: int, n: int, attr: int) -> None:
self.screen.chgat(y - row, self.x + x, n, attr)
def write(self, row: int, bottom_padding: int = 0) -> None:
for n_row in range(min(self.screen_rows - bottom_padding, self.total_lines - row)):
text_line = self.text[row + n_row]
self.screen.addstr(n_row, self.x, text_line)
if (
self.spread == 2
and row + self.screen_rows - bottom_padding + n_row < self.total_lines
):
text_line = self.text[row + self.screen_rows - bottom_padding + n_row]
# TODO: clean this up
if re.search("\\[IMG:[0-9]+\\]", text_line):
self.screen.addstr(
n_row, self.x_alt, text_line.center(self.textwidth), curses.A_BOLD
)
else:
self.screen.addstr(n_row, self.x_alt, text_line)
self.render_styles(row, self.default_style, bottom_padding)
self.render_styles(row, self.temporary_style, bottom_padding)
# self.screen.refresh()
def write_n(
self,
row: int,
n: int = 1,
direction: Direction = Direction.FORWARD,
bottom_padding: int = 0,
) -> None:
assert n > 0
for n_row in range(min(self.screen_rows - bottom_padding, self.total_lines - row)):
text_line = self.text[row + n_row]
if direction == Direction.FORWARD:
# self.screen.addnstr(n_row, self.x + self.textwidth - n, self.text[row+n_row], n)
# `+ " " * (self.textwidth - len(self.text[row + n_row]))` is workaround to
# to prevent curses trace because not calling screen.clear()
self.screen.addnstr(
n_row,
self.x + self.textwidth - n,
text_line + " " * (self.textwidth - len(text_line)),
n,
)
if (
self.spread == 2
and row + self.screen_rows - bottom_padding + n_row < self.total_lines
):
text_line_alt = self.text[row + n_row + self.screen_rows - bottom_padding]
self.screen.addnstr(
n_row,
self.x_alt + self.textwidth - n,
text_line_alt + " " * (self.textwidth - len(text_line_alt)),
n,
)
else:
if text_line[self.textwidth - n :]:
self.screen.addnstr(n_row, self.x, text_line[self.textwidth - n :], n)
if (
self.spread == 2
and row + self.screen_rows - bottom_padding + n_row < self.total_lines
):
text_line_alt = self.text[row + n_row + self.screen_rows - bottom_padding]
self.screen.addnstr(
n_row,
self.x_alt,
text_line_alt[self.textwidth - n :],
n,
)