Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
"""Screen layout and rendering engine for the enhanced block clock.
|
|
|
|
|
|
|
|
|
|
Uses ANSI cursor positioning for flicker-free partial screen updates.
|
|
|
|
|
Composes cfonts output with widget overlays.
|
|
|
|
|
"""
|
|
|
|
|
|
|
|
|
|
import shutil
|
|
|
|
|
import sys
|
|
|
|
|
import time
|
|
|
|
|
|
|
|
|
|
from cfonts import render
|
|
|
|
|
|
|
|
|
|
from .widgets import (
|
|
|
|
|
render_countdown,
|
|
|
|
|
render_epoch_bar,
|
|
|
|
|
render_fees,
|
|
|
|
|
render_utc_time,
|
2026-04-02 09:08:18 -03:00
|
|
|
render_miner_pool,
|
|
|
|
|
render_block_weight,
|
|
|
|
|
render_peer_count,
|
|
|
|
|
render_block_time_histogram,
|
|
|
|
|
render_streak,
|
|
|
|
|
render_moon_phase,
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
)
|
|
|
|
|
from .sparkline import render_sparkline
|
|
|
|
|
from .generative import hash_art
|
|
|
|
|
from .sound import play_sound
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
# ANSI helpers
|
|
|
|
|
def _move(row, col=1):
|
|
|
|
|
return f"\033[{row};{col}H"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _clear_line():
|
|
|
|
|
return "\033[2K"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _hide_cursor():
|
|
|
|
|
return "\x1b[?25l"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _show_cursor():
|
|
|
|
|
return "\x1b[?25h"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _bold(text):
|
|
|
|
|
return f"\033[1m{text}\033[0m"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _dim(text):
|
|
|
|
|
return f"\033[2m{text}\033[0m"
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _clear_screen():
|
|
|
|
|
sys.stdout.write("\033[2J\033[H")
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
def _render_block_height(height, settings):
|
|
|
|
|
"""Render block height using cfonts."""
|
|
|
|
|
colors = [settings.get('colorA', 'green'), settings.get('colorB', 'yellow')]
|
|
|
|
|
gradient = settings.get('gradient', '')
|
|
|
|
|
font = settings.get('design', 'block')
|
|
|
|
|
|
|
|
|
|
kwargs = {'align': 'center', 'font': font}
|
|
|
|
|
if gradient == 'grd':
|
|
|
|
|
kwargs['gradient'] = colors
|
|
|
|
|
else:
|
|
|
|
|
kwargs['colors'] = colors
|
|
|
|
|
|
|
|
|
|
return render(str(height), **kwargs)
|
|
|
|
|
|
|
|
|
|
|
|
|
|
|
class Layout:
|
|
|
|
|
"""Manages screen regions and partial updates."""
|
|
|
|
|
|
|
|
|
|
def __init__(self, settings):
|
|
|
|
|
self.settings = settings
|
|
|
|
|
self.term_width, self.term_height = shutil.get_terminal_size((80, 24))
|
|
|
|
|
self._height_lines = 0
|
|
|
|
|
self._heartbeat_step = 0
|
|
|
|
|
self._last_rendered_height = None
|
2026-04-02 09:08:18 -03:00
|
|
|
self._countdown_row = 0
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
|
|
|
|
|
def _is_zen(self):
|
|
|
|
|
return self.settings.get('zen_mode', False)
|
|
|
|
|
|
|
|
|
|
def _write(self, text):
|
|
|
|
|
sys.stdout.write(text)
|
|
|
|
|
sys.stdout.flush()
|
|
|
|
|
|
|
|
|
|
def _render_cfonts_at(self, output, start_row):
|
|
|
|
|
"""Render cfonts output at a specific row, line by line."""
|
|
|
|
|
lines = output.rstrip('\n').split('\n')
|
|
|
|
|
self._height_lines = len(lines)
|
|
|
|
|
buf = []
|
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
|
buf.append(_move(start_row + i) + _clear_line() + line)
|
|
|
|
|
self._write(''.join(buf))
|
|
|
|
|
return start_row + len(lines)
|
|
|
|
|
|
|
|
|
|
def render_full(self, data):
|
|
|
|
|
"""Clear screen and render all regions."""
|
|
|
|
|
_clear_screen()
|
|
|
|
|
self._write(_hide_cursor())
|
|
|
|
|
self.term_width, self.term_height = shutil.get_terminal_size((80, 24))
|
|
|
|
|
|
|
|
|
|
output = _render_block_height(data.block_height, self.settings)
|
|
|
|
|
self._last_rendered_height = data.block_height
|
|
|
|
|
|
|
|
|
|
if self._is_zen():
|
|
|
|
|
# Center vertically
|
|
|
|
|
lines = output.rstrip('\n').split('\n')
|
|
|
|
|
start = max(1, (self.term_height - len(lines)) // 2)
|
|
|
|
|
self._render_cfonts_at(output, start)
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
# Region 1: Block height (row 2)
|
|
|
|
|
next_row = self._render_cfonts_at(output, 2)
|
|
|
|
|
|
|
|
|
|
# Region 2: Block info
|
|
|
|
|
next_row = self._render_info(data, next_row + 1)
|
|
|
|
|
|
|
|
|
|
# Region 3+: Widgets
|
|
|
|
|
self._render_widgets(data, next_row + 1)
|
|
|
|
|
|
|
|
|
|
def _render_info(self, data, row):
|
|
|
|
|
"""Render block size and tx count line."""
|
|
|
|
|
if data.block_size > 0:
|
|
|
|
|
size_mb = data.block_size / 1_000_000
|
|
|
|
|
info = f" \033[0;37;40m{size_mb:.2f} MB · {data.block_tx_count} txs"
|
|
|
|
|
center_pad = max(0, (self.term_width - len(info) + 20) // 2)
|
|
|
|
|
self._write(_move(row) + _clear_line() + ' ' * center_pad + info)
|
|
|
|
|
return row + 1
|
|
|
|
|
return row
|
|
|
|
|
|
|
|
|
|
def _render_widgets(self, data, start_row):
|
|
|
|
|
"""Render all enabled widget overlays."""
|
|
|
|
|
row = start_row
|
|
|
|
|
s = self.settings
|
|
|
|
|
w = self.term_width
|
|
|
|
|
|
|
|
|
|
if s.get('show_countdown', True):
|
2026-04-02 09:08:18 -03:00
|
|
|
self._countdown_row = row
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
text = render_countdown(data.seconds_since_block, w)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 2
|
|
|
|
|
|
|
|
|
|
if s.get('show_epoch_bar', True):
|
|
|
|
|
text = render_epoch_bar(
|
|
|
|
|
data.block_height, data.epoch_block,
|
|
|
|
|
data.blocks_to_halving, data.next_halving_block, w
|
|
|
|
|
)
|
2026-04-02 09:08:18 -03:00
|
|
|
lines = text.split('\n')
|
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
|
self._write(_move(row + i) + _clear_line() + line)
|
|
|
|
|
row += len(lines) + 1
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
|
|
|
|
|
if s.get('show_fee_rates', True):
|
|
|
|
|
text = render_fees(
|
|
|
|
|
data.fee_fastest, data.fee_half_hour, data.fee_hour, w
|
|
|
|
|
)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 2
|
|
|
|
|
|
|
|
|
|
if s.get('show_sparkline', False) and data.hashrate_history:
|
|
|
|
|
text = render_sparkline(
|
|
|
|
|
data.hashrate_history, data.hashrate_current, w
|
|
|
|
|
)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 2
|
|
|
|
|
|
2026-04-02 09:08:18 -03:00
|
|
|
if s.get('show_miner_pool', True) and data.miner_pool:
|
|
|
|
|
text = render_miner_pool(data.miner_pool, w)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 2
|
|
|
|
|
|
|
|
|
|
if s.get('show_block_weight', False) and data.block_weight > 0:
|
|
|
|
|
text = render_block_weight(
|
|
|
|
|
data.block_weight, data.max_block_weight, w
|
|
|
|
|
)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 2
|
|
|
|
|
|
|
|
|
|
if s.get('show_block_times', True) and data.block_time_history:
|
|
|
|
|
text = render_block_time_histogram(data.block_time_history, w)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 1
|
|
|
|
|
if data.streak_count >= 2:
|
|
|
|
|
text = render_streak(data.streak_type, data.streak_count, w)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 1
|
|
|
|
|
row += 1
|
|
|
|
|
|
|
|
|
|
if s.get('show_peers', False) and data.peer_count > 0:
|
|
|
|
|
text = render_peer_count(data.peer_count, w)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 2
|
|
|
|
|
|
|
|
|
|
if s.get('show_moon', False):
|
|
|
|
|
text = render_moon_phase(w)
|
|
|
|
|
self._write(_move(row) + _clear_line() + text)
|
|
|
|
|
row += 2
|
|
|
|
|
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
if s.get('show_utc_time', False):
|
|
|
|
|
text = render_utc_time(
|
|
|
|
|
[s.get('colorA', 'green'), s.get('colorB', 'yellow')]
|
|
|
|
|
)
|
|
|
|
|
lines = text.rstrip('\n').split('\n')
|
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
|
self._write(_move(row + i) + _clear_line() + line)
|
|
|
|
|
row += len(lines) + 1
|
|
|
|
|
|
|
|
|
|
if s.get('generative_art', False) and data.block_hash:
|
2026-04-02 09:10:53 -03:00
|
|
|
art = hash_art(data.block_hash, min(60, w - 4), 6, w)
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
lines = art.split('\n')
|
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
|
self._write(_move(row + i) + _clear_line() + line)
|
|
|
|
|
row += len(lines) + 1
|
|
|
|
|
|
|
|
|
|
return row
|
|
|
|
|
|
|
|
|
|
def update_countdown(self, data):
|
|
|
|
|
"""Update only the countdown timer (called every poll cycle)."""
|
|
|
|
|
if self._is_zen() or not self.settings.get('show_countdown', True):
|
|
|
|
|
return
|
2026-04-02 09:08:18 -03:00
|
|
|
if self._countdown_row == 0:
|
|
|
|
|
return
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
text = render_countdown(data.seconds_since_block, self.term_width)
|
2026-04-02 09:08:18 -03:00
|
|
|
self._write(_move(self._countdown_row) + _clear_line() + text)
|
Add enhanced block clock with 12 new features for Menu A
New clock/ package replaces the old design() polling loop with a
flicker-free ANSI cursor-positioned renderer. Features include:
countdown timer, epoch/halving progress bar, fee rate indicator,
hashrate sparkline, matrix mining animation, odometer digit transition,
heartbeat pulse, zen mode, UTC time display, fireworks on milestone
blocks, generative hash art, and configurable sound modes.
All features are toggleable via Settings → D (Clock Display Settings).
Also fixes hardcoded config paths in menuSelection(), menuSelectionLN(),
and TUI startup to use the cfg singleton instead.
Co-Authored-By: Claude Opus 4.6 (1M context) <noreply@anthropic.com>
2026-04-02 08:35:59 -03:00
|
|
|
|
|
|
|
|
def heartbeat(self, data):
|
|
|
|
|
"""Toggle bold/dim on block height for breathing effect."""
|
|
|
|
|
if self._is_zen() or not self.settings.get('heartbeat', True):
|
|
|
|
|
return
|
|
|
|
|
if self._last_rendered_height is None:
|
|
|
|
|
return
|
|
|
|
|
|
|
|
|
|
self._heartbeat_step += 1
|
|
|
|
|
output = _render_block_height(data.block_height, self.settings)
|
|
|
|
|
lines = output.rstrip('\n').split('\n')
|
|
|
|
|
|
|
|
|
|
start_row = 2
|
|
|
|
|
|
|
|
|
|
# Apply dim on odd steps
|
|
|
|
|
wrapper = _dim if self._heartbeat_step % 2 == 1 else lambda x: x
|
|
|
|
|
buf = []
|
|
|
|
|
for i, line in enumerate(lines):
|
|
|
|
|
buf.append(_move(start_row + i) + _clear_line() + wrapper(line))
|
|
|
|
|
self._write(''.join(buf))
|
|
|
|
|
|
|
|
|
|
def on_new_block(self, data, animations_mod):
|
|
|
|
|
"""Handle new block arrival: sound, animation, then full re-render."""
|
|
|
|
|
play_sound(self.settings.get('sound', 'bell'))
|
|
|
|
|
|
|
|
|
|
anim = self.settings.get('animation', 'matrix')
|
|
|
|
|
|
|
|
|
|
# Check for milestone fireworks first
|
|
|
|
|
if self.settings.get('fireworks', True):
|
|
|
|
|
milestone = animations_mod.is_milestone_block(data.block_height)
|
|
|
|
|
if milestone:
|
|
|
|
|
animations_mod.fireworks_animation(
|
|
|
|
|
self.term_width, self.term_height, duration=5.0
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
if anim == 'matrix':
|
|
|
|
|
animations_mod.mining_animation(duration=3.0)
|
|
|
|
|
elif anim == 'odometer' and self._last_rendered_height is not None:
|
|
|
|
|
animations_mod.odometer_transition(
|
|
|
|
|
str(self._last_rendered_height),
|
|
|
|
|
str(data.block_height),
|
|
|
|
|
self.settings, 2
|
|
|
|
|
)
|
|
|
|
|
|
|
|
|
|
self.render_full(data)
|
|
|
|
|
|
|
|
|
|
def cleanup(self):
|
|
|
|
|
"""Restore terminal state."""
|
|
|
|
|
self._write(_show_cursor() + "\033[0m")
|