Implements BaseRenderer abstract interface and CursesRenderer with green-on-black VT220 aesthetic. Includes all 18 widget methods: header, footer, menu, table, detail, form, filter list, comparison view, box drawing, message dialogs, and input prompts. Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
153 lines
5.6 KiB
Python
153 lines
5.6 KiB
Python
"""
|
|
Abstract base renderer interface for the AUTOPARTES console application.
|
|
|
|
Every renderer (curses VT220, Textual/Rich, etc.) must subclass
|
|
:class:`BaseRenderer` and implement all of its methods. Screens call
|
|
these methods without knowing which backend is active.
|
|
"""
|
|
|
|
|
|
class BaseRenderer:
|
|
"""Abstract interface that all renderers must implement.
|
|
|
|
Methods raise :exc:`NotImplementedError` so that missing overrides
|
|
are caught immediately at runtime.
|
|
"""
|
|
|
|
# ── Lifecycle ────────────────────────────────────────────────────
|
|
|
|
def init_screen(self):
|
|
"""Initialise the terminal / display backend."""
|
|
raise NotImplementedError
|
|
|
|
def cleanup(self):
|
|
"""Restore the terminal to its original state."""
|
|
raise NotImplementedError
|
|
|
|
# ── Screen queries ───────────────────────────────────────────────
|
|
|
|
def get_size(self) -> tuple:
|
|
"""Return ``(height, width)`` of the usable display area."""
|
|
raise NotImplementedError
|
|
|
|
# ── Primitive operations ─────────────────────────────────────────
|
|
|
|
def clear(self):
|
|
"""Clear the entire screen buffer."""
|
|
raise NotImplementedError
|
|
|
|
def refresh(self):
|
|
"""Flush the screen buffer to the terminal."""
|
|
raise NotImplementedError
|
|
|
|
def get_key(self) -> int:
|
|
"""Block until a key is pressed and return its key code."""
|
|
raise NotImplementedError
|
|
|
|
# ── High-level widgets ───────────────────────────────────────────
|
|
|
|
def draw_header(self, title, subtitle=''):
|
|
"""Draw the application header bar on the top two rows.
|
|
|
|
*title* is left-aligned; *subtitle* is right-aligned.
|
|
Row 1 is a horizontal separator.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_footer(self, key_labels):
|
|
"""Draw the footer bar on the bottom two rows.
|
|
|
|
*key_labels* is a list of ``(key, description)`` tuples,
|
|
e.g. ``[("F1", "Ayuda"), ("ESC", "Atras")]``.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_menu(self, items, selected_index=0, title=''):
|
|
"""Draw a numbered menu list starting at row 3.
|
|
|
|
*items* is a list of ``(number, label)`` tuples.
|
|
Separator items have ``number == '---'``.
|
|
The item at *selected_index* is highlighted.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_table(self, headers, rows, widths, page_info=None,
|
|
selected_row=-1):
|
|
"""Draw a columnar data table.
|
|
|
|
*headers*: list of column header strings.
|
|
*rows*: list of row tuples (each tuple matches *headers*).
|
|
*widths*: list of int column widths.
|
|
*page_info*: optional dict ``{page, total_pages, total_rows}``.
|
|
*selected_row*: index of the highlighted row (-1 = none).
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_detail(self, fields, title=''):
|
|
"""Draw a detail view with label-value pairs.
|
|
|
|
*fields* is a list of ``(label, value)`` tuples displayed as
|
|
``Label........: Value``.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_form(self, fields, focused_index=0, title=''):
|
|
"""Draw an editable form.
|
|
|
|
*fields* is a list of dicts with keys:
|
|
``label``, ``value``, ``width``, ``type``, ``hint``.
|
|
The field at *focused_index* uses the active style.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_filter_list(self, items, filter_text, selected_index,
|
|
title=''):
|
|
"""Draw a filterable list with a text input at the top.
|
|
|
|
*items*: list of ``(number, label)`` tuples.
|
|
*filter_text*: current filter string.
|
|
*selected_index*: highlighted item index.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def draw_comparison(self, columns, title=''):
|
|
"""Draw a side-by-side comparison view.
|
|
|
|
*columns* is a list of dicts, each with:
|
|
``header`` (str) and ``rows`` (list of ``(label, value)``).
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
# ── Low-level drawing ────────────────────────────────────────────
|
|
|
|
def draw_text(self, row, col, text, style='normal'):
|
|
"""Draw *text* at ``(row, col)`` using the named *style*."""
|
|
raise NotImplementedError
|
|
|
|
def draw_box(self, top, left, height, width, title=''):
|
|
"""Draw a box with Unicode line-drawing characters.
|
|
|
|
Optional *title* is rendered in the top border.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
# ── Dialogs ──────────────────────────────────────────────────────
|
|
|
|
def show_message(self, text, msg_type='info') -> bool:
|
|
"""Show a centred message box.
|
|
|
|
*msg_type* is one of ``'info'``, ``'error'``, or ``'confirm'``.
|
|
For ``'confirm'`` the user must press S (si) or N (no);
|
|
returns ``True`` for S, ``False`` for N.
|
|
For other types, waits for any key and returns ``True``.
|
|
"""
|
|
raise NotImplementedError
|
|
|
|
def show_input(self, prompt, max_len=40):
|
|
"""Show a centred input dialog.
|
|
|
|
Returns the entered string, or ``None`` if the user pressed
|
|
Escape to cancel.
|
|
"""
|
|
raise NotImplementedError
|