Source code for simvx.editor.menus
"""Editor Menus — Menu bar setup and keyboard shortcuts."""
from __future__ import annotations
from simvx.core import (
MenuBar,
MenuItem,
)
from .state import EditorState
[docs]
def register_shortcuts(state: EditorState):
"""Register all editor keyboard shortcuts."""
s = state.shortcuts
# File
s.register("new_scene", "Ctrl+N", lambda: state.new_scene_requested.emit())
s.register("new_file", "Ctrl+Shift+N", lambda: _new_file(state))
s.register("open_scene", "Ctrl+O", lambda: _open_scene(state))
s.register("save_scene", "Ctrl+S", lambda: state.save_scene())
s.register("save_scene_as", "Ctrl+Shift+S", lambda: _save_scene_as(state))
# Edit
s.register("undo", "Ctrl+Z", state.undo_stack.undo)
s.register("redo", "Ctrl+Shift+Z", state.undo_stack.redo)
s.register("delete", "Delete", lambda: _delete(state))
# Scene
s.register("play", "F5", state.play_scene)
s.register("stop", "F6", state.stop_scene)
s.register("pause", "F7", state.pause_scene)
# Gizmo
s.register("gizmo_cycle", "Q", state.gizmo.cycle_mode)
# ---- Action implementations ----
def _open_scene(state: EditorState):
"""Open scene file dialog."""
state._show_open_dialog()
def _save_scene_as(state: EditorState):
"""Save scene with file dialog."""
state._show_save_as_dialog()
def _quit(state: EditorState):
"""Quit the editor cleanly.
SystemExit is caught by the try/finally in Engine.run(),
which ensures proper Vulkan cleanup before process exit.
"""
raise SystemExit(0)
def _cut(state: EditorState):
"""Cut selected node."""
node = state.selection.primary
if node:
state.clipboard.copy_node(node)
state.remove_node(node)
def _copy(state: EditorState):
"""Copy selected node."""
node = state.selection.primary
if node:
state.clipboard.copy_node(node)
def _paste(state: EditorState):
"""Paste node from clipboard."""
if state.clipboard.has_node():
parent = state.selection.primary or (state.edited_scene.root if state.edited_scene else None)
if parent:
node = state.clipboard.paste_node()
if node:
state.add_node(node, parent)
def _delete(state: EditorState):
"""Delete selected node."""
node = state.selection.primary
if node and node.parent:
state.remove_node(node)
state.selection.clear()
def _select_all(state: EditorState):
"""Select all nodes in the scene."""
from simvx.core import Node
root = state.edited_scene.root if state.edited_scene else None
if root:
all_nodes = [root] + root.find_all(Node)
state.selection.select_all(all_nodes)
def _add_node(state: EditorState):
"""Request the Add Node type dialog via the editor state signal."""
state.add_node_requested.emit()
def _instance_scene(state: EditorState):
"""Open file dialog to instance a scene."""
state._show_open_dialog()
def _new_file(state: EditorState):
"""Create a new untitled file and switch to Script mode."""
code_panel = getattr(state, "_viewport_code", None)
if code_panel and hasattr(code_panel, "new_file"):
code_panel.new_file()
state.viewport_mode = "code"
state.viewport_mode_changed.emit()
def _set_viewport(state: EditorState, mode: str):
"""Switch viewport mode and notify the editor shell."""
state.viewport_mode = mode
state.viewport_mode_changed.emit()
def _reset_layout(state: EditorState):
"""Reset dock layout to default split ratios."""
from simvx.core import SplitContainer
dock = getattr(state, "_dock_container", None)
if dock is None:
return
def _reset(node):
for child in getattr(node, "children", []):
if isinstance(child, SplitContainer):
child.split_ratio = 0.25 if child.vertical else 0.75
child._update_layout()
_reset(child)
_reset(dock)