From cbd2e4c4849656c49377c59aaa70cdb1b592e93f Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sat, 6 Jul 2024 20:44:19 +0700 Subject: [PATCH] wip: fix grid line --- Memola.xcodeproj/project.pbxproj | 8 ++ .../Buffers/Vertices/PointGridVertex.swift | 4 + Memola/Canvas/Core/Canvas.swift | 7 +- Memola/Canvas/Core/Renderer.swift | 3 +- Memola/Canvas/Tool/Core/Tool.swift | 10 ++- .../ViewController/CanvasViewController.swift | 89 ++++++++++++++++--- .../View/Bridge/Views/CenterClipView.swift | 25 ++++++ Memola/Memola.entitlements | 5 ++ Memola/Utilies/Extensions/Image++.swift | 53 +++++++++++ 9 files changed, 184 insertions(+), 20 deletions(-) create mode 100644 Memola/Canvas/View/Bridge/Views/CenterClipView.swift create mode 100644 Memola/Memola.entitlements diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index b8a89fa..3dd6735 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -119,6 +119,7 @@ ECF7B2E02C39169C004D2C57 /* Image++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2CD2C39169C004D2C57 /* Image++.swift */; }; ECF7B2E12C39169C004D2C57 /* View++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2CE2C39169C004D2C57 /* View++.swift */; }; ECF7B2E42C39174D004D2C57 /* Platform.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2E32C39174D004D2C57 /* Platform.swift */; }; + ECF7B2E72C39544E004D2C57 /* CenterClipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2E62C39544E004D2C57 /* CenterClipView.swift */; }; ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA151F2BEF21EF00455818 /* MemoObject.swift */; }; ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA15212BEF21F500455818 /* CanvasObject.swift */; }; ECFA15242BEF223300455818 /* GraphicContextObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA15232BEF223300455818 /* GraphicContextObject.swift */; }; @@ -242,6 +243,8 @@ ECF7B2CD2C39169C004D2C57 /* Image++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Image++.swift"; sourceTree = ""; }; ECF7B2CE2C39169C004D2C57 /* View++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View++.swift"; sourceTree = ""; }; ECF7B2E32C39174D004D2C57 /* Platform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Platform.swift; sourceTree = ""; }; + ECF7B2E62C39544E004D2C57 /* CenterClipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CenterClipView.swift; sourceTree = ""; }; + ECF7B2E82C395A8E004D2C57 /* Memola.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Memola.entitlements; sourceTree = ""; }; ECFA151F2BEF21EF00455818 /* MemoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoObject.swift; sourceTree = ""; }; ECFA15212BEF21F500455818 /* CanvasObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanvasObject.swift; sourceTree = ""; }; ECFA15232BEF223300455818 /* GraphicContextObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicContextObject.swift; sourceTree = ""; }; @@ -320,6 +323,7 @@ isa = PBXGroup; children = ( ECA738AC2BE60CC600A4542E /* DrawingView.swift */, + ECF7B2E62C39544E004D2C57 /* CenterClipView.swift */, ); path = Views; sourceTree = ""; @@ -455,6 +459,7 @@ EC7F6BEA2BE5E6E300A34A7B /* Memola */ = { isa = PBXGroup; children = ( + ECF7B2E82C395A8E004D2C57 /* Memola.entitlements */, ECA738762BE5EE4E00A4542E /* App */, ECA7387E2BE5FE4200A4542E /* Canvas */, EC50500A2BF6672000B4D86E /* Components */, @@ -1053,6 +1058,7 @@ ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */, ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */, ECA738DC2BE6108D00A4542E /* StrokeRenderPass.swift in Sources */, + ECF7B2E72C39544E004D2C57 /* CenterClipView.swift in Sources */, ECF7B2D22C39169C004D2C57 /* CGFloat++.swift in Sources */, EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */, ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */, @@ -1235,6 +1241,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Memola/Memola.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\""; @@ -1271,6 +1278,7 @@ buildSettings = { ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; + CODE_SIGN_ENTITLEMENTS = Memola/Memola.entitlements; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\""; diff --git a/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift b/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift index 4861397..ccae5af 100644 --- a/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift +++ b/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift @@ -10,7 +10,11 @@ import Foundation struct PointGridVertex { var position: vector_float4 + #if os(macOS) + var pointSize: Float = 256 + #else var pointSize: Float = 10 + #endif } extension PointGridVertex { diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index f1546b5..8ab5e9f 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -86,7 +86,7 @@ extension Canvas { state = .closing let previewImage = renderer?.drawPreview(on: self) #if os(macOS) - #warning("TODO: implement for macos") + memoObject.preview = previewImage?.tiffRepresentation #else memoObject.preview = previewImage?.jpegData(compressionQuality: 0.8) #endif @@ -109,7 +109,12 @@ extension Canvas { func updateTransform(on drawingView: DrawingView) { let bounds = CGRect(origin: .zero, size: size) let renderView = drawingView.renderView + #if os(macOS) + let drawingViewBounds = CGRect(origin: .zero, size: drawingView.bounds.size.multiply(by: zoomScale)) + let targetRect = drawingView.convert(drawingViewBounds, to: renderView) + #else let targetRect = drawingView.convert(drawingView.bounds, to: renderView) + #endif let transform1 = bounds.transform(to: targetRect) let transform2 = renderView.bounds.transform(to: CGRect(x: -1.0, y: -1.0, width: 2.0, height: 2.0)) let transform3 = CGAffineTransform.identity.translatedBy(x: 0, y: 1).scaledBy(x: 1, y: -1).translatedBy(x: 0, y: 1) diff --git a/Memola/Canvas/Core/Renderer.swift b/Memola/Canvas/Core/Renderer.swift index 7d43ceb..b1434bb 100644 --- a/Memola/Canvas/Core/Renderer.swift +++ b/Memola/Canvas/Core/Renderer.swift @@ -125,8 +125,7 @@ final class Renderer { return nil } #if os(macOS) - #warning("TODO: implement here") - return nil + return NSImage(cgImage: cgImage, size: .init(width: CGFloat(cgImage.width), height: CGFloat(cgImage.height))).flipped(flipVertically: true) #else return UIImage(cgImage: cgImage, scale: 1.0, orientation: .downMirrored) #endif diff --git a/Memola/Canvas/Tool/Core/Tool.swift b/Memola/Canvas/Tool/Core/Tool.swift index cb906f7..8a413b3 100644 --- a/Memola/Canvas/Tool/Core/Tool.swift +++ b/Memola/Canvas/Tool/Core/Tool.swift @@ -148,7 +148,12 @@ public class Tool: NSObject, ObservableObject { let rect = CGRect(origin: .zero, size: targetSize) #if os(macOS) - return (image, dimension) + let newImage = NSImage(size: rect.size, flipped: false) { destRect in + NSGraphicsContext.current?.imageInterpolation = .high + image.draw(in: destRect, from: NSZeroRect, operation: .copy, fraction: 1) + return true + } + return (newImage, dimension) #else UIGraphicsBeginImageContextWithOptions(targetSize, true, 1.0) image.draw(in: rect) @@ -163,8 +168,7 @@ public class Tool: NSObject, ObservableObject { private func bookmarkPhoto(of image: Platform.Image, and previewImage: Platform.Image, in dimension: CGSize, with canvasID: NSManagedObjectID) -> PhotoItem? { #if os(macOS) - #warning("TODO: implement for macos") - let data = Data() + guard let data = image.tiffRepresentation else { return nil } #else guard let data = image.jpegData(compressionQuality: 1) else { return nil } #endif diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index c1342d4..81e482f 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -48,10 +48,14 @@ class CanvasViewController: Platform.ViewController { } #if os(macOS) - override func viewWillAppear() { - super.viewWillAppear() + override func viewWillLayout() { + super.viewWillLayout() resizeDocumentView() updateDocumentBounds() + } + + override func viewWillAppear() { + super.viewWillAppear() loadMemo() } @@ -95,9 +99,11 @@ class CanvasViewController: Platform.ViewController { extension CanvasViewController { func configureViews() { #if os(macOS) - #warning("TODO: implement for macos") + view.wantsLayer = true + view.layer?.backgroundColor = NSColor.white.cgColor #else view.backgroundColor = .white + #endif renderView.autoResizeDrawable = false renderView.enableSetNeedsDisplay = true @@ -111,6 +117,15 @@ extension CanvasViewController { renderView.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) + #if os(macOS) + scrollView.maxMagnification = canvas.maximumZoomScale + scrollView.minMagnification = canvas.minimumZoomScale + scrollView.hasVerticalScroller = true + scrollView.hasHorizontalScroller = true + scrollView.allowsMagnification = true + scrollView.drawsBackground = false + scrollView.scrollerKnobStyle = .dark + #else scrollView.maximumZoomScale = canvas.maximumZoomScale scrollView.minimumZoomScale = canvas.minimumZoomScale scrollView.contentInsetAdjustmentBehavior = .never @@ -119,6 +134,7 @@ extension CanvasViewController { scrollView.showsHorizontalScrollIndicator = true scrollView.delegate = self scrollView.backgroundColor = .clear + #endif scrollView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(scrollView) @@ -129,6 +145,11 @@ extension CanvasViewController { scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor) ]) + #if os(macOS) + scrollView.contentView = CenterClipView() + scrollView.contentView.drawsBackground = false + scrollView.documentView = drawingView + #else scrollView.addSubview(drawingView) drawingView.backgroundColor = .clear drawingView.isUserInteractionEnabled = false @@ -137,10 +158,10 @@ extension CanvasViewController { func resizeDocumentView(to newSize: CGSize? = nil) { #if os(macOS) - #warning("TODO: implement for macos") + scrollView.layoutSubtreeIfNeeded() #else scrollView.layoutIfNeeded() - + #endif let size = canvas.size let widthScale = (newSize?.width ?? view.frame.width) / size.width let heightScale = (newSize?.height ?? view.frame.height) / size.height @@ -151,36 +172,42 @@ extension CanvasViewController { let newFrame = CGRect(x: 0, y: 0, width: width, height: height) drawingView.frame = newFrame + #if os(macOS) + let rect = scrollView.documentVisibleRect + let center = NSPoint(x: rect.midX, y: rect.midY) + scrollView.setMagnification(canvas.defaultZoomScale, centeredAt: center) + #else scrollView.setZoomScale(canvas.defaultZoomScale, animated: true) centerDocumentView(to: newSize) + #endif + #if os(iOS) let offsetX = (newFrame.width * canvas.defaultZoomScale - view.frame.width) / 2 let offsetY = (newFrame.height * canvas.defaultZoomScale - view.frame.height) / 2 let point = CGPoint(x: offsetX, y: offsetY) scrollView.setContentOffset(point, animated: true) - - drawingView.updateDrawableSize(with: view.frame.size) #endif + drawingView.updateDrawableSize(with: view.frame.size) + NSLog("[Memola] - drawingView: \(drawingView.frame.size.multiply(by: scrollView.magnification)), scrollView: \(scrollView.frame), renderView: \(renderView.frame)") } + #if os(iOS) func centerDocumentView(to newSize: CGSize? = nil) { - #if os(macOS) - #warning("TODO: implement for macos") - #else let documentViewSize = drawingView.frame.size let scrollViewSize = newSize ?? view.frame.size let verticalPadding = documentViewSize.height < scrollViewSize.height ? (scrollViewSize.height - documentViewSize.height) / 2 : 0 let horizontalPadding = documentViewSize.width < scrollViewSize.width ? (scrollViewSize.width - documentViewSize.width) / 2 : 0 self.scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding) - #endif } + #endif func updateDocumentBounds() { #if os(macOS) - #warning("TODO: implement for macos") + var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.magnification) #else var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.zoomScale) + #endif let xDelta = bounds.minX * 0.0 let yDelta = bounds.minY * 0.0 bounds.origin.x -= xDelta @@ -191,12 +218,33 @@ extension CanvasViewController { if canvas.state == .loaded { canvas.loadStrokes(bounds) } - #endif } } extension CanvasViewController { func configureListeners() { + #if os(macOS) + NotificationCenter.default.publisher(for: NSScrollView.didEndLiveMagnifyNotification, object: scrollView) + .sink { [weak self] _ in + self?.magnificationEnded() + } + .store(in: &cancellables) + NotificationCenter.default.publisher(for: NSScrollView.willStartLiveMagnifyNotification, object: scrollView) + .sink { [weak self] _ in + self?.magnificationStarted() + } + .store(in: &cancellables) + NotificationCenter.default.publisher(for: NSScrollView.willStartLiveScrollNotification, object: scrollView) + .sink { [weak self] _ in + self?.draggingStarted() + } + .store(in: &cancellables) + NotificationCenter.default.publisher(for: NSScrollView.didEndLiveScrollNotification, object: scrollView) + .sink { [weak self] _ in + self?.draggingEnded() + } + .store(in: &cancellables) + #endif canvas.$state .sink { [weak self] state in self?.canvasStateChanged(state) @@ -344,6 +392,9 @@ extension CanvasViewController: UIScrollViewDelegate { extension CanvasViewController { func magnificationStarted() { + #if os(macOS) + canvas.setZoomScale(scrollView.magnification) + #endif guard !renderer.updatesViewPort else { return } drawingView.touchCancelled() canvas.updateClipBounds(scrollView, on: drawingView) @@ -352,6 +403,9 @@ extension CanvasViewController { } func magnificationEnded() { + #if os(macOS) + canvas.setZoomScale(scrollView.magnification) + #endif renderer.setUpdatesViewPort(false) renderer.setRedrawsGraphicRender() renderView.draw() @@ -359,6 +413,9 @@ extension CanvasViewController { } func draggingStarted() { + #if os(macOS) + canvas.setZoomScale(scrollView.magnification) + #endif guard !renderer.updatesViewPort else { return } canvas.updateClipBounds(scrollView, on: drawingView) drawingView.disableUserInteraction() @@ -366,6 +423,9 @@ extension CanvasViewController { } func draggingEnded() { + #if os(macOS) + canvas.setZoomScale(scrollView.magnification) + #endif renderer.setUpdatesViewPort(false) renderer.setRedrawsGraphicRender() renderView.draw() @@ -413,7 +473,8 @@ extension CanvasViewController { extension CanvasViewController { func zoomChanged(_ zoomScale: CGFloat) { #if os(macOS) - #warning("TODO: implement for macos") + let rect = scrollView.documentVisibleRect + scrollView.setMagnification(zoomScale, centeredAt: CGPoint(x: rect.midX, y: rect.midY)) #else scrollView.setZoomScale(zoomScale, animated: true) #endif diff --git a/Memola/Canvas/View/Bridge/Views/CenterClipView.swift b/Memola/Canvas/View/Bridge/Views/CenterClipView.swift new file mode 100644 index 0000000..1a4ba12 --- /dev/null +++ b/Memola/Canvas/View/Bridge/Views/CenterClipView.swift @@ -0,0 +1,25 @@ +// +// CenterClipView.swift +// Memola +// +// Created by Dscyre Scotti on 7/6/24. +// + +#if canImport(AppKit) +import AppKit + +class CenterClipView: NSClipView { + override func constrainBoundsRect(_ proposedBounds: NSRect) -> NSRect { + var rect = super.constrainBoundsRect(proposedBounds) + if let containerView = self.documentView { + if (rect.size.width > containerView.frame.size.width) { + rect.origin.x = (containerView.frame.width - rect.width) / 2 + } + if(rect.size.height > containerView.frame.size.height) { + rect.origin.y = (containerView.frame.height - rect.height) / 2 + } + } + return rect + } +} +#endif diff --git a/Memola/Memola.entitlements b/Memola/Memola.entitlements new file mode 100644 index 0000000..0c67376 --- /dev/null +++ b/Memola/Memola.entitlements @@ -0,0 +1,5 @@ + + + + + diff --git a/Memola/Utilies/Extensions/Image++.swift b/Memola/Utilies/Extensions/Image++.swift index 0f9fc16..2c420c7 100644 --- a/Memola/Utilies/Extensions/Image++.swift +++ b/Memola/Utilies/Extensions/Image++.swift @@ -18,6 +18,59 @@ extension Image { } } +#if os(macOS) +extension NSImage { + func upsideDownMirrored() -> NSImage { + let degrees: CGFloat = 180 + let sinDegrees = abs(sin(degrees * CGFloat.pi / 180.0)) + let cosDegrees = abs(cos(degrees * CGFloat.pi / 180.0)) + let newSize = CGSize( + width: size.height * sinDegrees + size.width * cosDegrees, + height: size.width * sinDegrees + size.height * cosDegrees + ) + + let imageBounds = NSRect( + x: (newSize.width - size.width) / 2, + y: (newSize.height - size.height) / 2, + width: size.width, + height: size.height + ) + + let otherTransform = NSAffineTransform() + otherTransform.translateX(by: newSize.width / 2, yBy: newSize.height / 2) + otherTransform.rotate(byDegrees: degrees) + otherTransform.translateX(by: -newSize.width / 2, yBy: -newSize.height / 2) + + let rotatedImage = NSImage(size: newSize) + rotatedImage.lockFocus() + otherTransform.concat() + draw(in: imageBounds, from: CGRect.zero, operation: NSCompositingOperation.copy, fraction: 1.0) + rotatedImage.unlockFocus() + + return rotatedImage + } + + func flipped(flipHorizontally: Bool = false, flipVertically: Bool = false) -> NSImage { + let flippedImage = NSImage(size: size) + + flippedImage.lockFocus() + + NSGraphicsContext.current?.imageInterpolation = .high + + let transform = NSAffineTransform() + transform.translateX(by: flipHorizontally ? size.width : 0, yBy: flipVertically ? size.height : 0) + transform.scaleX(by: flipHorizontally ? -1 : 1, yBy: flipVertically ? -1 : 1) + transform.concat() + + draw(at: .zero, from: NSRect(origin: .zero, size: size), operation: .sourceOver, fraction: 1) + + flippedImage.unlockFocus() + + return flippedImage + } +} +#endif + #if os(iOS) extension UIImage { func imageWithUpOrientation() -> UIImage? {