Files
aza/APP/logo-tool/ui/main_window.py
2026-03-30 07:59:11 +02:00

239 lines
8.4 KiB
Python
Raw Permalink Blame History

This file contains ambiguous Unicode characters
This file contains Unicode characters that might be confused with other characters. If you think that this is intentional, you can safely ignore this warning. Use the Escape button to reveal them.
"""
Hauptfenster: verbindet Canvas, Toolbar, Properties-Panel und Statusleiste.
"""
from __future__ import annotations
from pathlib import Path
from PySide6.QtWidgets import (
QMainWindow, QHBoxLayout, QWidget, QFileDialog, QMessageBox,
)
from PySide6.QtCore import Qt
from PySide6.QtGui import QPixmap
from ui.canvas_widget import LogoCanvas
from ui.properties_panel import PropertiesPanel
from ui.toolbar import MainToolbar
from ui.status_bar import LogoStatusBar
from core.logo_model import LogoProject
from core.image_loader import load_png, numpy_to_qpixmap
class MainWindow(QMainWindow):
def __init__(self):
super().__init__()
self.setWindowTitle("Logo-Tool Pixel-Perfect Editor")
self.resize(1400, 900)
self.setMinimumSize(900, 600)
self.project = LogoProject()
self._apply_dark_theme()
self._build_ui()
self._connect_signals()
def _apply_dark_theme(self):
self.setStyleSheet("""
QMainWindow {
background-color: #252525;
}
QWidget {
color: #ddd;
font-family: "Segoe UI", "Inter", sans-serif;
font-size: 13px;
}
QScrollBar:vertical {
background: #2a2a2a; width: 10px; border: none;
}
QScrollBar::handle:vertical {
background: #555; border-radius: 4px; min-height: 30px;
}
QScrollBar:horizontal {
background: #2a2a2a; height: 10px; border: none;
}
QScrollBar::handle:horizontal {
background: #555; border-radius: 4px; min-width: 30px;
}
QDoubleSpinBox, QSpinBox {
background: #333; border: 1px solid #555;
border-radius: 3px; padding: 3px 6px; color: #ddd;
}
QSlider::groove:horizontal {
background: #444; height: 4px; border-radius: 2px;
}
QSlider::handle:horizontal {
background: #0078d4; width: 14px; height: 14px;
margin: -5px 0; border-radius: 7px;
}
""")
def _build_ui(self):
# Toolbar
self.toolbar = MainToolbar(self)
self.addToolBar(Qt.ToolBarArea.TopToolBarArea, self.toolbar)
# Statusbar
self.status_bar = LogoStatusBar(self)
self.setStatusBar(self.status_bar)
# Zentrales Layout: Canvas + Properties-Panel
central = QWidget()
layout = QHBoxLayout(central)
layout.setContentsMargins(0, 0, 0, 0)
layout.setSpacing(0)
self.canvas = LogoCanvas()
layout.addWidget(self.canvas, stretch=1)
self.properties = PropertiesPanel()
layout.addWidget(self.properties)
self.setCentralWidget(central)
def _connect_signals(self):
# Toolbar → Aktionen
self.toolbar.open_file_requested.connect(self._on_open_file)
self.toolbar.save_file_requested.connect(self._on_save_file)
self.toolbar.zoom_to_fit_requested.connect(self.canvas.fit_to_view)
self.toolbar.zoom_value_changed.connect(self.canvas.set_zoom)
self.toolbar.show_original_toggled.connect(self._on_toggle_original)
self.toolbar.show_vectors_toggled.connect(self._on_toggle_vectors)
# Canvas → Statusleiste
self.canvas.zoom_changed.connect(self.status_bar.set_zoom)
self.canvas.zoom_changed.connect(self.toolbar.update_zoom_display)
# Properties → Rendering
self.properties.letter_spacing_changed.connect(self._on_spacing_changed)
self.properties.stroke_width_changed.connect(self._on_stroke_changed)
self.properties.retrace_requested.connect(self._on_retrace)
self.properties.export_requested.connect(self._on_export)
# ─── Datei-Aktionen ──────────────────────────────────────
def _on_open_file(self):
path, _ = QFileDialog.getOpenFileName(
self, "Logo-PNG öffnen", "",
"Bilder (*.png *.jpg *.jpeg *.bmp);;Alle Dateien (*)",
)
if not path:
return
try:
pixels, w, h = load_png(path)
except Exception as e:
QMessageBox.critical(self, "Fehler", f"Bild konnte nicht geladen werden:\n{e}")
return
self.project = LogoProject(
source_path=Path(path),
original_pixels=pixels,
width=w,
height=h,
)
pixmap = numpy_to_qpixmap(pixels, w, h)
self.canvas.set_original_image(pixmap)
self.status_bar.set_file_info(Path(path).name, w, h)
self.status_bar.set_path_count(0)
self._try_auto_trace()
def _on_save_file(self):
if not self.project.has_image:
QMessageBox.information(self, "Info", "Kein Bild geladen.")
return
path, _ = QFileDialog.getSaveFileName(
self, "Logo speichern", "", "PNG (*.png);;SVG (*.svg)",
)
if not path:
return
if path.lower().endswith(".png"):
self._export_png(path)
else:
QMessageBox.information(
self, "Info", "SVG-Export wird in einer zukünftigen Version unterstützt."
)
def _on_export(self):
self._on_save_file()
# ─── Rendering ───────────────────────────────────────────
def _on_spacing_changed(self, value: float):
self.project.global_letter_spacing = value
self._update_vector_overlay()
def _on_stroke_changed(self, value: float):
self.project.global_stroke_width = value
self._update_vector_overlay()
def _on_retrace(self):
self._try_auto_trace()
def _try_auto_trace(self):
"""Versucht die automatische Vektorisierung."""
if not self.project.has_image:
return
try:
from core.vectorizer import trace_to_paths
except ImportError:
QMessageBox.warning(
self, "Warnung",
"potracer ist nicht installiert.\n"
"Bitte 'pip install potracer' ausführen für Vektorisierung.",
)
return
threshold = self.properties.slider_threshold.value()
try:
glyphs = trace_to_paths(self.project.original_pixels, threshold=threshold)
self.project.glyphs = glyphs
self.status_bar.set_path_count(len(glyphs))
self.properties.populate_glyphs([g.label for g in glyphs])
self._update_vector_overlay()
except ImportError as e:
QMessageBox.warning(self, "Warnung", str(e))
except Exception as e:
QMessageBox.critical(self, "Tracing-Fehler", f"Vektorisierung fehlgeschlagen:\n{e}")
def _update_vector_overlay(self):
"""Rendert die Vektorpfade neu und aktualisiert das Overlay."""
if not self.project.has_vectors:
self.canvas.clear_overlay()
return
try:
from core.cairo_renderer import render_to_qpixmap
overlay = render_to_qpixmap(self.project)
self.canvas.set_overlay(overlay)
except Exception as e:
self.status_bar.showMessage(f"Render-Fehler: {e}", 5000)
def _export_png(self, path: str):
"""Exportiert das aktuelle Rendering als PNG."""
try:
from core.cairo_renderer import render_project
import numpy as np
from PIL import Image
pixels = render_project(self.project)
img = Image.fromarray(pixels, "RGBA")
img.save(path)
self.status_bar.showMessage(f"Exportiert: {path}", 3000)
except Exception as e:
QMessageBox.critical(self, "Export-Fehler", f"Export fehlgeschlagen:\n{e}")
# ─── Ansicht ─────────────────────────────────────────────
def _on_toggle_original(self, visible: bool):
if hasattr(self.canvas, '_pixmap_item') and self.canvas._pixmap_item:
self.canvas._pixmap_item.setVisible(visible)
def _on_toggle_vectors(self, visible: bool):
if hasattr(self.canvas, '_overlay_item') and self.canvas._overlay_item:
self.canvas._overlay_item.setVisible(visible)