aboutsummaryrefslogtreecommitdiffstats
path: root/src/epy_reader/board.py
diff options
context:
space:
mode:
authorBenawi Adha <benawiadha@gmail.com>2022-10-02 21:22:38 +0700
committerBenawi Adha <benawiadha@gmail.com>2022-10-02 21:22:38 +0700
commit258c30d2e088cd4ab091a53794da3f93af79915d (patch)
treef49340bf565deb20c730358af74a01bcc231de53 /src/epy_reader/board.py
parentd43533f01d9d5baf5f78b71f832641382bd5962a (diff)
downloadepy-258c30d2e088cd4ab091a53794da3f93af79915d.tar.gz
Major refactor: breakdown epy.py script
into package project structure for easier development Squashed commit of the following: commit 01309b961a4ab32394bff0d90949b57435dfda47 Author: Benawi Adha <benawiadha@gmail.com> Date: Sun Oct 2 21:15:04 2022 +0700 Fix missing objects commit aab2e773c30b255c81b1250b3b20967d5da40338 Author: Benawi Adha <benawiadha@gmail.com> Date: Sun Oct 2 21:09:31 2022 +0700 Update README.md commit d4e98926bcd9b00ce0410ad71249d24e6315abc5 Author: Benawi Adha <benawiadha@gmail.com> Date: Sun Oct 2 21:07:28 2022 +0700 Add keywords in pyproject.toml commit 432055af8245560a3ff2e046aef0b4e87da44930 Author: Benawi Adha <benawiadha@gmail.com> Date: Sun Oct 2 21:04:34 2022 +0700 Bump version and deprecete setup.py commit 51dd15aab8f8ff5996f822f8378e813f0b9fb80d Author: Benawi Adha <benawiadha@gmail.com> Date: Sun Oct 2 20:56:38 2022 +0700 Formatting commit 81fb35e3b6fa0e27d79ef1da77202ed81eb99500 Author: Benawi Adha <benawiadha@gmail.com> Date: Sun Oct 2 20:55:08 2022 +0700 Fix speakers module commit 3b852e7c59b38d5a28520038e35f50a95270d2f1 Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 20:52:46 2022 +0700 Fix circular import commit 061e8a2649dabacd28a9e2f972559475316c654c Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 20:39:27 2022 +0700 Run formatting commit abc2d0ab156992c63dc04745d14a69679a60accb Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 20:39:00 2022 +0700 Update isort and black config in pyproject commit 5dc2e41bab5b997bd719bdc1561eb51ba0c17a83 Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 20:31:00 2022 +0700 Add app Config commit ed485a2ea8281585bf86dc5772f0c6dd9c803cc4 Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 20:23:02 2022 +0700 Update debugpy script commit 68b0553dd4d63eb4b847132c68ea4018587fa8ec Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 20:14:11 2022 +0700 Connect reader to main script commit 63c3dd176f18a784a4ed2e88aa72b13d1c2b0990 Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 20:11:17 2022 +0700 Implement reader commit ce5eec8fb4e1db3870a16a07541365cd777d6c4c Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 19:29:49 2022 +0700 Fix script in pyproject.toml commit 941e8e49f1593731fb582d92084206772b3f0442 Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 19:28:39 2022 +0700 Rename modules commit 5a3e7f766aee774c09b3b5336f3a2968e9cb1d0c Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 19:28:20 2022 +0700 Rename tool method commit 3c0503ff475cb7eff8b12d3be0bda7a38efe1072 Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 19:27:03 2022 +0700 Add ebooks lib commit b5f71c3296a7d6f36454f6e1cbe84e15a45092ee Author: Benawi Adha <benawiadha@gmail.com> Date: Sat Oct 1 17:25:11 2022 +0700 Initial reorganization
Diffstat (limited to 'src/epy_reader/board.py')
-rw-r--r--src/epy_reader/board.py148
1 files changed, 148 insertions, 0 deletions
diff --git a/src/epy_reader/board.py b/src/epy_reader/board.py
new file mode 100644
index 0000000..0562d3f
--- /dev/null
+++ b/src/epy_reader/board.py
@@ -0,0 +1,148 @@
+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,
+ )