ui(console): center menu in a bordered box with better spacing
Both renderers now draw the menu inside a centered box with: - Rounded corners (modern) / box-drawing (VT220) - Title centered inside the box top - Section separators as horizontal lines within the box - Selected item highlighted across the full box width - Vertical and horizontal centering on screen Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
This commit is contained in:
@@ -167,17 +167,42 @@ class CursesRenderer(BaseRenderer):
|
||||
|
||||
def draw_menu(self, items, selected_index=0, title=''):
|
||||
h, w = self.get_size()
|
||||
start_row = 3
|
||||
|
||||
# Calculate menu dimensions for centering
|
||||
item_count = len(items)
|
||||
# Find widest label for box sizing
|
||||
max_label = 0
|
||||
for num, label in items:
|
||||
if num != "---" and num != "\u2500":
|
||||
max_label = max(max_label, len(f" {num}. {label} "))
|
||||
box_w = max(max_label + 8, 44)
|
||||
box_w = min(box_w, w - 4)
|
||||
box_h = item_count + 4 # top/bottom border + title + blank line
|
||||
if title:
|
||||
self._safe_addstr(start_row, 2, title, self._attr("title"))
|
||||
start_row += 2
|
||||
box_h += 2
|
||||
|
||||
visible = h - start_row - 3 # leave room for footer
|
||||
if visible < 1:
|
||||
return
|
||||
# Center the box vertically and horizontally
|
||||
start_row = max((h - box_h) // 2 - 1, 2)
|
||||
start_col = max((w - box_w) // 2, 1)
|
||||
|
||||
# Scrolling offset
|
||||
# Draw box
|
||||
self.draw_box(start_row, start_col, box_h, box_w, "")
|
||||
|
||||
row = start_row + 1
|
||||
if title:
|
||||
# Title centered inside the box
|
||||
title_col = start_col + max((box_w - len(title)) // 2, 2)
|
||||
self._safe_addstr(row, title_col, title,
|
||||
self._attr("title"))
|
||||
row += 1
|
||||
self._hline(row, start_col + 1, box_w - 2)
|
||||
row += 1
|
||||
|
||||
# Menu items inside the box
|
||||
inner_left = start_col + 3
|
||||
inner_w = box_w - 6
|
||||
|
||||
visible = box_h - (row - start_row) - 1
|
||||
offset = 0
|
||||
if selected_index >= visible:
|
||||
offset = selected_index - visible + 1
|
||||
@@ -188,19 +213,20 @@ class CursesRenderer(BaseRenderer):
|
||||
break
|
||||
if idx < offset:
|
||||
continue
|
||||
row = start_row + drawn
|
||||
|
||||
# Separator
|
||||
if num == "\u2500" or num == "---":
|
||||
self._hline(row, 2, w - 4)
|
||||
self._hline(row, start_col + 1, box_w - 2)
|
||||
row += 1
|
||||
drawn += 1
|
||||
continue
|
||||
|
||||
marker = "\u25b8 " if idx == selected_index else " "
|
||||
text = f"{marker}{num}. {label}"
|
||||
style = "highlight" if idx == selected_index else "normal"
|
||||
self._safe_addstr(row, 2, pad_right(text, w - 4),
|
||||
self._safe_addstr(row, inner_left, pad_right(text, inner_w),
|
||||
self._attr(style))
|
||||
row += 1
|
||||
drawn += 1
|
||||
|
||||
def draw_table(self, headers, rows, widths, page_info=None,
|
||||
|
||||
@@ -258,46 +258,94 @@ class TextualRenderer(BaseRenderer):
|
||||
|
||||
def draw_menu(self, items, selected_index=0, title=''):
|
||||
h, w = self.get_size()
|
||||
visible_lines = h - 6
|
||||
|
||||
# Calculate menu box dimensions
|
||||
max_label = 0
|
||||
for num, label in items:
|
||||
if num != "---" and num != "\u2500":
|
||||
max_label = max(max_label, len(f" {num}. {label} "))
|
||||
box_inner = max(max_label + 4, 40)
|
||||
box_inner = min(box_inner, w - 8)
|
||||
|
||||
# Vertical centering: blank lines before menu
|
||||
item_count = len(items)
|
||||
menu_height = item_count + (3 if title else 0)
|
||||
top_pad = max((h - menu_height - 6) // 2 - 1, 0)
|
||||
for _ in range(top_pad):
|
||||
self._add_line(Text(""))
|
||||
|
||||
# Horizontal padding
|
||||
pad_left = max((w - box_inner - 4) // 2, 2)
|
||||
pad_str = " " * pad_left
|
||||
|
||||
# Box top border
|
||||
self._add_line(Text(
|
||||
f"{pad_str}\u256d{'─' * (box_inner + 2)}\u256e",
|
||||
style="blue",
|
||||
))
|
||||
|
||||
if title:
|
||||
self._add_line(Text(f" {title}", style="bold white"))
|
||||
self._add_line(Text(""))
|
||||
visible_lines -= 2
|
||||
# Title centered inside box
|
||||
title_pad = max((box_inner - len(title)) // 2, 0)
|
||||
line = Text()
|
||||
line.append(f"{pad_str}\u2502 ", style="blue")
|
||||
line.append(" " * title_pad, style="")
|
||||
line.append(title, style="bold white")
|
||||
line.append(
|
||||
" " * (box_inner - title_pad - len(title)),
|
||||
style="",
|
||||
)
|
||||
line.append(" \u2502", style="blue")
|
||||
self._add_line(line)
|
||||
# Separator inside box
|
||||
self._add_line(Text(
|
||||
f"{pad_str}\u251c{'─' * (box_inner + 2)}\u2524",
|
||||
style="blue",
|
||||
))
|
||||
|
||||
if visible_lines < 1:
|
||||
return
|
||||
visible = h - self._line_count - 4
|
||||
if visible < 1:
|
||||
visible = item_count
|
||||
|
||||
offset = 0
|
||||
if selected_index >= visible_lines:
|
||||
offset = selected_index - visible_lines + 1
|
||||
if selected_index >= visible:
|
||||
offset = selected_index - visible + 1
|
||||
|
||||
drawn = 0
|
||||
for idx, (num, label) in enumerate(items):
|
||||
if drawn >= visible_lines:
|
||||
if drawn >= visible:
|
||||
break
|
||||
if idx < offset:
|
||||
continue
|
||||
|
||||
if num == "\u2500" or num == "---":
|
||||
self._add_line(
|
||||
Text(" " + "─" * (w - 4), style="dim blue")
|
||||
)
|
||||
self._add_line(Text(
|
||||
f"{pad_str}\u251c{'─' * (box_inner + 2)}\u2524",
|
||||
style="dim blue",
|
||||
))
|
||||
drawn += 1
|
||||
continue
|
||||
|
||||
marker = "\u25b8 " if idx == selected_index else " "
|
||||
entry = f"{marker}{num}. {label}"
|
||||
entry = pad_right(entry, box_inner)
|
||||
|
||||
line = Text()
|
||||
line.append(f"{pad_str}\u2502 ", style="blue")
|
||||
if idx == selected_index:
|
||||
entry = pad_right(f"{marker}{num}. {label}", w - 4)
|
||||
self._add_line(
|
||||
Text(f" {entry}", style="bold white on rgb(30,60,120)")
|
||||
)
|
||||
line.append(f" {entry}", style="bold white on rgb(30,60,120)")
|
||||
else:
|
||||
self._add_line(
|
||||
Text(f" {marker}{num}. {label}", style="white")
|
||||
)
|
||||
line.append(f" {entry}", style="white")
|
||||
line.append(" \u2502", style="blue")
|
||||
self._add_line(line)
|
||||
drawn += 1
|
||||
|
||||
# Box bottom border
|
||||
self._add_line(Text(
|
||||
f"{pad_str}\u2570{'─' * (box_inner + 2)}\u256f",
|
||||
style="blue",
|
||||
))
|
||||
|
||||
def draw_table(self, headers, rows, widths, page_info=None,
|
||||
selected_row=-1):
|
||||
h, w = self.get_size()
|
||||
|
||||
Reference in New Issue
Block a user