Source code for simvx.editor.modal_dialog
"""Shared modal-dialog scaffolding for the SimVX editor.
The editor hosts a family of small modal dialogs (About, Preferences, Duplicate
Node, Rename Class, …) that all wire up the engine's modal-router contract in
exactly the same way: ``modal=True``, outside-click dismissal, a ``top_level``
overlay that does not pause the tree, the ``cancel_requested`` router signal, and
``show_modal()`` / ``close_modal()`` to drive the modal stack.
:class:`BaseModalDialog` centralises that boilerplate so each dialog only
declares its own geometry, contents, and result signals. It is a thin
:class:`~simvx.core.Panel` subclass: the panel background starts fully
transparent (so a dialog that paints itself via ``on_draw`` or an inner panel is
unaffected), and the modal router injects the dimmed backdrop.
Subclasses typically:
* set ``z_index`` (overlay stacking) and, for full-rect overlays,
``set_anchor_preset(AnchorPreset.FULL_RECT)``;
* call :meth:`open_modal` to show and :meth:`dismiss` to hide;
* override :meth:`_on_cancel` when an outside click / Escape should emit a
``cancelled`` result signal (the default just hides the dialog).
"""
from __future__ import annotations
from simvx.core import Panel
__all__ = ["BaseModalDialog"]
[docs]
class BaseModalDialog(Panel):
"""Base class for the editor's modal-router dialogs.
Centralises the modal-router setup (``modal``, ``dismiss_on_outside_click``,
``pause_tree_when_modal``, ``top_level``, the ``cancel_requested`` wiring) and
the show/hide lifecycle (:meth:`open_modal` / :meth:`dismiss`). The panel
background is transparent by default; subclasses paint their own card via
``on_draw`` or an inner :class:`~simvx.core.Panel`.
``pause_tree_when_modal`` defaults to ``False`` (the editor stays live behind
the dialog). Set :attr:`PAUSE_TREE_WHEN_MODAL` on the subclass to opt into a
dimmed, paused backdrop instead.
"""
#: Whether the running scene tree pauses while this dialog is open. Overlay
#: dialogs that want the engine's dimmed backdrop set this to ``True``.
PAUSE_TREE_WHEN_MODAL = False
def __init__(self, **kwargs):
super().__init__(**kwargs)
# Transparent panel background: subclasses draw their own card.
self.bg_colour = (0.0, 0.0, 0.0, 0.0)
self.border_width = 0
self.modal = True
self.dismiss_on_outside_click = True
self.pause_tree_when_modal = self.PAUSE_TREE_WHEN_MODAL
self.top_level = True
self.visible = False
self.cancel_requested.connect(self._on_router_cancel)
# --------------------------------------------------------------- lifecycle
[docs]
def open_modal(self) -> None:
"""Show the dialog and capture UI input via the modal router."""
self.show_modal()
[docs]
def dismiss(self) -> None:
"""Hide the dialog without emitting a result signal. Idempotent."""
if not self.visible:
return
self.close_modal()
# ----------------------------------------------------------------- router
def _on_router_cancel(self) -> None:
"""Router fired ``cancel_requested`` (Escape / outside click)."""
if self.visible:
self._on_cancel()
def _on_cancel(self) -> None:
"""Handle Escape / outside-click / Cancel-button dismissal.
Defaults to a plain :meth:`dismiss`. Override to additionally emit a
``cancelled`` result signal.
"""
self.dismiss()