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 build_menu_bar(state: EditorState) -> MenuBar: """Create the editor's menu bar with all menus wired to state.""" bar = MenuBar(name="MenuBar") bar.size_y = 28.0 # ---- File menu ---- bar.add_menu( "File", [ MenuItem("New Scene", callback=lambda: state.new_scene_requested.emit(), shortcut="Ctrl+N"), MenuItem("New File", callback=lambda: _new_file(state), shortcut="Ctrl+Shift+N"), MenuItem("Open Scene...", callback=lambda: _open_scene(state), shortcut="Ctrl+O"), MenuItem(separator=True), MenuItem("Save", callback=lambda: state.save_scene(), shortcut="Ctrl+S"), MenuItem("Save As...", callback=lambda: _save_scene_as(state), shortcut="Ctrl+Shift+S"), MenuItem(separator=True), MenuItem("Quit", callback=lambda: _quit(state), shortcut="Ctrl+Q"), ], ) # ---- Edit menu ---- bar.add_menu( "Edit", [ MenuItem("Undo", callback=state.undo_stack.undo, shortcut="Ctrl+Z"), MenuItem("Redo", callback=state.undo_stack.redo, shortcut="Ctrl+Shift+Z"), MenuItem(separator=True), MenuItem("Cut", callback=lambda: _cut(state), shortcut="Ctrl+X"), MenuItem("Copy", callback=lambda: _copy(state), shortcut="Ctrl+C"), MenuItem("Paste", callback=lambda: _paste(state), shortcut="Ctrl+V"), MenuItem("Delete", callback=lambda: _delete(state), shortcut="Delete"), MenuItem(separator=True), MenuItem("Select All", callback=lambda: _select_all(state), shortcut="Ctrl+A"), MenuItem(separator=True), MenuItem("Preferences...", callback=lambda: state.preferences_requested.emit()), ], ) # ---- Scene menu ---- bar.add_menu( "Scene", [ MenuItem("Add Node...", callback=lambda: _add_node(state)), MenuItem("Instance Scene...", callback=lambda: _instance_scene(state)), MenuItem(separator=True), MenuItem("Run Scene", callback=state.play_scene, shortcut="F5"), MenuItem("Stop", callback=state.stop_scene, shortcut="F6"), MenuItem("Pause", callback=state.pause_scene, shortcut="F7"), ], ) # ---- View menu ---- bar.add_menu( "View", [ MenuItem("3D Viewport", callback=lambda: _set_viewport(state, "3d")), MenuItem("2D Viewport", callback=lambda: _set_viewport(state, "2d")), MenuItem("Script Editor", callback=lambda: _set_viewport(state, "code")), MenuItem(separator=True), MenuItem("Reset Layout", callback=lambda: _reset_layout(state)), ], ) # ---- Help menu ---- bar.add_menu( "Help", [ MenuItem("About SimVX", callback=lambda: state.about_requested.emit()), ], ) # Store reference so plugins can add menu items later state._tools_menu_bar = bar return bar
[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)