""" Rich-based UI components for PyBLOCK. Provides styled menus, status bars, error panels, and progress indicators using the Rich library. Falls back to ANSI equivalents from shared.ui if needed. """ from rich.console import Console from rich.table import Table from rich.panel import Panel from rich.text import Text from rich.columns import Columns from rich.progress import Progress, SpinnerColumn, TextColumn, BarColumn from rich.style import Style from rich.theme import Theme # PyBLOCK theme PYBLOCK_THEME = Theme({ "pyblock.title": "bold red", "pyblock.mode.local": "bold green", "pyblock.mode.remote": "bold cyan", "pyblock.mode.lite": "bold yellow", "pyblock.mode.onchain": "bold yellow", "pyblock.menu.key": "bold green", "pyblock.menu.label": "white", "pyblock.menu.bitcoin": "bold rgb(255,102,0)", "pyblock.menu.lightning": "bold yellow", "pyblock.menu.platforms": "bold rgb(0,200,0)", "pyblock.menu.settings": "bold blue", "pyblock.menu.donate": "bold white", "pyblock.menu.exit": "bold rgb(128,0,255)", "pyblock.error": "bold red", "pyblock.warning": "bold yellow", "pyblock.success": "bold green", "pyblock.dim": "dim white", "pyblock.price": "bold green", "pyblock.block": "bold white", }) console = Console(theme=PYBLOCK_THEME, highlight=False, color_system="truecolor") def rich_status_bar(mode="", block_height="", btc_price="", extra=""): """Render a styled status bar with mode, block height, and BTC price.""" mode_styles = { "local": "pyblock.mode.local", "remote": "pyblock.mode.remote", "onchain_only": "pyblock.mode.onchain", "lite": "pyblock.mode.lite", } mode_labels = { "local": "Bitcoin + Lightning", "remote": "Remote Node", "onchain_only": "Bitcoin Only", "lite": "Lite Mode", } parts = [] label = mode_labels.get(mode, mode) style = mode_styles.get(mode, "white") if label: parts.append(Text(label, style=style)) if block_height: t = Text() t.append("Block: ", style="pyblock.dim") t.append(block_height, style="pyblock.block") parts.append(t) if btc_price: t = Text() t.append("BTC: ", style="pyblock.dim") t.append(f"${btc_price}", style="pyblock.price") parts.append(t) if extra: parts.append(Text(extra)) separator = Text(" | ", style="pyblock.dim") combined = Text() for i, part in enumerate(parts): if i > 0: combined.append_text(separator) combined.append_text(part) console.print(Panel(combined, expand=False, style="on default", border_style="dim", padding=(0, 2))) def rich_sysinfo(cpu_percent, mem_percent): """Render CPU and Memory with colored bars in a panel.""" cpu_color = "green" if cpu_percent < 70 else ("yellow" if cpu_percent < 90 else "red") mem_color = "green" if mem_percent < 70 else ("yellow" if mem_percent < 90 else "red") cpu_bar = _make_bar(cpu_percent, cpu_color) mem_bar = _make_bar(mem_percent, mem_color) text = Text.from_markup( f"[italic yellow]CPU[/] {cpu_bar} [bold {cpu_color}]{cpu_percent}%[/]\n" f"[italic yellow]Memory[/] {mem_bar} [bold {mem_color}]{mem_percent}%[/]" ) console.print(Panel(text, expand=False, style="on default", border_style="dim")) def _make_bar(percent, color): """Create a simple text-based progress bar.""" filled = int(percent / 5) empty = 20 - filled return f"[{color}]{'█' * filled}[/{color}][rgb(60,60,60)]{'─' * empty}[/rgb(60,60,60)]" def rich_menu(title, items, footer_text=""): """Render a styled menu in a panel. Args: title: Menu section title items: List of (key, label, style) tuples footer_text: Optional text below the menu """ lines = [] for key, label, style in items: lines.append(f"[bold {style}]{key}.[/] {label}") if footer_text: lines.append(f"\n[dim]{footer_text}[/dim]") content = Text.from_markup("\n".join(lines)) console.print(Panel(content, expand=False, style="on default", border_style="dim", padding=(1, 2))) console.print() def rich_error(message): """Display error in a red panel.""" console.print(Panel( Text(f" {message}", style="white"), title="Error", title_align="left", style="pyblock.error", expand=False, padding=(0, 1), )) def rich_warning(message): """Display warning in a yellow panel.""" console.print(Panel( Text(f" {message}", style="white"), title="Warning", title_align="left", style="pyblock.warning", expand=False, padding=(0, 1), )) def rich_success(message): """Display success message.""" console.print(f" [pyblock.success]✓[/pyblock.success] {message}") def rich_header(node_type, block_height, version, alias=None): """Render the main menu header with node info.""" info = Text() info.append(f"{node_type}", style="bold white") info.append(": ", style="dim") info.append("PyBLOCK", style="bold red") info.append("\n") if alias: info.append("Node: ", style="bold white") info.append(f"{alias}", style="bold yellow") info.append("\n") info.append("Block: ", style="bold white") info.append(f"{block_height}", style="bold green") info.append(" ") info.append("Version: ", style="bold white") info.append(f"{version}", style="dim") console.print(Panel(info, expand=False, style="on default", border_style="dim", padding=(0, 2))) def rich_loading(label="Loading"): """Create a Rich progress context for loading operations.""" return Progress( SpinnerColumn(), TextColumn("[pyblock.dim]{task.description}"), transient=True, console=console, ) def rich_prompt(prompt_text="Select option"): """Styled input prompt.""" console.print(f" [bold green]{prompt_text}:[/bold green] ", end="") return input("")