From a750e87517f4ca45ac05b64a81505f388908364a Mon Sep 17 00:00:00 2001 From: Karim Date: Sun, 5 Jul 2026 20:14:10 +0200 Subject: [PATCH] =?UTF-8?q?Native=20macOS-Titelleiste:=20titleBarStyle=20O?= =?UTF-8?q?verlay=20(echte=20Ampeln=20+=20gerundete=20Ecken),=20Zeile=20li?= =?UTF-8?q?nks=20einger=C3=BCckt;=20WindowControls=20Electron-only?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- src-tauri/tauri.conf.json | 3 +- src/styles.css | 6 ++++ src/ui/TopBar.tsx | 24 ++++++++++----- src/ui/WindowControls.tsx | 64 +++++++-------------------------------- 4 files changed, 36 insertions(+), 61 deletions(-) diff --git a/src-tauri/tauri.conf.json b/src-tauri/tauri.conf.json index 8032b5e..cad57c1 100644 --- a/src-tauri/tauri.conf.json +++ b/src-tauri/tauri.conf.json @@ -15,7 +15,8 @@ "width": 1400, "height": 900, "dragDropEnabled": false, - "decorations": false + "titleBarStyle": "Overlay", + "hiddenTitle": true } ], "security": { diff --git a/src/styles.css b/src/styles.css index fca0bb6..dd9af0b 100644 --- a/src/styles.css +++ b/src/styles.css @@ -174,6 +174,12 @@ body { scrollbar-width: thin; scrollbar-color: transparent transparent; } +/* Tauri/macOS: native Ampeln (titleBarStyle "Overlay") liegen oben links über + dem Inhalt → Zeile links einrücken, damit `dossier.` frei bleibt (~3 Ampeln + + Rand). */ +.topbar.topbar-native-inset { + padding-left: 78px; +} /* Sehr ruhige eigene Scrollleiste (WebKit/Blink): Thumb nur beim Scrollen (`is-scrolling`, von TopBar gesetzt) oder beim Überfahren sichtbar. */ .topbar::-webkit-scrollbar { diff --git a/src/ui/TopBar.tsx b/src/ui/TopBar.tsx index f532ab2..62ead48 100644 --- a/src/ui/TopBar.tsx +++ b/src/ui/TopBar.tsx @@ -1298,14 +1298,24 @@ export function TopBar({ }; }, []); + // Unter Tauri (native macOS-Ampeln oben links via titleBarStyle "Overlay") + // braucht die Zeile links Platz, damit `dossier.` nicht darunter liegt. + const tauriNativeTitlebar = + typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; + return ( - // `data-tauri-drag-region`: die randlose (decorations:false) Titelleiste ist - // selbst der Fenster-Ziehbereich. Kind-Buttons/Dropdowns bleiben klickbar; - // nur die leeren Zonen ziehen das Fenster. -
- {/* Fensterknöpfe (Schliessen/Minimieren/Maximieren) GANZ LINKS — die - randlose Titelleiste beginnt mit ihnen (macOS-Konvention). Nur unter - Electron/Tauri sichtbar (sonst null). */} + // `data-tauri-drag-region`: die Titelleiste ist selbst der Fenster-Ziehbereich + // (macOS titleBarStyle "Overlay"). Kind-Buttons/Dropdowns bleiben klickbar; + // nur die leeren Zonen ziehen das Fenster. Unter Tauri bekommt die Zeile links + // einen Inset, damit `dossier.` nicht unter den nativen macOS-Ampeln liegt. +
+ {/* Fensterknöpfe NUR unter Electron (randloses frame:false-Fenster) — unter + Tauri liefern die NATIVEN macOS-Ampeln Schliessen/Minimieren/Maximieren, + daher rendert WindowControls dort null. */} {/* Kleine Wortmarke „dossier" (OCS-Stil, klein/kleingeschrieben) + Quick- diff --git a/src/ui/WindowControls.tsx b/src/ui/WindowControls.tsx index 76b31b8..e060e1c 100644 --- a/src/ui/WindowControls.tsx +++ b/src/ui/WindowControls.tsx @@ -1,75 +1,33 @@ -// Eigene Fenstersteuerung (Minimieren/Maximieren/Schliessen) für das RANDLOSE -// Fenster — sowohl Electron (frame:false, s. scripts/electron-main.cjs) ALS AUCH -// Tauri (decorations:false, s. tauri.conf.json). Ohne native Titelleiste gibt es -// sonst keine Möglichkeit, das Fenster zu steuern. +// Eigene Fenstersteuerung (Minimieren/Maximieren/Schliessen) für das randlose +// ELECTRON-Fenster (frame:false, s. scripts/electron-main.cjs) — dort gibt es +// ohne native Titelleiste sonst keine Fenstersteuerung. // -// Rendert NUR, wenn eine Fenster-API vorhanden ist: unter Electron -// `window.dossierWindow` (Preload), unter Tauri `getCurrentWindow()` -// (@tauri-apps/api/window, dynamisch geladen wie in nativeSync.ts). Im reinen -// Browser-Dev-Modus (?vite) bleibt die Komponente unsichtbar (`null`) — dort -// steuert der Browser das Fenster. Die Tauri-Permissions liegen in -// src-tauri/capabilities/default.json (core:window:allow-minimize/…/close). +// Unter TAURI werden die NATIVEN macOS-Ampeln verwendet (tauri.conf.json: +// titleBarStyle "Overlay" + hiddenTitle, native Dekoration mit gerundeten Ecken), +// daher rendert diese Komponente dort NICHT (kein `window.dossierWindow`) — sonst +// gäbe es doppelte Knöpfe. Im Browser-Dev-Modus ebenfalls unsichtbar (`null`). import { useEffect, useState } from "react"; -/** Gemeinsame, minimale Fenster-API (Electron-Preload ODER Tauri-Adapter). */ -interface WindowApi { +/** Von electron-preload.cjs via contextBridge injiziert (nur unter Electron). */ +interface DossierWindowApi { minimize: () => void; toggleMaximize: () => void; close: () => void; isMaximized: () => Promise; - /** Abo auf Maximiert-Wechsel; liefert eine Abmelde-Funktion. */ onMaximizedChange: (callback: (isMaximized: boolean) => void) => () => void; } declare global { interface Window { - dossierWindow?: WindowApi; + dossierWindow?: DossierWindowApi; } } -/** Tauri-Laufzeit erkannt (analog nativeSync.ts). */ -function isTauri(): boolean { - return typeof window !== "undefined" && "__TAURI_INTERNALS__" in window; -} - export function WindowControls() { - const [api, setApi] = useState(null); + const api = window.dossierWindow; const [maximized, setMaximized] = useState(false); - // Passende Fenster-API einmalig auflösen: Electron synchron, Tauri asynchron - // (dynamischer Import, damit Browser-/Build-Pfad ohne @tauri-apps grün bleibt). - useEffect(() => { - if (window.dossierWindow) { - setApi(window.dossierWindow); - return; - } - if (!isTauri()) return; - let disposed = false; - import(/* @vite-ignore */ "@tauri-apps/api/window") - .then(({ getCurrentWindow }) => { - if (disposed) return; - const w = getCurrentWindow(); - setApi({ - minimize: () => void w.minimize(), - toggleMaximize: () => void w.toggleMaximize(), - close: () => void w.close(), - isMaximized: () => w.isMaximized(), - onMaximizedChange: (cb) => { - // Bei jeder Größenänderung den Maximiert-Zustand neu melden. - const unlisten = w.onResized(() => { - void w.isMaximized().then(cb); - }); - return () => void unlisten.then((fn) => fn()); - }, - }); - }) - .catch(() => {}); - return () => { - disposed = true; - }; - }, []); - useEffect(() => { if (!api) return; let cancelled = false;