from __future__ import annotations import curses from pathlib import Path from .colors import UIColors from .views import HeaderView, GraphView, NumberView, LogView class CollatzDashboard: """Coordinates curses windows and renders the visualization.""" def __init__(self, stdscr: "curses._CursesWindow", log_dir: Path) -> None: self.stdscr = stdscr self.log_dir = log_dir try: curses.curs_set(0) except curses.error: pass self.stdscr.nodelay(True) self.stdscr.timeout(100) UIColors.setup() self.num_scroll = 0 self.log_scroll = 0 self._last_step = 0 self._build_views() def _build_views(self) -> None: lines = curses.LINES cols = curses.COLS header_h = 6 graph_h = 1 available = max(2, lines - header_h - graph_h - 2) num_h = max(1, (available * 3) // 4) log_h = max(1, available - num_h) self.header = HeaderView(header_h, cols, 0, 0) self.graph = GraphView(graph_h, cols, header_h + 1, 0) self.number = NumberView(num_h, cols, header_h + graph_h + 1, 0) self.log = LogView(log_h, cols, header_h + graph_h + num_h + 2, 0, self.log_dir) def render( self, step: int, elapsed: float, avg_step: float, digits: str, delta_sign: int, current_calc: float, last_calc: float, computing: bool, ) -> None: self.header.render( step, elapsed, avg_step, len(digits), current_calc, last_calc, computing, ) if step > self._last_step: self.graph.add_delta(delta_sign) self._last_step = step else: self.graph.redraw() self.num_scroll = self.number.render(digits, self.num_scroll) self.log_scroll = self.log.render(self.log_scroll) curses.doupdate() def handle_input(self, ch: int) -> None: if ch == curses.KEY_UP: self.num_scroll = max(0, self.num_scroll - 1) elif ch == curses.KEY_DOWN: self.num_scroll += 1 elif ch == curses.KEY_PPAGE: self.log_scroll = max(0, self.log_scroll - 3) elif ch == curses.KEY_NPAGE: self.log_scroll += 3 def show_message(self, message: str, color_pair: int) -> None: self.stdscr.nodelay(False) self.stdscr.erase() try: self.stdscr.addstr(0, 0, message, UIColors.style(color_pair, bold=True)) except curses.error: self.stdscr.addstr(0, 0, message) self.stdscr.refresh() self.stdscr.getch() self.stdscr.nodelay(True) self.stdscr.timeout(10)