refactor(gui_2): merge command_palette; split registry->commands + render->gui_2; git rm src/command_palette.py

Per spec FR1 + Phase 1.3 + architecture feedback: src/command_palette.py
split by responsibility:
  - Command/ScoredCommand/CommandRegistry/fuzzy_match/_close_palette/_execute (data/ops)
    -> src/commands.py (which already owns _LazyCommandRegistry pattern)
  - render_palette_modal (view/ImGui) -> src/gui_2.py

GUI is a pure view; the registry/data classes are ops; commands.py owns
the registry because commands.py is where @registry.register decorators live.
gui_2.render_palette_modal imports Command from commands.py to type its
parameters.

Also fixes Phase 1.1 (bg_shader) per architecture feedback:
BackgroundShader no longer owns 'enabled' state - the GUI is pure view.
State is now owned by AppController.bg_shader_enabled (read on load from
config, written from gui_2 checkbox via app's __setattr__ delegation).

Tests:
- tests/test_command_palette.py: imports from src.commands (was src.command_palette)
- tests/test_commands_no_top_level_command_palette.py: rewritten for the
  new architecture (eager registry in commands.py; render in gui_2; no
  circular import between commands.py and gui_2)
This commit is contained in:
ed
2026-06-26 06:54:59 -04:00
parent be5607dee8
commit 3dd153f718
6 changed files with 259 additions and 327 deletions
+69 -12
View File
@@ -24,7 +24,8 @@ if _thirdparty not in sys.path:
from contextlib import ExitStack, nullcontext
from pathlib import Path
from typing import Optional, Any
from typing import Optional, Any, Callable, Dict, List
from dataclasses import dataclass, field
from imgui_bundle import imgui, hello_imgui, immapp, imgui_node_editor as ed, imgui_color_text_edit as ced
# Lazy proxies (startup_speedup_20260606 Phase 5D)
@@ -1093,9 +1094,9 @@ class App:
pushed_prior_tint = False
# Render background shader
bg = get_bg()
ws = imgui.get_io().display_size
if bg.enabled: bg.render(ws.x, ws.y)
if getattr(self, 'bg_shader_enabled', False):
ws = imgui.get_io().display_size
get_bg().render(ws.x, ws.y)
theme.render_post_fx(ws.x, ws.y, self.ai_status, self.ui_crt_filter)
@@ -6265,13 +6266,14 @@ def render_theme_panel(app: App) -> None:
ch_ct, ctrans = imgui.slider_float("##ctrans", theme.get_child_transparency(), 0.1, 1.0, "%.2f")
if ch_ct:
theme.set_child_transparency(ctrans)
bg = get_bg()
ch_bg, bg.enabled = imgui.checkbox("Animated Background Shader", bg.enabled)
if ch_bg:
bg_enabled = getattr(self, 'bg_shader_enabled', False)
ch_bg, new_bg = imgui.checkbox("Animated Background Shader", bg_enabled)
if ch_bg and new_bg != bg_enabled:
self.bg_shader_enabled = new_bg
gui_cfg = app.config.setdefault("gui", {})
gui_cfg["bg_shader_enabled"] = bg.enabled
app._flush_to_config()
app.save_config()
gui_cfg["bg_shader_enabled"] = new_bg
if hasattr(app, "_flush_to_config"): app._flush_to_config()
if hasattr(app, "save_config"): app.save_config()
ch_crt, app.ui_crt_filter = imgui.checkbox("CRT Filter", app.ui_crt_filter)
if ch_crt:
@@ -8443,12 +8445,11 @@ _bg: _Optional["BackgroundShader"] = None
class BackgroundShader:
def __init__(self):
self.enabled = False
self.start_time = _bg_time.time()
self.ctx: _Optional[_Any] = None
def render(self, width: float, height: float):
if not self.enabled or width <= 0 or height <= 0:
if width <= 0 or height <= 0:
return
t = _bg_time.time() - self.start_time
dl = imgui.get_background_draw_list()
@@ -8506,3 +8507,59 @@ def draw_soft_shadow(draw_list: imgui.ImDrawList, p_min: imgui.ImVec2, p_max: im
thickness=1.0
)
#endregion: Shaders
#region: Command Palette Modal (rendering only; registry lives in src/commands.py)
from src.commands import Command as _CpCommand, fuzzy_match as _cp_fuzzy_match, _close_palette, _execute as _cp_execute
def render_palette_modal(app: Any, commands: List[Any]) -> None:
if not getattr(app, "show_command_palette", False):
return
viewport = imgui.get_main_viewport()
center = viewport.get_center()
imgui.set_next_window_pos((center.x - 300, center.y - 200), imgui.Cond_.always)
imgui.set_next_window_size((600, 400), imgui.Cond_.always)
if not hasattr(app, "_command_palette_query"): app._command_palette_query = ""
if not hasattr(app, "_command_palette_selected"): app._command_palette_selected = 0
if not hasattr(app, "_command_palette_focused"): app._command_palette_focused = False
if not app._command_palette_focused:
imgui.set_next_window_focus()
app._command_palette_focused = True
if imgui.is_key_pressed(imgui.Key.escape):
_close_palette(app)
return
expanded, opened = imgui.begin("Command Palette##manual_slop", True, imgui.WindowFlags_.no_collapse)
if not expanded or not opened:
app.show_command_palette = False
app._command_palette_focused = False
imgui.end()
return
if not getattr(app, '_command_palette_input_focused', False):
imgui.set_keyboard_focus_here()
app._command_palette_input_focused = True
results = _cp_fuzzy_match(app._command_palette_query, commands, top_n=20)
if results: app._command_palette_selected = max(0, min(app._command_palette_selected, len(results) - 1))
else: app._command_palette_selected = 0
if imgui.is_key_pressed(imgui.Key.down_arrow):
if results:
app._command_palette_selected = min(app._command_palette_selected + 1, len(results) - 1)
if imgui.is_key_pressed(imgui.Key.up_arrow):
if results:
app._command_palette_selected = max(app._command_palette_selected - 1, 0)
if imgui.is_key_pressed(imgui.Key.enter) or imgui.is_key_pressed(imgui.Key.keypad_enter):
if results and 0 <= app._command_palette_selected < len(results):
_cp_execute(app, results[app._command_palette_selected].command)
imgui.set_next_item_width(-1)
_, app._command_palette_query = imgui.input_text("##query", app._command_palette_query)
if imgui.begin_child("##results", (0, -1)):
for i, scored in enumerate(results):
is_selected = (i == app._command_palette_selected)
label = f"[{scored.command.category}] {scored.command.title}"
clicked, _ = imgui.selectable(label, is_selected)
if clicked:
app._command_palette_selected = i
_cp_execute(app, scored.command)
if not results:
imgui.text_disabled("No matching commands.")
imgui.end_child()
imgui.end()
#endregion: Command Palette Modal