pyblock/pybitblock/shared/rich_ui.py
GaltRanch cb89d6fbcf Restore Panel boxes with transparent background (style='on default')
Bring back the Panel borders for sysinfo, status bar, header, and menu
but use style='on default' which inherits the terminal's background
color instead of Rich's default dark gray. border_style='dim' keeps
the borders subtle.

Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-01 15:29:47 -03:00

189 lines
6 KiB
Python

"""
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("")