"""Inspector widget interaction handlers (rename, edit, set_colour, set_anchor_preset)."""
import logging
from simvx.core.scripted_demo import DemoRunner
from .._helpers import (
_colour_to_hex,
_drive_backspace_clear,
_drive_click,
_drive_type_chars,
_find_prop_widget,
_get_editor,
_init_click,
_send_key,
)
from ..steps import (
InspectorEdit,
InspectorRename,
InspectorSetAnchorPreset,
InspectorSetColour,
)
log = logging.getLogger(__name__)
def _handle_inspector_rename(runner: DemoRunner, step: InspectorRename, dt: float):
runner._action_desc = f"Rename: {step.new_name}"
editor = _get_editor(runner)
if step._phase == 0:
if not editor or not editor.inspector_panel or not editor.inspector_panel._name_edit:
runner._advance()
return
edit = editor.inspector_panel._name_edit
gx, gy, gw, gh = edit.get_global_rect()
_init_click(step, runner, (gx + gw / 2, gy + gh / 2))
step._edit_ref = edit
step._phase = 1
elif step._phase == 1:
if _drive_click(runner, step, dt):
_send_key(runner, "end")
step._clear_count = len(step._edit_ref.text)
step._cleared = 0
step._mc_t = 0.0
step._phase = 2
elif step._phase == 2:
if _drive_backspace_clear(runner, step, dt):
step._char_idx = 0
step._mc_t = 0.0
step._phase = 3
elif step._phase == 3:
if _drive_type_chars(runner, step, dt, step.new_name):
step._phase = 4
elif step._phase == 4:
_send_key(runner, "enter")
if editor and editor.scene_tree_panel:
editor.scene_tree_panel._rebuild_tree()
runner._advance()
def _handle_inspector_edit(runner: DemoRunner, step: InspectorEdit, dt: float):
runner._action_desc = f"Set: {step.prop} = {step.value}"
editor = _get_editor(runner)
if step._phase == 0:
if not editor or not editor.inspector_panel:
runner._advance()
return
widget = _find_prop_widget(editor.inspector_panel, step.prop, step.component)
if not widget:
# In visual mode the inspector rebuild can lag several frames
retries = getattr(step, "_retry_count", 0)
if retries < 30:
step._retry_count = retries + 1
return
runner._advance()
return
step._widget_ref = widget
gx, gy, gw, gh = widget.get_global_rect()
# Click centre of the widget (SpinBox value area, or TextEdit body)
target = (gx + gw / 2, gy + gh / 2)
_init_click(step, runner, target)
step._phase = 1
elif step._phase == 1:
if _drive_click(runner, step, dt):
from simvx.core.ui.advanced import SpinBox as _SpinBox
from simvx.core.ui.widgets import TextEdit as _TextEdit
widget = step._widget_ref
if isinstance(widget, _SpinBox):
# Ensure SpinBox entered editing mode (click routing can
# miss due to UI clipping or overlapping widgets)
if not widget._editing:
widget._enter_edit()
step._char_idx = 0
step._mc_t = 0.0
step._phase = 3
elif isinstance(widget, _TextEdit):
_send_key(runner, "end")
step._clear_count = len(widget.text)
step._cleared = 0
step._mc_t = 0.0
step._phase = 2
else:
runner._advance()
elif step._phase == 2: # clear TextEdit via backspace
if _drive_backspace_clear(runner, step, dt):
val_str = str(step.value)
if not val_str:
step._phase = 4
else:
step._char_idx = 0
step._mc_t = 0.0
step._phase = 3
elif step._phase == 3: # type value
if _drive_type_chars(runner, step, dt, str(step.value)):
from simvx.core.ui.advanced import SpinBox as _SpinBox
widget = step._widget_ref
if isinstance(widget, _SpinBox):
# Ensure the input text matches the intended value,
# character-by-character typing can lose characters if
# the widget momentarily loses focus between frames.
widget._input_text = str(step.value)
step._phase = 4
step._mc_t = 0.0
elif step._phase == 4: # enter to submit
_send_key(runner, "enter")
runner._advance()
def _handle_inspector_set_colour(runner: DemoRunner, step: InspectorSetColour, dt: float):
runner._action_desc = f"Colour: {step.prop}"
editor = _get_editor(runner)
if step._phase == 0:
# Find the ColourPicker widget in inspector. In visual mode the
# inspector rebuild may lag a frame behind the step, retry once
# on the next frame before failing.
if not editor or not editor.inspector_panel:
raise ValueError(f"InspectorSetColour: no inspector panel for {step.prop}")
widget = editor.inspector_panel._property_widgets.get(step.prop)
if widget is None:
widget = editor.inspector_panel._property_widgets.get(f"mat_{step.prop}")
if not widget:
# In visual mode the inspector rebuild can lag several frames
retries = getattr(step, "_retry_count", 0)
if retries < 30:
step._retry_count = retries + 1
return
raise ValueError(f"InspectorSetColour: property widget not found: {step.prop}")
from simvx.core import ColourPicker
if not isinstance(widget, ColourPicker):
raise ValueError(f"InspectorSetColour: widget for {step.prop} is not a ColourPicker")
step._widget_ref = widget
# Click the hex input area
hx, hy, hw, hh = widget._hex_rect()
_init_click(step, runner, (hx + hw / 2, hy + hh / 2))
step._phase = 1
elif step._phase == 1:
# Drive click on hex rect → activates hex editing mode
if _drive_click(runner, step, dt):
step._clear_count = len(step._widget_ref._hex_text)
step._cleared = 0
step._mc_t = 0.0
step._phase = 2
elif step._phase == 2:
# Clear existing hex text via backspace
if _drive_backspace_clear(runner, step, dt):
step._char_idx = 0
step._mc_t = 0.0
step._phase = 3
elif step._phase == 3:
# Type hex colour value char by char
if _drive_type_chars(runner, step, dt, _colour_to_hex(step.colour)):
step._phase = 4
elif step._phase == 4:
# Press enter to commit the hex value
_send_key(runner, "enter")
runner._advance()
def _handle_inspector_set_anchor_preset(
runner: DemoRunner, step: InspectorSetAnchorPreset, dt: float,
):
"""Click the named preset button in the inspector's AnchorPresetButton."""
runner._action_desc = f"Anchor preset: {step.preset}"
editor = _get_editor(runner)
from simvx.core.ui.enums import AnchorPreset
from ....panels.anchor_preset_widget import AnchorPresetButton
if step._phase == 0:
if not editor or not editor.inspector_panel:
raise ValueError("InspectorSetAnchorPreset: no inspector panel")
widget = editor.inspector_panel._property_widgets.get("__anchor_preset")
if widget is None:
if step._retry_count < 30:
step._retry_count += 1
return
raise ValueError("InspectorSetAnchorPreset: no AnchorPresetButton in inspector "
"(selected node is not a Control with Layout properties)")
if not isinstance(widget, AnchorPresetButton):
raise ValueError("InspectorSetAnchorPreset: widget is not an AnchorPresetButton")
try:
preset = AnchorPreset[step.preset]
except KeyError as exc:
raise ValueError(f"InspectorSetAnchorPreset: unknown preset '{step.preset}'") from exc
btn = widget._preset_buttons.get(preset)
if btn is None:
raise ValueError(f"InspectorSetAnchorPreset: preset button missing for {step.preset}")
bx, by, bw, bh = btn.get_global_rect()
if bw <= 0 or bh <= 0:
if step._retry_count < 30:
step._retry_count += 1
return
raise ValueError("InspectorSetAnchorPreset: preset button not laid out")
step._widget_ref = widget
_init_click(step, runner, (bx + bw / 2, by + bh / 2))
step._phase = 1
elif step._phase == 1:
if _drive_click(runner, step, dt):
runner._advance()