From: Christian Heller Date: Tue, 17 Jun 2025 15:30:21 +0000 (+0200) Subject: Handle tiny screen sizes in TUI rendering. X-Git-Url: https://plomlompom.com/repos/%7B%7Bprefix%7D%7D/%22https:/validator.w3.org/index.html?a=commitdiff_plain;h=96d4572f74e5faf13ddd09c0496818be3a853d0e;p=ircplom Handle tiny screen sizes in TUI rendering. --- diff --git a/ircplom/tui.py b/ircplom/tui.py index 61589b8..77d098d 100644 --- a/ircplom/tui.py +++ b/ircplom/tui.py @@ -17,6 +17,8 @@ from ircplom.irc_conn import ( InitConnWindowEvent, InitReconnectEvent, IrcMessage, LoginNames, LogConnEvent, NickSetEvent, SendEvent, TIMEOUT_LOOP) +_MIN_HEIGHT = 4 +_MIN_WIDTH = 32 _B64_PREFIX = 'b64:' _OSC52_PREFIX = ']52;c;' @@ -71,16 +73,22 @@ class _Widget(ABC): @abstractmethod def __init__(self, *args, **kwargs) -> None: self.tainted = True + self._drawable = False @abstractmethod - def set_geometry(self, measurements: _YX) -> None: + def set_geometry(self, measurements: _YX) -> bool: 'Update widget\'s measurements, re-generate content where necessary.' self.tainted = True + self._drawable = len([m for m in measurements if m < 0]) == 0 + return self._drawable @abstractmethod - def draw(self) -> None: + def draw(self) -> bool: 'Print widget\'s content in shape appropriate to set geometry.' + if not self._drawable: + return False self.tainted = False + return True class _ScrollableWidget(_Widget, ABC): @@ -122,14 +130,15 @@ class _LogWidget(_ScrollableWidget): self._wrapped += [(idx_original, line) for line in wrapped_lines] return len(wrapped_lines) - def set_geometry(self, measurements: _YX) -> None: - super().set_geometry(measurements) + def set_geometry(self, measurements: _YX) -> bool: + if not super().set_geometry(measurements): + return False self._view_size = measurements self._y_pgscroll = self._view_size.y // 2 self._wrapped.clear() self._wrapped += [(None, '')] * self._view_size.y if not self._history: - return + return True for idx_history, line in enumerate(self._history): self._add_wrapped(idx_history, line) wrapped_lines_for_history_idx = [ @@ -137,17 +146,22 @@ class _LogWidget(_ScrollableWidget): if t[0] == len(self._history) + self._history_idx] idx_their_last = self._wrapped.index(wrapped_lines_for_history_idx[-1]) self._wrapped_idx = idx_their_last - len(self._wrapped) + return True def append(self, to_append: str) -> None: super().append(to_append) + self.tainted = True + if self._history_idx < -1: + self._history_idx -= 1 + if not self._drawable: + return n_wrapped_lines = self._add_wrapped(len(self._history) - 1, to_append) if self._wrapped_idx < -1: - self._history_idx -= 1 self._wrapped_idx -= n_wrapped_lines - self.tainted = True - def draw(self) -> None: - super().draw() + def draw(self) -> bool: + if not super().draw(): + return False start_idx = self._wrapped_idx - self._view_size.y + 1 end_idx = self._wrapped_idx to_write = [t[1] for t in self._wrapped[start_idx:end_idx]] @@ -159,9 +173,12 @@ class _LogWidget(_ScrollableWidget): to_write += [self._wrapped[self._wrapped_idx][1]] for i, line in enumerate(to_write): self._write(line, i) + return True def _scroll(self, up: bool = True) -> None: super()._scroll(up) + if not self._drawable: + return if up: self._wrapped_idx = max(self._view_size.y + 1 - len(self._wrapped), self._wrapped_idx - self._y_pgscroll) @@ -195,12 +212,15 @@ class _PromptWidget(_ScrollableWidget): self.tainted = True self._input_buffer_unsafe = content - def set_geometry(self, measurements: _YX) -> None: - super().set_geometry(measurements) + def set_geometry(self, measurements: _YX) -> bool: + if not super().set_geometry(measurements): + return False self._y, self._width = measurements + return True - def draw(self) -> None: - super().draw() + def draw(self) -> bool: + if not super().draw(): + return False prefix = self._prompt[:] content = self._input_buffer if self._cursor_x == len(self._input_buffer): @@ -221,6 +241,7 @@ class _PromptWidget(_ScrollableWidget): self._write(to_write[cursor_x_to_write], attribute='reverse', padding=False) self._write(to_write[cursor_x_to_write + 1:]) + return True def _archive_prompt(self) -> None: self.append(self._input_buffer) @@ -316,21 +337,38 @@ class _Window(_Widget): if hasattr(self._term, 'size'): self.set_geometry() - def set_geometry(self, _=None) -> None: - super().set_geometry(_) + def set_geometry(self, _=None) -> bool: assert _ is None + if self._term.size.y < _MIN_HEIGHT or self._term.size.x < _MIN_WIDTH: + bad_yx = _YX(-1, -1) + super().set_geometry(bad_yx) + self.log.set_geometry(bad_yx) + self.prompt.set_geometry(bad_yx) + return False + super().set_geometry(_YX(0, 0)) self._y_status = self._term.size.y - 2 self.log.set_geometry(_YX(self._y_status, self._term.size.x)) self.prompt.set_geometry(_YX(self._term.size.y - 1, self._term.size.x)) + return True - def draw(self) -> None: - super().draw() + def draw(self) -> bool: + self._term.clear() + if not super().draw(): + if self._term.size.x > 0: + lines = [''] + for i, c in enumerate('screen too small'): + if i > 0 and 0 == i % self._term.size.x: + lines += [''] + lines[-1] += c + for y, line in enumerate(lines): + self._term.write(line, y) + return False idx_box = f'[{self.idx}]' status_line = idx_box + '=' * (self._term.size.x - len(idx_box)) - self._term.clear() self.log.draw() self._term.write(status_line, self._y_status) self.prompt.draw() + return True def cmd__paste(self) -> None: 'Write OSC 52 ? sequence to get encoded clipboard paste into stdin.' @@ -575,7 +613,7 @@ class Terminal: padding: bool = True ) -> None: 'Print to terminal, with position, padding to line end, attributes.' - if start_y: + if start_y is not None: self._cursor_yx = _YX(start_y, 0) # ._blessed.length can slow down things notably: only use where needed! end_x = self._cursor_yx.x + (len(msg) if msg.isascii()