aboutsummaryrefslogtreecommitdiff
path: root/QuickLook
diff options
context:
space:
mode:
authorRuben Beltran del Rio <git@r.bdr.sh>2025-07-11 12:06:04 +0200
committerRuben Beltran del Rio <git@r.bdr.sh>2025-07-11 12:06:04 +0200
commit0cc8ac9cc39ce3a1bcc9ea88345d18f17a46cb2a (patch)
tree97b0f4e6a38040e73412a0981c43c29b473abcd9 /QuickLook
parent8c74a32ef5aade1960b056950e52f2f3273db8c7 (diff)
Add QuickLook plugin
Diffstat (limited to 'QuickLook')
-rw-r--r--QuickLook/Base.lproj/PreviewViewController.xib21
-rw-r--r--QuickLook/Info.plist26
-rw-r--r--QuickLook/PreviewViewController.swift131
-rw-r--r--QuickLook/QuickLook.entitlements10
4 files changed, 188 insertions, 0 deletions
diff --git a/QuickLook/Base.lproj/PreviewViewController.xib b/QuickLook/Base.lproj/PreviewViewController.xib
new file mode 100644
index 0000000..d8dd0f3
--- /dev/null
+++ b/QuickLook/Base.lproj/PreviewViewController.xib
@@ -0,0 +1,21 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<document type="com.apple.InterfaceBuilder3.Cocoa.XIB" version="3.0" toolsVersion="11762" targetRuntime="MacOSX.Cocoa" propertyAccessControl="none" useAutolayout="YES" customObjectInstantitationMethod="direct">
+ <dependencies>
+ <deployment identifier="macosx"/>
+ <plugIn identifier="com.apple.InterfaceBuilder.CocoaPlugin" version="11762"/>
+ <capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
+ </dependencies>
+ <objects>
+ <customObject id="-2" userLabel="File's Owner" customClass="PreviewViewController" customModuleProvider="target">
+ <connections>
+ <outlet property="view" destination="c22-O7-iKe" id="NRM-P4-wb6"/>
+ </connections>
+ </customObject>
+ <customObject id="-1" userLabel="First Responder" customClass="FirstResponder"/>
+ <customObject id="-3" userLabel="Application" customClass="NSObject"/>
+ <customView id="c22-O7-iKe" userLabel="Preview View">
+ <rect key="frame" x="0.0" y="0.0" width="480" height="272"/>
+ <autoresizingMask key="autoresizingMask" flexibleMaxX="YES" flexibleMinY="YES"/>
+ </customView>
+ </objects>
+</document>
diff --git a/QuickLook/Info.plist b/QuickLook/Info.plist
new file mode 100644
index 0000000..38643ce
--- /dev/null
+++ b/QuickLook/Info.plist
@@ -0,0 +1,26 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>ATSApplicationFontsPath</key>
+ <string>Fonts</string>
+ <key>NSExtension</key>
+ <dict>
+ <key>NSExtensionAttributes</key>
+ <dict>
+ <key>QLIsDataBasedPreview</key>
+ <false/>
+ <key>QLSupportedContentTypes</key>
+ <array>
+ <string>systems.tranquil.map.wmap</string>
+ </array>
+ <key>QLSupportsSearchableItems</key>
+ <false/>
+ </dict>
+ <key>NSExtensionPointIdentifier</key>
+ <string>com.apple.quicklook.preview</string>
+ <key>NSExtensionPrincipalClass</key>
+ <string>$(PRODUCT_MODULE_NAME).PreviewViewController</string>
+ </dict>
+</dict>
+</plist>
diff --git a/QuickLook/PreviewViewController.swift b/QuickLook/PreviewViewController.swift
new file mode 100644
index 0000000..03efe72
--- /dev/null
+++ b/QuickLook/PreviewViewController.swift
@@ -0,0 +1,131 @@
+//
+// PreviewViewController.swift
+// QuickLook
+//
+// Created by Ruben Beltran del Rio on 2025-07-11.
+//
+
+import Cocoa
+import Quartz
+import SwiftUI
+
+class PreviewViewController: NSViewController, QLPreviewingController {
+
+ override var nibName: NSNib.Name? {
+ return NSNib.Name("PreviewViewController")
+ }
+
+ override func loadView() {
+ super.loadView()
+ }
+
+ func preparePreviewOfFile(at url: URL) async throws {
+ // Read the .wmap file content
+ let content = try String(contentsOf: url, encoding: .utf8)
+
+ // Parse the content to create a ParsedMap
+ let lexer = Wmap.Lexer(content)
+ let parser = Wmap.Parser(lexer: lexer)
+ let parsedMap = parser.parse()
+
+ await MainActor.run {
+ // Clear any existing subviews and force cleanup
+ view.subviews.forEach { subview in
+ if let hostingView = subview as? NSHostingView<AnyView> {
+ hostingView.rootView = AnyView(EmptyView())
+ }
+ subview.removeFromSuperview()
+ }
+
+ // Force memory cleanup
+ autoreleasepool {
+ // Create the map preview view with error handling
+ let mapPreview: AnyView
+ if parsedMap.entities.isEmpty {
+ // Fallback view for empty or invalid maps
+ mapPreview = AnyView(
+ VStack {
+ Image(systemName: "map")
+ .font(.system(size: 48))
+ .foregroundColor(.gray)
+ Text("quick_look.empty_map.title")
+ .font(.Theme.Title.emphasized)
+ .foregroundColor(.Theme.UI.foreground)
+ Text("quick_look.empty_map.description")
+ .font(.Theme.Body.regular)
+ .foregroundColor(.Theme.UI.foreground)
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .foregroundColor(.Theme.UI.background)
+ )
+ } else {
+ // Limit entities for memory efficiency in QuickLook
+ let limitedEntities = Array(parsedMap.entities.prefix(100)) // Limit to first 100 entities
+ let limitedMap = Wmap.ParsedMap(
+ entities: limitedEntities, vertexLabels: parsedMap.vertexLabels)
+ mapPreview = AnyView(MapPreviewView(parsedMap: limitedMap))
+ }
+
+ let hostingView = NSHostingView(rootView: mapPreview)
+
+ hostingView.translatesAutoresizingMaskIntoConstraints = false
+ view.addSubview(hostingView)
+
+ NSLayoutConstraint.activate([
+ hostingView.topAnchor.constraint(equalTo: view.topAnchor),
+ hostingView.leadingAnchor.constraint(equalTo: view.leadingAnchor),
+ hostingView.trailingAnchor.constraint(equalTo: view.trailingAnchor),
+ hostingView.bottomAnchor.constraint(equalTo: view.bottomAnchor),
+ ])
+
+ view.needsLayout = true
+ view.layoutSubtreeIfNeeded()
+ } // autoreleasepool
+ }
+ }
+
+ deinit {
+ // Cleanup when view controller is deallocated
+ view.subviews.forEach { subview in
+ if let hostingView = subview as? NSHostingView<AnyView> {
+ hostingView.rootView = AnyView(EmptyView())
+ }
+ subview.removeFromSuperview()
+ }
+ }
+}
+
+// A SwiftUI view specifically for QuickLook preview
+struct MapPreviewView: View {
+ let parsedMap: Wmap.ParsedMap
+ @State private var selectedEvolution: StageType = .general
+
+ var body: some View {
+ GeometryReader { geometry in
+ let mapSize = Dimensions.Map.size
+ let padding = Dimensions.Map.padding
+ let totalWidth = mapSize.width + 2 * padding
+ let totalHeight = mapSize.height + 2 * padding
+
+ // Calculate scale to fit the available space
+ let scaleX = geometry.size.width / totalWidth
+ let scaleY = geometry.size.height / totalHeight
+ let scale = min(scaleX, scaleY, 1.0) // Don't scale up, only down
+
+ ScrollView([.horizontal, .vertical]) {
+ MapRenderView(
+ entities: parsedMap.entities,
+ evolution: $selectedEvolution
+ )
+ .drawingGroup(opaque: false)
+ .scaleEffect(scale)
+ .frame(
+ width: totalWidth * scale,
+ height: totalHeight * scale
+ )
+ }
+ .frame(maxWidth: .infinity, maxHeight: .infinity)
+ .background(Color.white)
+ }
+ }
+}
diff --git a/QuickLook/QuickLook.entitlements b/QuickLook/QuickLook.entitlements
new file mode 100644
index 0000000..f2ef3ae
--- /dev/null
+++ b/QuickLook/QuickLook.entitlements
@@ -0,0 +1,10 @@
+<?xml version="1.0" encoding="UTF-8"?>
+<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
+<plist version="1.0">
+<dict>
+ <key>com.apple.security.app-sandbox</key>
+ <true/>
+ <key>com.apple.security.files.user-selected.read-only</key>
+ <true/>
+</dict>
+</plist>