From 4445b715434d503cedd62b7ebd2c01fe6c700e0c Mon Sep 17 00:00:00 2001
From: Ariver <ar@MacBook-Air.local>
Date: Mon, 11 May 2026 00:31:04 +0800
Subject: [PATCH] Archive TagLauncher 4.0.7

---
 Apptag/ApptagApp.swift |  133 +++++++++++++++++++++++++-------------------
 1 files changed, 76 insertions(+), 57 deletions(-)

diff --git a/Apptag/ApptagApp.swift b/Apptag/ApptagApp.swift
index f3f3948..b572f20 100644
--- a/Apptag/ApptagApp.swift
+++ b/Apptag/ApptagApp.swift
@@ -5,7 +5,7 @@
 // MARK: - Application Entry Point
 
 @main
-struct ApptagApp: App {
+struct TagLauncherApp: App {
     @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate
 
     var body: some Scene {
@@ -45,7 +45,7 @@
         setupLaunchAtLogin()
     }
 
-    /// Dock icon click → show overlay (same as menubar "Show Apptag")
+    /// Dock icon click → show overlay (same as menubar "Show TagLauncher")
     func applicationShouldHandleReopen(_ sender: NSApplication, hasVisibleWindows flag: Bool) -> Bool {
         showOverlay()
         return false  // Suppress default "unhide all windows" behavior
@@ -137,9 +137,9 @@
         if let button = statusItem.button {
             button.image = NSImage(
                 systemSymbolName: "tag.fill",
-                accessibilityDescription: "Apptag"
+                accessibilityDescription: "TagLauncher"
             )
-            button.toolTip = "Apptag — Tag-based app launcher"
+            button.toolTip = "TagLauncher — Tag-based app launcher"
             button.action = #selector(toggleOverlay)
             button.target = self
         }
@@ -216,41 +216,10 @@
             NSMouseInRect(mousePoint, $0.frame, false)
         }) ?? NSScreen.main ?? NSScreen.screens.first else { return }
 
-        if overlayWindow == nil {
-            let panel = OverlayPanel(
-                contentRect: screen.frame,
-                styleMask: [.borderless, .fullSizeContentView, .nonactivatingPanel],
-                backing: .buffered,
-                defer: false
-            )
-            panel.isFloatingPanel = true
-            panel.hidesOnDeactivate = false
-            overlayWindow = panel
-            overlayWindow?.collectionBehavior = [
-                .canJoinAllSpaces,
-                .fullScreenAuxiliary,
-                .stationary,
-                .transient,
-                .ignoresCycle
-            ]
-            overlayWindow?.isOpaque = false
-            overlayWindow?.backgroundColor = .clear
-            overlayWindow?.hasShadow = false
-            overlayWindow?.titlebarAppearsTransparent = true
-            overlayWindow?.titleVisibility = .hidden
-            overlayWindow?.isReleasedWhenClosed = false
-
-            overlayWindow?.contentView = DismissibleHostingView(
-                rootView: ContentView(hideOverlay: { [weak self] in
-                    self?.hideOverlay()
-                }),
-                onBackdropTap: { [weak self] in
-                    self?.hideOverlay()
-                }
-            )
-        }
+        overlayWindow?.orderOut(nil)
+        overlayWindow = makeOverlayWindow(on: screen)
         overlayWindow?.setFrame(screen.frame, display: true)
-        overlayWindow?.level = .screenSaver
+        overlayWindow?.level = NSWindow.Level(rawValue: Int(CGWindowLevelForKey(.maximumWindow)))
 
         // Local key monitor: catch Escape while overlay is up
         NSEvent.addLocalMonitorForEvents(matching: .keyDown) { [weak self] event in
@@ -263,12 +232,47 @@
 
         overlayWindow?.makeKeyAndOrderFront(nil)
         overlayWindow?.orderFrontRegardless()
-        NSApp.activate(ignoringOtherApps: true)
     }
 
-    private func hideOverlay() {
-        guard !isInEditMode else { return }
+    private func makeOverlayWindow(on screen: NSScreen) -> NSWindow {
+        let panel = OverlayPanel(
+            contentRect: screen.frame,
+            styleMask: [.borderless, .fullSizeContentView, .nonactivatingPanel],
+            backing: .buffered,
+            defer: false
+        )
+        panel.isFloatingPanel = true
+        panel.hidesOnDeactivate = false
+        panel.collectionBehavior = [
+            .moveToActiveSpace,
+            .fullScreenAuxiliary,
+            .stationary,
+            .transient,
+            .ignoresCycle
+        ]
+        panel.isOpaque = false
+        panel.backgroundColor = .clear
+        panel.hasShadow = false
+        panel.titlebarAppearsTransparent = true
+        panel.titleVisibility = .hidden
+        panel.isReleasedWhenClosed = false
+        panel.contentView = DismissibleHostingView(
+            rootView: ContentView(hideOverlay: { [weak self] in
+                self?.hideOverlay(force: true)
+            }),
+            onBackdropTap: { [weak self] in
+                self?.hideOverlay()
+            }
+        )
+        return panel
+    }
+
+    private func hideOverlay(force: Bool = false) {
+        guard force || !isInEditMode else { return }
         overlayWindow?.orderOut(nil)
+        if force {
+            overlayWindow = nil
+        }
     }
 
     // MARK: - Global Hotkey (Shift+Option+Space)
@@ -293,7 +297,7 @@
         hotkeyRef = ref
 
         if status != noErr {
-            print("[Apptag] Hotkey registration failed: \(status). Falling back to menu bar only.")
+            print("[TagLauncher] Hotkey registration failed: \(status). Falling back to menu bar only.")
             return
         }
 
@@ -341,19 +345,26 @@
 
             // Settings/Preferences window → float it above overlay for real-time preview
             if NSApp.windows.contains(keyWindow) {
-                if self.overlayWindow?.isVisible == true {
-                    keyWindow.level = .popUpMenu
-                }
-                if self.settingsWindow == nil {
-                    keyWindow.minSize = NSSize(width: 660, height: 380)
-                    keyWindow.maxSize = NSSize(width: 660, height: CGFloat.greatestFiniteMagnitude)
-                }
-                self.settingsWindow = keyWindow
+                self.prepareSettingsWindow(keyWindow)
                 return
             }
 
             self.hideOverlay()
         }
+    }
+
+    /// Settings must always float above the overlay so changes can be previewed live.
+    private func prepareSettingsWindow(_ window: NSWindow) {
+        if overlayWindow?.isVisible == true {
+            let overlayLevel = overlayWindow?.level.rawValue ?? NSWindow.Level.screenSaver.rawValue
+            window.level = NSWindow.Level(rawValue: overlayLevel + 1)
+            window.orderFrontRegardless()
+        }
+        if settingsWindow == nil {
+            window.minSize = NSSize(width: 660, height: 380)
+            window.maxSize = NSSize(width: 660, height: CGFloat.greatestFiniteMagnitude)
+        }
+        settingsWindow = window
     }
 
     /// Clean up settingsWindow reference when the Settings window closes.
@@ -374,7 +385,7 @@
     /// Track whether the overlay is in edit mode to suppress auto-dismiss.
     private func observeEditMode() {
         NotificationCenter.default.addObserver(
-            forName: .apptagEditModeChanged,
+            forName: .tagLauncherEditModeChanged,
             object: nil,
             queue: .main
         ) { [weak self] notification in
@@ -384,8 +395,13 @@
 
     @objc private func openPreferences() {
         // Don't hide overlay — keep it visible for real-time setting preview.
-        // observeOtherWindows handles raising the Settings window above the overlay.
         NSApp.sendAction(Selector(("showSettingsWindow:")), to: nil, from: nil)
+        DispatchQueue.main.async { [weak self] in
+            guard let self else { return }
+            for window in NSApp.windows where window != self.overlayWindow {
+                self.prepareSettingsWindow(window)
+            }
+        }
     }
 
     @objc private func switchLanguage(_ sender: NSMenuItem) {
@@ -499,14 +515,14 @@
     private func exportTags() {
         let panel = NSSavePanel()
         panel.title = tr("settings.export")
-        panel.nameFieldStringValue = "Apptag-tags.json"
+        panel.nameFieldStringValue = "TagLauncher-tags.json"
         panel.allowedContentTypes = [.json]
         panel.begin { response in
             guard response == .OK, let url = panel.url else { return }
             do {
                 try TagDatabase.exportTo(url)
             } catch {
-                fputs("[Apptag] Export failed: \(error)\n", stderr)
+                fputs("[TagLauncher] Export failed: \(error)\n", stderr)
             }
         }
     }
@@ -522,7 +538,7 @@
                 _ = try TagDatabase.importFrom(url)
                 scanApps()
             } catch {
-                fputs("[Apptag] Import failed: \(error)\n", stderr)
+                fputs("[TagLauncher] Import failed: \(error)\n", stderr)
                 // Show alert on failure
                 let alert = NSAlert()
                 alert.messageText = tr("settings.importFailed")
@@ -573,9 +589,12 @@
                             Picker("", selection: $displayMode) {
                                 Text(tr("settings.flat")).tag("flat")
                                 Text(tr("settings.container")).tag("container")
+                                Text(tr("settings.coloredContainer")).tag("coloredContainer")
+                                Text(tr("settings.gridContainer")).tag("gridContainer")
+                                Text(tr("settings.coloredGridContainer")).tag("coloredGridContainer")
                             }
                             .pickerStyle(.segmented)
-                            .frame(width: 280, alignment: .leading)
+                            .frame(width: 520, alignment: .leading)
                             Text(tr("settings.flatDesc"))
                                 .font(.caption).foregroundStyle(.secondary)
                                 .fixedSize(horizontal: false, vertical: true)
@@ -675,7 +694,7 @@
                                 .frame(width: 128, height: 128)
                         }
                         VStack(alignment: .leading, spacing: 4) {
-                            Text("Apptag")
+                            Text("TagLauncher")
                                 .font(.title2)
                                 .fontWeight(.semibold)
                             Text(tr("app.description"))

--
Gitblit v1.9.3