"""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)