"""Toolbar, dialog, and menu interaction handlers."""
import logging
from simvx.core import Node
from simvx.core.scripted_demo import DemoRunner
from .._helpers import (
_drive_click,
_drive_type_chars,
_get_editor,
_init_click,
_resolve_root_type,
_walk_children,
)
from ..steps import (
ClickAddButton,
ClickBottomTab,
ClickInspectorButton,
ClickMenu,
ClickToolbar,
DialogSelect,
NewSceneSelect,
)
log = logging.getLogger(__name__)
def _handle_click_add_button(runner: DemoRunner, step: ClickAddButton, dt: float):
runner._action_desc = "Click: + (Add Node)"
editor = _get_editor(runner)
if step._phase == 0:
if not editor or not editor.scene_tree_panel:
runner._advance()
return
btn = editor.scene_tree_panel._add_btn
gx, gy, gw, gh = btn.get_global_rect()
_init_click(step, runner, (gx + gw / 2, gy + gh / 2))
step._phase = 1
elif step._phase == 1:
if _drive_click(runner, step, dt):
runner._advance()
def _handle_dialog_select(runner: DemoRunner, step: DialogSelect, dt: float):
runner._action_desc = f"Select: {step.type_name}"
editor = _get_editor(runner)
if step._phase == 0:
# Type filter chars into the dialog's focused filter field
step._mc_t = getattr(step, "_mc_t", 0.0)
if _drive_type_chars(runner, step, dt, step.type_name):
step._phase = 1
step._mc_t = 0.0
elif step._phase == 1:
# Wait for filter to apply
step._mc_t += dt
if step._mc_t >= 0.05:
if not editor or not editor.scene_tree_panel:
runner._advance()
return
dialog = editor.scene_tree_panel._add_dialog
if not dialog.visible:
runner._advance()
return
gx, gy, _, _ = dialog.get_global_rect()
row_y = dialog.type_row_y(step.type_name)
if row_y is not None:
_init_click(step, runner, (gx + dialog.DIALOG_WIDTH / 2, row_y + dialog.ROW_HEIGHT / 2))
step._phase = 2
return
runner._advance() # type not found in filtered list
elif step._phase == 2:
if _drive_click(runner, step, dt):
runner._advance()
def _handle_click_bottom_tab(runner: DemoRunner, step: ClickBottomTab, dt: float):
"""Click a tab in the editor's bottom TabContainer by name."""
runner._action_desc = f"Click bottom tab: {step.name}"
editor = _get_editor(runner)
if step._phase == 0:
tabs = getattr(editor, "_bottom_tabs", None) if editor else None
if not tabs:
raise ValueError("ClickBottomTab: editor has no _bottom_tabs container")
from simvx.core import Control
tab_controls = [c for c in tabs.children if isinstance(c, Control)]
idx = next((i for i, c in enumerate(tab_controls) if c.name == step.name), -1)
if idx < 0:
available = ", ".join(c.name for c in tab_controls) or "<empty>"
raise ValueError(
f"ClickBottomTab: no tab named {step.name!r}. Available: {available}"
)
gx, gy, gw, _gh = tabs.get_global_rect()
plus_w = 28.0 if tabs.show_new_tab_button else 0.0
usable_w = gw - plus_w
tab_width = usable_w / max(len(tab_controls), 1)
cx = gx + idx * tab_width + tab_width / 2
cy = gy + tabs.tab_height / 2
_init_click(step, runner, (cx, cy))
step._phase = 1
elif step._phase == 1:
if _drive_click(runner, step, dt):
runner._advance()
def _handle_click_toolbar(runner: DemoRunner, step: ClickToolbar, dt: float):
runner._action_desc = f"Click: {step.name}"
editor = _get_editor(runner)
if step._phase == 0:
if not editor:
runner._advance()
return
play_controls = getattr(editor, "_play_controls", None)
btn_map = {
"Play": getattr(play_controls, "play_btn", None),
"Stop": getattr(play_controls, "stop_btn", None),
"Pause": getattr(play_controls, "pause_btn", None),
"3D": getattr(editor, "_btn_3d", None),
"2D": getattr(editor, "_btn_2d", None),
"Code": getattr(editor, "_btn_code", None),
}
btn = btn_map.get(step.name)
if not btn:
runner._advance()
return
gx, gy, gw, gh = btn.get_global_rect()
_init_click(step, runner, (gx + gw / 2, gy + gh / 2))
step._phase = 1
elif step._phase == 1:
if _drive_click(runner, step, dt):
runner._advance()
def _handle_click_inspector_button(runner: DemoRunner, step: ClickInspectorButton, dt: float):
runner._action_desc = f"Click: {step.label}"
editor = _get_editor(runner)
if step._phase == 0:
if not editor or not editor.inspector_panel:
raise ValueError("ClickInspectorButton: no inspector panel")
from simvx.core import Button as _Button
btn = None
for child in _walk_children(editor.inspector_panel):
if isinstance(child, _Button) and child.text == step.label:
btn = child
break
if not btn:
raise ValueError(f"ClickInspectorButton: button '{step.label}' not found in inspector")
gx, gy, gw, gh = btn.get_global_rect()
_init_click(step, runner, (gx + gw / 2, gy + gh / 2))
step._phase = 1
elif step._phase == 1:
if _drive_click(runner, step, dt):
runner._advance()
def _handle_click_menu(runner: DemoRunner, step: ClickMenu, dt: float):
runner._action_desc = f"Menu: {step.menu_name} > {step.item_text}"
editor = _get_editor(runner)
if step._phase == 0:
# Find menu label rect on MenuBar
if not editor or not editor._menu_bar:
runner._advance()
return
bar = editor._menu_bar
rects = bar._menu_rects()
for i, (name, _popup) in enumerate(bar.menus):
if name == step.menu_name:
lx, ly, lw, lh = rects[i]
_init_click(step, runner, (lx + lw / 2, ly + lh / 2))
step._phase = 1
return
runner._advance() # menu not found
elif step._phase == 1:
# Click to open menu
if _drive_click(runner, step, dt):
step._phase = 2
step._mc_t = 0.0
elif step._phase == 2:
# Find item in the open popup
step._mc_t += dt
if step._mc_t < 0.05:
return # brief wait for popup
editor = _get_editor(runner)
if not editor or not editor._menu_bar:
runner._advance()
return
bar = editor._menu_bar
if bar._open_index < 0:
runner._advance()
return
_name, popup = bar.menus[bar._open_index]
gx, gy, _gw, _gh = popup.get_global_rect()
for i, item in enumerate(popup.items):
if item.text == step.item_text:
row_y = gy + i * 28 + 14 # approximate row center
_init_click(step, runner, (gx + popup.size.x / 2, row_y))
step._phase = 3
return
# Item not found, close menu and advance
bar._close_all()
runner._advance()
elif step._phase == 3:
# Click the menu item
if _drive_click(runner, step, dt):
runner._advance()
def _handle_new_scene_select(runner: DemoRunner, step: NewSceneSelect, dt: float):
runner._action_desc = f"NewScene: {step.root_type}"
editor = _get_editor(runner)
if step._phase == 0:
# Wait for NewSceneDialog to appear
if not editor:
runner._advance()
return
dialog = getattr(editor, "_new_scene_dialog", None)
if not dialog or not dialog.visible:
# In headless mode, the dialog may not exist yet, call new_scene directly
state = editor.state
type_map = {
"3D Scene (Default)": (_resolve_root_type("Node3D"), True),
"3D Scene": (_resolve_root_type("Node3D"), False),
"2D Scene": (_resolve_root_type("Node2D"), False),
"UI Scene": (_resolve_root_type("Control"), False),
"Empty": (Node, False),
}
rt, pop = type_map.get(step.root_type, (Node, False))
state.new_scene(root_type=rt, populate=pop)
runner._advance()
return
# Find the matching row button
inner = dialog._dialog_panel
if not inner:
runner._advance()
return
from simvx.core import Button as _Button
for child in inner.children:
if isinstance(child, _Button) and child.text == step.root_type:
gx, gy, gw, gh = child.get_global_rect()
_init_click(step, runner, (gx + gw / 2, gy + gh / 2))
step._phase = 1
return
runner._advance()
elif step._phase == 1:
if _drive_click(runner, step, dt):
runner._advance()