Add 3D-style buttons with box-drawing characters
Create DOS/BIOS-style 3D button effects using Unicode characters: Button3D (multi-line): ┌──────────┐ │ Label │▄ └──────────┘█ Button3DCompact (single-line): ▐ Label ▌▄ Changes: - Add Button3D class for large standalone buttons - Add Button3DCompact class for inline/dialog buttons - Update palette with shadow colors (shadow_edge, button_highlight, button_shadow) - Replace all ClickableButton/urwid.Button with Button3DCompact throughout: - Main menu action buttons (Create, Edit, Delete, Test, Providers, EXIT) - Dialog OK/Cancel buttons - Load buttons in code/prompt dialogs - Provider dropdown buttons - Auto-adjust button 🤖 Generated with [Claude Code](https://claude.com/claude-code) Co-Authored-By: Claude <noreply@anthropic.com>
This commit is contained in:
parent
b8c85df398
commit
aef5c0f99f
|
|
@ -10,22 +10,33 @@ from .tool import (
|
||||||
from .providers import Provider, load_providers, add_provider, delete_provider, get_provider
|
from .providers import Provider, load_providers, add_provider, delete_provider, get_provider
|
||||||
|
|
||||||
|
|
||||||
# Color palette - BIOS style
|
# Color palette - BIOS style with 3D button effects
|
||||||
PALETTE = [
|
PALETTE = [
|
||||||
('body', 'white', 'dark blue'),
|
('body', 'white', 'dark blue'),
|
||||||
('header', 'white', 'dark red', 'bold'),
|
('header', 'white', 'dark red', 'bold'),
|
||||||
('footer', 'black', 'light gray'),
|
('footer', 'black', 'light gray'),
|
||||||
|
# Button colors - raised 3D effect
|
||||||
('button', 'black', 'light gray'),
|
('button', 'black', 'light gray'),
|
||||||
('button_focus', 'white', 'dark red', 'bold'),
|
('button_focus', 'white', 'dark red', 'bold'),
|
||||||
|
('button_highlight', 'white', 'light gray'), # Top/left edge (light)
|
||||||
|
('button_shadow', 'dark gray', 'light gray'), # Bottom/right edge (dark)
|
||||||
|
('button_pressed', 'black', 'dark gray'), # Pressed state
|
||||||
|
# Edit fields
|
||||||
('edit', 'black', 'light gray'),
|
('edit', 'black', 'light gray'),
|
||||||
('edit_focus', 'black', 'yellow'),
|
('edit_focus', 'black', 'yellow'),
|
||||||
|
# List items
|
||||||
('listbox', 'black', 'light gray'),
|
('listbox', 'black', 'light gray'),
|
||||||
('listbox_focus', 'white', 'dark red'),
|
('listbox_focus', 'white', 'dark red'),
|
||||||
|
# Dialog
|
||||||
('dialog', 'black', 'light gray'),
|
('dialog', 'black', 'light gray'),
|
||||||
('dialog_border', 'white', 'dark blue'),
|
('dialog_border', 'white', 'dark blue'),
|
||||||
|
# Text styles
|
||||||
('label', 'yellow', 'dark blue', 'bold'),
|
('label', 'yellow', 'dark blue', 'bold'),
|
||||||
('error', 'white', 'dark red', 'bold'),
|
('error', 'white', 'dark red', 'bold'),
|
||||||
('success', 'light green', 'dark blue', 'bold'),
|
('success', 'light green', 'dark blue', 'bold'),
|
||||||
|
# 3D shadow elements
|
||||||
|
('shadow', 'black', 'black'),
|
||||||
|
('shadow_edge', 'dark gray', 'dark blue'),
|
||||||
]
|
]
|
||||||
|
|
||||||
|
|
||||||
|
|
@ -55,8 +66,149 @@ class SelectableText(urwid.WidgetWrap):
|
||||||
return False
|
return False
|
||||||
|
|
||||||
|
|
||||||
|
class Button3D(urwid.WidgetWrap):
|
||||||
|
"""A 3D-style button using box-drawing characters for depth.
|
||||||
|
|
||||||
|
Creates a raised button effect like DOS/BIOS interfaces:
|
||||||
|
┌──────────┐
|
||||||
|
│ Label │▄
|
||||||
|
└──────────┘█
|
||||||
|
|
||||||
|
When focused, colors change to show selection.
|
||||||
|
"""
|
||||||
|
|
||||||
|
signals = ['click']
|
||||||
|
|
||||||
|
def __init__(self, label, on_press=None, user_data=None):
|
||||||
|
self.label = label
|
||||||
|
self.on_press = on_press
|
||||||
|
self.user_data = user_data
|
||||||
|
self._pressed = False
|
||||||
|
|
||||||
|
# Build the 3D button structure
|
||||||
|
self._build_widget()
|
||||||
|
super().__init__(self._widget)
|
||||||
|
|
||||||
|
def _build_widget(self):
|
||||||
|
"""Build the 3D button widget structure."""
|
||||||
|
label = self.label
|
||||||
|
width = len(label) + 4 # Padding inside button
|
||||||
|
|
||||||
|
# Button face with border
|
||||||
|
# Top edge: ┌────┐
|
||||||
|
top = '┌' + '─' * (width - 2) + '┐'
|
||||||
|
# Middle: │ Label │ with shadow
|
||||||
|
middle_text = '│ ' + label + ' │'
|
||||||
|
# Bottom edge: └────┘ with shadow
|
||||||
|
bottom = '└' + '─' * (width - 2) + '┘'
|
||||||
|
|
||||||
|
# Shadow characters (right and bottom)
|
||||||
|
shadow_right = '▄'
|
||||||
|
shadow_bottom = '█'
|
||||||
|
|
||||||
|
# Create the rows
|
||||||
|
top_row = urwid.Text(top + ' ') # Space for shadow alignment
|
||||||
|
middle_row = urwid.Columns([
|
||||||
|
('pack', urwid.Text(middle_text)),
|
||||||
|
('pack', urwid.Text(('shadow_edge', shadow_right))),
|
||||||
|
])
|
||||||
|
bottom_row = urwid.Columns([
|
||||||
|
('pack', urwid.Text(bottom)),
|
||||||
|
('pack', urwid.Text(('shadow_edge', shadow_right))),
|
||||||
|
])
|
||||||
|
shadow_row = urwid.Text(('shadow_edge', ' ' + shadow_bottom * (width - 1)))
|
||||||
|
|
||||||
|
# Stack them
|
||||||
|
pile = urwid.Pile([
|
||||||
|
top_row,
|
||||||
|
middle_row,
|
||||||
|
bottom_row,
|
||||||
|
shadow_row,
|
||||||
|
])
|
||||||
|
|
||||||
|
self._widget = urwid.AttrMap(pile, 'button', 'button_focus')
|
||||||
|
|
||||||
|
def selectable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def keypress(self, size, key):
|
||||||
|
if key == 'enter':
|
||||||
|
self._activate()
|
||||||
|
return None
|
||||||
|
return key
|
||||||
|
|
||||||
|
def mouse_event(self, size, event, button, col, row, focus):
|
||||||
|
if button == 1:
|
||||||
|
if event == 'mouse press':
|
||||||
|
self._pressed = True
|
||||||
|
return True
|
||||||
|
elif event == 'mouse release' and self._pressed:
|
||||||
|
self._pressed = False
|
||||||
|
self._activate()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _activate(self):
|
||||||
|
"""Trigger the button callback."""
|
||||||
|
if self.on_press:
|
||||||
|
self.on_press(self.user_data)
|
||||||
|
self._emit('click')
|
||||||
|
|
||||||
|
|
||||||
|
class Button3DCompact(urwid.WidgetWrap):
|
||||||
|
"""A compact 3D button that fits on a single line with shadow effect.
|
||||||
|
|
||||||
|
Creates a subtle 3D effect: [ Label ]▌
|
||||||
|
|
||||||
|
Better for inline use where vertical space is limited.
|
||||||
|
"""
|
||||||
|
|
||||||
|
signals = ['click']
|
||||||
|
|
||||||
|
def __init__(self, label, on_press=None, user_data=None):
|
||||||
|
self.label = label
|
||||||
|
self.on_press = on_press
|
||||||
|
self.user_data = user_data
|
||||||
|
|
||||||
|
# Build compact button: ▐ Label ▌ with shadow
|
||||||
|
# Using block characters for edges
|
||||||
|
button_text = urwid.Text([
|
||||||
|
('button_highlight', '▐'),
|
||||||
|
('button', f' {label} '),
|
||||||
|
('button_shadow', '▌'),
|
||||||
|
('shadow_edge', '▄'),
|
||||||
|
])
|
||||||
|
|
||||||
|
self._widget = urwid.AttrMap(button_text, None, {
|
||||||
|
'button': 'button_focus',
|
||||||
|
'button_highlight': 'button_focus',
|
||||||
|
'button_shadow': 'button_focus',
|
||||||
|
})
|
||||||
|
super().__init__(self._widget)
|
||||||
|
|
||||||
|
def selectable(self):
|
||||||
|
return True
|
||||||
|
|
||||||
|
def keypress(self, size, key):
|
||||||
|
if key == 'enter':
|
||||||
|
self._activate()
|
||||||
|
return None
|
||||||
|
return key
|
||||||
|
|
||||||
|
def mouse_event(self, size, event, button, col, row, focus):
|
||||||
|
if button == 1 and event == 'mouse release':
|
||||||
|
self._activate()
|
||||||
|
return True
|
||||||
|
return False
|
||||||
|
|
||||||
|
def _activate(self):
|
||||||
|
if self.on_press:
|
||||||
|
self.on_press(self.user_data)
|
||||||
|
self._emit('click')
|
||||||
|
|
||||||
|
|
||||||
class ClickableButton(urwid.WidgetWrap):
|
class ClickableButton(urwid.WidgetWrap):
|
||||||
"""A button that responds to mouse clicks."""
|
"""A button that responds to mouse clicks (legacy wrapper)."""
|
||||||
|
|
||||||
def __init__(self, label, on_press=None, user_data=None):
|
def __init__(self, label, on_press=None, user_data=None):
|
||||||
self.on_press = on_press
|
self.on_press = on_press
|
||||||
|
|
@ -466,16 +618,16 @@ class ToolBuilderLayout(urwid.WidgetWrap):
|
||||||
|
|
||||||
|
|
||||||
class Dialog(urwid.WidgetWrap):
|
class Dialog(urwid.WidgetWrap):
|
||||||
"""A dialog box overlay."""
|
"""A dialog box overlay with 3D-style buttons."""
|
||||||
|
|
||||||
def __init__(self, title, body, buttons, width=60, height=None):
|
def __init__(self, title, body, buttons, width=60, height=None):
|
||||||
# Title
|
# Title
|
||||||
title_widget = urwid.Text(('header', f' {title} '), align='center')
|
title_widget = urwid.Text(('header', f' {title} '), align='center')
|
||||||
|
|
||||||
# Buttons row
|
# Buttons row - use 3D compact buttons for dialog actions
|
||||||
button_widgets = []
|
button_widgets = []
|
||||||
for label, callback in buttons:
|
for label, callback in buttons:
|
||||||
btn = ClickableButton(label, callback)
|
btn = Button3DCompact(label, callback)
|
||||||
button_widgets.append(btn)
|
button_widgets.append(btn)
|
||||||
buttons_row = urwid.Columns([('pack', b) for b in button_widgets], dividechars=2)
|
buttons_row = urwid.Columns([('pack', b) for b in button_widgets], dividechars=2)
|
||||||
buttons_centered = urwid.Padding(buttons_row, align='center', width='pack')
|
buttons_centered = urwid.Padding(buttons_row, align='center', width='pack')
|
||||||
|
|
@ -647,22 +799,22 @@ class SmartToolsUI:
|
||||||
tool_listbox = ToolListBox(self._tool_walker, on_focus_change=self._on_tool_focus)
|
tool_listbox = ToolListBox(self._tool_walker, on_focus_change=self._on_tool_focus)
|
||||||
tool_box = urwid.LineBox(tool_listbox, title='Tools')
|
tool_box = urwid.LineBox(tool_listbox, title='Tools')
|
||||||
|
|
||||||
# Action buttons - Tab navigates here from tool list
|
# Action buttons - Tab navigates here from tool list (3D style)
|
||||||
create_btn = ClickableButton("Create", lambda _: self._create_tool_before_selected())
|
create_btn = Button3DCompact("Create", lambda _: self._create_tool_before_selected())
|
||||||
edit_btn = ClickableButton("Edit", lambda _: self._edit_selected_tool())
|
edit_btn = Button3DCompact("Edit", lambda _: self._edit_selected_tool())
|
||||||
delete_btn = ClickableButton("Delete", lambda _: self._delete_selected_tool())
|
delete_btn = Button3DCompact("Delete", lambda _: self._delete_selected_tool())
|
||||||
test_btn = ClickableButton("Test", lambda _: self._test_selected_tool())
|
test_btn = Button3DCompact("Test", lambda _: self._test_selected_tool())
|
||||||
providers_btn = ClickableButton("Providers", lambda _: self.manage_providers())
|
providers_btn = Button3DCompact("Providers", lambda _: self.manage_providers())
|
||||||
|
|
||||||
buttons_row = urwid.Columns([
|
buttons_row = urwid.Columns([
|
||||||
('pack', create_btn),
|
('pack', create_btn),
|
||||||
('pack', urwid.Text(" ")),
|
('pack', urwid.Text(" ")),
|
||||||
('pack', edit_btn),
|
('pack', edit_btn),
|
||||||
('pack', urwid.Text(" ")),
|
('pack', urwid.Text(" ")),
|
||||||
('pack', delete_btn),
|
('pack', delete_btn),
|
||||||
('pack', urwid.Text(" ")),
|
('pack', urwid.Text(" ")),
|
||||||
('pack', test_btn),
|
('pack', test_btn),
|
||||||
('pack', urwid.Text(" ")),
|
('pack', urwid.Text(" ")),
|
||||||
('pack', providers_btn),
|
('pack', providers_btn),
|
||||||
])
|
])
|
||||||
buttons_padded = urwid.Padding(buttons_row, align='left', left=1)
|
buttons_padded = urwid.Padding(buttons_row, align='left', left=1)
|
||||||
|
|
@ -687,9 +839,9 @@ class SmartToolsUI:
|
||||||
info_filler = urwid.Filler(info_content, valign='top')
|
info_filler = urwid.Filler(info_content, valign='top')
|
||||||
info_box = urwid.LineBox(info_filler, title='Tool Info')
|
info_box = urwid.LineBox(info_filler, title='Tool Info')
|
||||||
|
|
||||||
# Exit button at bottom
|
# Exit button at bottom (3D style)
|
||||||
exit_btn = ClickableButton("EXIT", lambda _: self.exit_app())
|
exit_btn = Button3DCompact("EXIT", lambda _: self.exit_app())
|
||||||
exit_centered = urwid.Padding(exit_btn, align='center', width=10)
|
exit_centered = urwid.Padding(exit_btn, align='center', width=12)
|
||||||
|
|
||||||
# Use a custom Pile that handles Tab to cycle between tool list and buttons
|
# Use a custom Pile that handles Tab to cycle between tool list and buttons
|
||||||
self._main_pile = TabCyclePile([
|
self._main_pile = TabCyclePile([
|
||||||
|
|
@ -1267,10 +1419,7 @@ class SmartToolsUI:
|
||||||
popup = Dialog("Select Provider", body, [])
|
popup = Dialog("Select Provider", body, [])
|
||||||
self.show_overlay(popup, width=50, height=min(len(provider_names) + 6, 16))
|
self.show_overlay(popup, width=50, height=min(len(provider_names) + 6, 16))
|
||||||
|
|
||||||
provider_select_btn = urwid.AttrMap(
|
provider_select_btn = Button3DCompact("▼", on_press=show_provider_dropdown)
|
||||||
urwid.Button("Select", on_press=show_provider_dropdown),
|
|
||||||
'button', 'button_focus'
|
|
||||||
)
|
|
||||||
|
|
||||||
# File input for external prompt
|
# File input for external prompt
|
||||||
default_file = existing.prompt_file if existing and existing.prompt_file else "prompt.txt"
|
default_file = existing.prompt_file if existing and existing.prompt_file else "prompt.txt"
|
||||||
|
|
@ -1357,7 +1506,7 @@ class SmartToolsUI:
|
||||||
def on_cancel(_):
|
def on_cancel(_):
|
||||||
self.close_overlay()
|
self.close_overlay()
|
||||||
|
|
||||||
load_btn = urwid.AttrMap(urwid.Button("Load", on_load), 'button', 'button_focus')
|
load_btn = Button3DCompact("Load", on_load)
|
||||||
|
|
||||||
# Prompt editor in a box - use ListBox for proper focus handling and scrolling
|
# Prompt editor in a box - use ListBox for proper focus handling and scrolling
|
||||||
# Wrap in DOSScrollBar for DOS-style scrollbar with arrow buttons
|
# Wrap in DOSScrollBar for DOS-style scrollbar with arrow buttons
|
||||||
|
|
@ -1481,10 +1630,7 @@ class SmartToolsUI:
|
||||||
popup = Dialog("Select Provider", popup_body, [])
|
popup = Dialog("Select Provider", popup_body, [])
|
||||||
self.show_overlay(popup, width=50, height=min(len(provider_names) + 6, 16))
|
self.show_overlay(popup, width=50, height=min(len(provider_names) + 6, 16))
|
||||||
|
|
||||||
ai_provider_select_btn = urwid.AttrMap(
|
ai_provider_select_btn = Button3DCompact("▼", on_press=show_ai_provider_dropdown)
|
||||||
urwid.Button("▼", on_press=show_ai_provider_dropdown),
|
|
||||||
'button', 'button_focus'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Default prompt template for AI code generation/adjustment
|
# Default prompt template for AI code generation/adjustment
|
||||||
default_ai_prompt = f"""Modify or generate Python code according to my instruction below.
|
default_ai_prompt = f"""Modify or generate Python code according to my instruction below.
|
||||||
|
|
@ -1547,10 +1693,7 @@ Return ONLY the Python code, no explanations or markdown fencing."""
|
||||||
error_msg = result.error or "Unknown error"
|
error_msg = result.error or "Unknown error"
|
||||||
ai_output_text.set_text(('error', f"✗ Error from {provider_name}:\n\n{error_msg}"))
|
ai_output_text.set_text(('error', f"✗ Error from {provider_name}:\n\n{error_msg}"))
|
||||||
|
|
||||||
auto_adjust_btn = urwid.AttrMap(
|
auto_adjust_btn = Button3DCompact("Auto-adjust", on_auto_adjust)
|
||||||
urwid.Button("Auto-adjust", on_auto_adjust),
|
|
||||||
'button', 'button_focus'
|
|
||||||
)
|
|
||||||
|
|
||||||
# Build the AI assist box with provider selector, prompt editor, output area, and button
|
# Build the AI assist box with provider selector, prompt editor, output area, and button
|
||||||
ai_provider_row = urwid.Columns([
|
ai_provider_row = urwid.Columns([
|
||||||
|
|
@ -1644,7 +1787,7 @@ Return ONLY the Python code, no explanations or markdown fencing."""
|
||||||
def on_cancel(_):
|
def on_cancel(_):
|
||||||
self.close_overlay()
|
self.close_overlay()
|
||||||
|
|
||||||
load_btn = urwid.AttrMap(urwid.Button("Load", on_load), 'button', 'button_focus')
|
load_btn = Button3DCompact("Load", on_load)
|
||||||
|
|
||||||
# Code editor in a box - use ListBox for proper focus handling and scrolling
|
# Code editor in a box - use ListBox for proper focus handling and scrolling
|
||||||
# Wrap in DOSScrollBar for DOS-style scrollbar with arrow buttons
|
# Wrap in DOSScrollBar for DOS-style scrollbar with arrow buttons
|
||||||
|
|
|
||||||
Loading…
Reference in New Issue