home · contact · privacy
Refactor widget sizing, drawing.
authorChristian Heller <c.heller@plomlompom.de>
Sat, 7 Jun 2025 15:58:48 +0000 (17:58 +0200)
committerChristian Heller <c.heller@plomlompom.de>
Sat, 7 Jun 2025 15:58:48 +0000 (17:58 +0200)
ircplom.py

index e63a1e21079cbd625fe510416783f12e17c5e1b8..b754b826f3db5a6ea97da9b7b14f408b4cb0b330 100755 (executable)
@@ -74,7 +74,7 @@ class Terminal:
     @contextmanager
     def context(self, q_to_main: EventQueue) -> Generator:
         'Combine multiple contexts into one.'
-        signal(SIGWINCH, lambda *_: q_to_main.eput('SIGWINCH'))
+        signal(SIGWINCH, lambda *_: q_to_main.eput('SET_SCREEN'))
         self._blessed = BlessedTerminal()
         with (self._blessed.raw(),
               self._blessed.fullscreen(),
@@ -412,7 +412,7 @@ class Widget(ABC):
     'Defines most basic TUI object API.'
 
     @abstractmethod
-    def set_geometry(self, measurements: tuple[int | YX, ...]) -> None:
+    def set_geometry(self, measurements: YX) -> None:
         'Update widget\'s measurements, re-generate content where necessary.'
 
     @abstractmethod
@@ -451,9 +451,7 @@ class PromptWidget(ScrollableWidget):
         self._input_buffer = ''
         self._history_idx = 0
 
-    def set_geometry(self, measurements: tuple[int | YX, ...]) -> None:
-        assert len(measurements) == 1
-        assert isinstance(measurements[0], int)
+    def set_geometry(self, measurements: YX) -> None:
         self._start_y = measurements[0]
 
     def append(self, to_append: str) -> None:
@@ -462,8 +460,8 @@ class PromptWidget(ScrollableWidget):
         self.draw()
 
     def draw(self) -> None:
-        self._write_yx(YX(self._start_y, len(INPUT_PROMPT)),
-                       f'{self._input_buffer}_')
+        self._write_yx(YX(self._start_y, 0),
+                       f'{INPUT_PROMPT} {self._input_buffer}_')
 
     def _scroll(self, up: bool = True) -> None:
         if up and -(self._history_idx) < len(self._history):
@@ -520,10 +518,8 @@ class LogWidget(ScrollableWidget):
         self._wrapped += [(idx_original, line) for line in wrapped_lines]
         return len(wrapped_lines)
 
-    def set_geometry(self, measurements: tuple[int | YX, ...]) -> None:
-        assert len(measurements) == 1
-        assert isinstance(measurements[0], YX)
-        self._view_size = measurements[0]
+    def set_geometry(self, measurements: YX) -> None:
+        self._view_size = measurements
         self._y_pgscroll = self._view_size.y // 2
         self._wrapped.clear()
         self._wrapped += [(None, '')] * self._view_size.y
@@ -571,23 +567,25 @@ class LogWidget(ScrollableWidget):
 
 class Window(Widget):
     'Collects a log and a prompt meant for the same content stream.'
+    _y_separator: int
 
     def __init__(self, term: Terminal) -> None:
         self._term = term
         self.log = LogWidget(self._term.wrap, self._term.write_yx)
         self.prompt = PromptWidget(self._term.write_yx)
+        if hasattr(self._term, 'size'):
+            self.set_geometry()
 
-    def set_geometry(self, measurements: tuple[int | YX, ...]) -> None:
-        assert len(measurements) == 2
-        assert isinstance(measurements[0], int)
-        assert isinstance(measurements[1], int)
-        log_height = measurements[0]
-        start_y_prompt = measurements[1]
-        self.log.set_geometry((YX(log_height, self._term.size.x),))
-        self.prompt.set_geometry((start_y_prompt,))
+    def set_geometry(self, _=None) -> None:
+        assert _ is None
+        self._y_separator = self._term.size.y - 2
+        self.log.set_geometry(YX(self._y_separator, self._term.size.x))
+        self.prompt.set_geometry(YX(self._term.size.y - 1, self._term.size.x))
 
     def draw(self) -> None:
+        self._term.clear()
         self.log.draw()
+        self._term.write_yx(YX(self._y_separator, 0), '=' * self._term.size.x)
         self.prompt.draw()
 
 
@@ -599,9 +597,8 @@ class TuiLoop(Loop):
         self._windows = [Window(self._term)]
         self._conn_windows: list[Window] = []
         self._window_idx = 0
-        self._calc_and_draw_all()
-        self._term.flush()
         super().__init__(*args, **kwargs)
+        self.put(Event('SET_SCREEN'))
 
     def _cmd_name_to_cmd(self, cmd_name: str) -> Optional[Callable]:
         cmd_parent = self
@@ -621,9 +618,13 @@ class TuiLoop(Loop):
     def process_main(self, event: Event) -> bool:
         if not super().process_main(event):
             return False
-        if event.type_ == 'CONNECTION_WINDOW':
+        if event.type_ == 'SET_SCREEN':
+            self._term.calc_geometry()
+            for window in self._windows:
+                window.set_geometry()
+            self.window.draw()
+        elif event.type_ == 'CONNECTION_WINDOW':
             conn_win = Window(self._term)
-            conn_win.set_geometry((self._y_separator, self._y_prompt))
             self._windows += [conn_win]
             self._conn_windows += [conn_win]
         elif event.type_ == 'ALERT':
@@ -645,8 +646,6 @@ class TuiLoop(Loop):
             cmd(*event.payload[1:])
         elif event.type_ == 'INPUT_CHAR':
             self.window.prompt.append(event.payload)
-        elif event.type_ == 'SIGWINCH':
-            self._calc_and_draw_all()
         # elif event.type_ == 'DEBUG':
         #     from traceback import format_exception
         #     for line in '\n'.join(format_exception(event.payload)
@@ -663,17 +662,6 @@ class TuiLoop(Loop):
         'Currently selected Window.'
         return self._windows[self._window_idx]
 
-    def _calc_and_draw_all(self) -> None:
-        self._term.clear()
-        self._term.calc_geometry()
-        self._y_prompt = self._term.size.y - 1
-        self._y_separator = self._term.size.y - 2
-        self._term.write_yx(YX(self._y_separator, 0), '=' * self._term.size.x)
-        self._term.write_yx(YX(self._y_prompt, 0), INPUT_PROMPT)
-        for window in self._windows:
-            window.set_geometry((self._y_separator, self._y_prompt))
-        self.window.draw()
-
     def cmd__connect(self,
                      hostname: str,
                      username: str,