"""Welcome screen del Studio. Pantalla inicial cuando no hay proyecto abierto. Muestra al usuario las dos acciones principales (crear nuevo / abrir existente) como botones grandes, más una lista de proyectos recientes y enlaces a docs. """ from __future__ import annotations from pathlib import Path from PySide6.QtCore import Qt, Signal from PySide6.QtGui import QIcon, QPixmap from PySide6.QtWidgets import ( QFrame, QHBoxLayout, QLabel, QPushButton, QVBoxLayout, QWidget, ) from vmssailor.studio.theme import ( C_ABYSS, C_CYAN, C_CYAN_DEEP, C_FOAM, C_FOG, C_HORIZON, C_MIDNIGHT, C_SAND, C_STEEL, display_font, mono_font, ui_font, ) BRAND_ROOT = Path(__file__).resolve().parents[2] / "docs" / "brand" class WelcomeScreen(QWidget): """Pantalla de bienvenida con CTAs grandes.""" newProjectRequested = Signal() openProjectRequested = Signal() openRecentRequested = Signal(str) # path def __init__(self, parent: QWidget | None = None) -> None: super().__init__(parent) self.setObjectName("welcomeScreen") self.setStyleSheet( f"#welcomeScreen {{ background: {C_ABYSS}; }}" ) outer = QVBoxLayout(self) outer.setContentsMargins(0, 0, 0, 0) outer.setSpacing(0) outer.addStretch(1) center = QWidget() center.setMaximumWidth(880) cl = QVBoxLayout(center) cl.setSpacing(0) cl.setContentsMargins(48, 0, 48, 0) # Hero with logo + title hero = QHBoxLayout() hero.setSpacing(24) logo_path = BRAND_ROOT / "logo-mark.svg" if logo_path.exists(): logo = QLabel() pix = QIcon(str(logo_path)).pixmap(120, 120) logo.setPixmap(pix) hero.addWidget(logo) title_box = QVBoxLayout() title_box.setSpacing(4) title = QLabel("VMS-Sailor Studio") title.setFont(display_font(36, 700)) title.setStyleSheet(f"color: {C_FOAM};") title_box.addWidget(title) sub = QLabel("VESSEL · MANAGEMENT · SYSTEM") sub.setFont(ui_font(11)) sub.setStyleSheet(f"color: {C_HORIZON}; letter-spacing: 4px;") title_box.addWidget(sub) body = QLabel( "Herramienta de ingeniería para configurar el VMS-Sailor de cada buque. " "Crea un nuevo proyecto desde el wizard o abre un .vmsproj existente." ) body.setWordWrap(True) body.setFont(ui_font(11)) body.setStyleSheet(f"color: {C_SAND}; margin-top: 12px;") title_box.addWidget(body) title_box.addStretch(1) hero.addLayout(title_box, 1) cl.addLayout(hero) cl.addSpacing(32) # Two big CTAs ctas = QHBoxLayout() ctas.setSpacing(16) ctas.addWidget( self._make_cta( title="Nuevo desde wizard", subtitle="Wizard de 8 pasos: tipo de buque, plantilla, dimensiones,\n" "sistemas, equipos, topología, confirmación.", signal=self.newProjectRequested, primary=True, shortcut="Ctrl+N", ), 1, ) ctas.addWidget( self._make_cta( title="Abrir proyecto", subtitle="Carga un archivo .vmsproj existente desde disco.\n" "Edita equipos, mímicos, tags y alarmas.", signal=self.openProjectRequested, primary=False, shortcut="Ctrl+O", ), 1, ) cl.addLayout(ctas) cl.addSpacing(28) # Quick links / docs links_label = QLabel("DOCUMENTACIÓN") links_label.setFont(ui_font(9)) links_label.setStyleSheet( f"color: {C_FOG}; letter-spacing: 2.5px; margin-bottom: 8px;" ) cl.addWidget(links_label) links_row = QHBoxLayout() links_row.setSpacing(8) for txt, hint in ( ("Sprint 0 — Fundaciones", "docs/architecture.md"), ("Sistema de coordenadas", "docs/coords.md"), ("Design System", "docs/design_system.md"), ("Mockups visuales", "docs/mockups/index.html"), ): chip = QLabel(f" {txt} ") chip.setFont(mono_font(9)) chip.setStyleSheet( f"color: {C_SAND}; background: {C_MIDNIGHT}; " f"border: 1px solid {C_STEEL}; border-radius: 12px; padding: 4px 10px;" ) chip.setToolTip(hint) links_row.addWidget(chip) links_row.addStretch(1) cl.addLayout(links_row) outer.addWidget(center, alignment=Qt.AlignmentFlag.AlignHCenter) outer.addStretch(2) footer = QLabel( "Propiedad intelectual de Álvaro · El Studio no se distribuye al cliente" ) footer.setFont(mono_font(9)) footer.setStyleSheet(f"color: {C_FOG}; padding: 16px;") footer.setAlignment(Qt.AlignmentFlag.AlignCenter) outer.addWidget(footer) def _make_cta( self, title: str, subtitle: str, signal, primary: bool, shortcut: str = "", ) -> QFrame: frame = QFrame() frame.setCursor(Qt.CursorShape.PointingHandCursor) bg_hover = C_CYAN_DEEP if primary else C_STEEL border = C_CYAN if primary else C_IRON_FALLBACK bg = C_MIDNIGHT if not primary else "rgba(0,217,255,0.08)" frame.setStyleSheet( f""" QFrame {{ background: {bg}; border: 1px solid {border}; border-radius: 14px; min-height: 180px; }} QFrame:hover {{ background: {bg_hover}; border-color: {C_CYAN}; }} """ ) lay = QVBoxLayout(frame) lay.setContentsMargins(24, 22, 24, 22) lay.setSpacing(8) t = QLabel(title) t.setFont(display_font(22, 700)) t.setStyleSheet(f"color: {C_FOAM};") lay.addWidget(t) s = QLabel(subtitle) s.setFont(ui_font(10)) s.setStyleSheet(f"color: {C_FOG};") s.setWordWrap(True) lay.addWidget(s) lay.addStretch(1) kbd_row = QHBoxLayout() kbd_row.addStretch(1) if shortcut: kbd = QLabel(shortcut) kbd.setFont(mono_font(9)) kbd.setStyleSheet( f"color: {C_FOG}; background: {C_ABYSS}; " f"border: 1px solid {C_STEEL}; border-radius: 4px; padding: 2px 8px;" ) kbd_row.addWidget(kbd) arrow = QLabel("→") arrow.setFont(display_font(22, 700)) arrow.setStyleSheet(f"color: {C_CYAN}; margin-left: 8px;") kbd_row.addWidget(arrow) lay.addLayout(kbd_row) frame.mousePressEvent = lambda _ev: signal.emit() # type: ignore[method-assign] return frame # Fallback color para borde no-primary (no exportado en theme aún si quieres) C_IRON_FALLBACK = "#2C3E5C"