aboutsummaryrefslogtreecommitdiff
path: root/QuickLook/PreviewViewController.swift
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/PreviewViewController.swift
parent8c74a32ef5aade1960b056950e52f2f3273db8c7 (diff)
Add QuickLook plugin
Diffstat (limited to 'QuickLook/PreviewViewController.swift')
-rw-r--r--QuickLook/PreviewViewController.swift131
1 files changed, 131 insertions, 0 deletions
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)
+ }
+ }
+}