Source code for simvx.editor.layout_presets

"""Layout Presets — Serialize and restore DockContainer state with built-in presets."""

import json
import logging
from pathlib import Path

log = logging.getLogger(__name__)

BUILTIN_PRESETS: dict[str, dict] = {
    "Default": {
        "type": "split",
        "vertical": True,
        "ratio": 0.2,
        "first": {"type": "panel", "name": "Scene"},
        "second": {
            "type": "split",
            "vertical": True,
            "ratio": 0.75,
            "first": {"type": "panel", "name": "Viewport"},
            "second": {"type": "panel", "name": "Inspector"},
        },
    },
    "Animation": {
        "type": "split",
        "vertical": False,
        "ratio": 0.6,
        "first": {
            "type": "split",
            "vertical": True,
            "ratio": 0.15,
            "first": {"type": "panel", "name": "Scene"},
            "second": {
                "type": "split",
                "vertical": True,
                "ratio": 0.82,
                "first": {"type": "panel", "name": "Viewport"},
                "second": {"type": "panel", "name": "Inspector"},
            },
        },
        "second": {"type": "panel", "name": "Bottom"},
    },
    "Code": {
        "type": "split",
        "vertical": True,
        "ratio": 0.2,
        "first": {"type": "panel", "name": "Scene"},
        "second": {"type": "panel", "name": "Viewport"},
    },
    "Debug": {
        "type": "split",
        "vertical": False,
        "ratio": 0.65,
        "first": {
            "type": "split",
            "vertical": True,
            "ratio": 0.25,
            "first": {"type": "panel", "name": "Scene"},
            "second": {
                "type": "split",
                "vertical": True,
                "ratio": 0.67,
                "first": {"type": "panel", "name": "Viewport"},
                "second": {"type": "panel", "name": "Inspector"},
            },
        },
        "second": {"type": "panel", "name": "Bottom"},
    },
}

USER_PRESETS_PATH = Path.home() / ".simvx" / "layout_presets.json"

[docs] class LayoutPresets: """Manages editor layout presets (built-in and user-defined).""" def __init__(self): self._custom_presets: dict[str, dict] = {} self._load_custom() # ------------------------------------------------------------------ # Public API # ------------------------------------------------------------------
[docs] def get_preset(self, name: str) -> dict | None: """Return preset data by name, preferring custom over built-in.""" return self._custom_presets.get(name) or BUILTIN_PRESETS.get(name)
[docs] def save_preset(self, name: str, layout: dict): """Save a custom preset (persists to disk).""" self._custom_presets[name] = layout self._persist_custom()
[docs] def delete_preset(self, name: str) -> bool: """Delete a custom preset. Built-in presets cannot be deleted.""" if name in self._custom_presets: del self._custom_presets[name] self._persist_custom() return True return False
[docs] def list_presets(self) -> list[str]: """Return all preset names (built-in first, then custom).""" return list(BUILTIN_PRESETS) + [k for k in self._custom_presets if k not in BUILTIN_PRESETS]
[docs] def apply_preset(self, name: str, shell) -> bool: """Apply a layout preset to the editor shell's DockContainer. Args: name: Preset name. shell: Object with a ``_dock`` attribute (the editor's DockContainer). Returns: True if applied, False if preset not found or no dock available. """ preset = self.get_preset(name) if not preset: return False dock = getattr(shell, "_dock", None) if dock is None: dock = getattr(getattr(shell, "state", None), "_dock_container", None) if dock is None: return False dock.restore_layout(preset) return True
[docs] def capture_layout(self, shell) -> dict | None: """Capture the current dock layout from the editor shell. Returns: Layout dict, or None if no dock is available. """ dock = getattr(shell, "_dock", None) if dock is None: dock = getattr(getattr(shell, "state", None), "_dock_container", None) if dock is None: return None return dock.save_layout()
# ------------------------------------------------------------------ # Persistence # ------------------------------------------------------------------ def _load_custom(self): """Load custom presets from disk.""" if not USER_PRESETS_PATH.exists(): return try: self._custom_presets = json.loads(USER_PRESETS_PATH.read_text(encoding="utf-8")) except Exception: log.warning("Could not load custom layout presets from %s", USER_PRESETS_PATH) def _persist_custom(self): """Write custom presets to disk.""" USER_PRESETS_PATH.parent.mkdir(parents=True, exist_ok=True) try: USER_PRESETS_PATH.write_text(json.dumps(self._custom_presets, indent=2), encoding="utf-8") except Exception: log.exception("Could not save custom layout presets to %s", USER_PRESETS_PATH)