139 lines
4.5 KiB
Python
Raw Permalink Normal View History

import curses
from dataclasses import dataclass
from typing import Optional
from .big_digits import BIG_DIGITS
@dataclass
class ColorScheme:
"""Color scheme configuration for the UI"""
PRIMARY = 1 # Green on black
HIGHLIGHT = 2 # Yellow on black
ERROR = 3 # Red on black
class ViewUtils:
"""Common utility functions for view classes"""
@staticmethod
def center_x(width: int, text_width: int) -> int:
"""Calculate x coordinate to center text horizontally"""
return max(0, (width - text_width) // 2)
@staticmethod
def center_y(height: int, text_height: int) -> int:
"""Calculate y coordinate to center text vertically"""
return max(0, (height - text_height) // 2)
@staticmethod
def draw_centered_text(stdscr, y: int, text: str, color_pair: Optional[int] = None, attrs: int = 0):
"""Draw text centered horizontally on the screen with optional color and attributes"""
height, width = stdscr.getmaxyx()
x = ViewUtils.center_x(width, len(text))
if color_pair is not None:
stdscr.attron(curses.color_pair(color_pair) | attrs)
try:
stdscr.addstr(y, x, text)
except curses.error:
pass # Ignore errors from writing at invalid positions
if color_pair is not None:
stdscr.attroff(curses.color_pair(color_pair) | attrs)
def init_colors():
"""Initialize color pairs for the application"""
try:
curses.start_color()
curses.use_default_colors()
# Primary color (green text on black background)
curses.init_pair(ColorScheme.PRIMARY, curses.COLOR_GREEN, curses.COLOR_BLACK)
# Highlight color (yellow text on black background)
curses.init_pair(ColorScheme.HIGHLIGHT, curses.COLOR_YELLOW, curses.COLOR_BLACK)
# Error color (red text on black background)
curses.init_pair(ColorScheme.ERROR, curses.COLOR_RED, curses.COLOR_BLACK)
except Exception as e:
# Log error or handle gracefully if color initialization fails
pass
def draw_error(stdscr, error_message: str, duration_sec: int = 3):
"""
Draw error message at the bottom of the screen
Args:
stdscr: Curses window object
error_message: Message to display
duration_sec: How long the error should be displayed (for reference by caller)
"""
height, width = stdscr.getmaxyx()
# Truncate message if too long
max_width = width - 4
if len(error_message) > max_width:
error_message = error_message[:max_width-3] + "..."
# Position near bottom of screen
error_y = height - 4
ViewUtils.draw_centered_text(
stdscr,
error_y,
error_message,
ColorScheme.ERROR,
curses.A_BOLD
)
def draw_big_digit(stdscr, y: int, x: int, digit: str):
"""
Draw a large digit using the predefined patterns
Args:
stdscr: Curses window object
y: Starting y coordinate
x: Starting x coordinate
digit: Character to draw ('0'-'9', ':', etc)
"""
try:
patterns = BIG_DIGITS.get(digit, BIG_DIGITS['?'])
for i, line in enumerate(patterns):
try:
stdscr.addstr(y + i, x, line)
except curses.error:
continue # Skip lines that would write outside the window
except (curses.error, IndexError):
pass # Ignore any drawing errors
def safe_addstr(stdscr, y: int, x: int, text: str, color_pair: Optional[int] = None, attrs: int = 0):
"""
Safely add a string to the screen, handling boundary conditions
Args:
stdscr: Curses window object
y: Y coordinate
x: X coordinate
text: Text to draw
color_pair: Optional color pair number
attrs: Additional curses attributes
"""
height, width = stdscr.getmaxyx()
# Check if the position is within bounds
if y < 0 or y >= height or x < 0 or x >= width:
return
# Truncate text if it would extend beyond screen width
if x + len(text) > width:
text = text[:width - x]
try:
if color_pair is not None:
stdscr.attron(curses.color_pair(color_pair) | attrs)
stdscr.addstr(y, x, text)
if color_pair is not None:
stdscr.attroff(curses.color_pair(color_pair) | attrs)
except curses.error:
pass # Ignore any drawing errors