From 15e96136f1b12e954a3ebeff39f82747a103d55b Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sat, 6 Jul 2024 14:20:31 +0700 Subject: [PATCH 01/22] feat: add macos runtime --- Memola.xcodeproj/project.pbxproj | 188 ++++++++++-------- Memola/App/MemolaApp.swift | 2 +- Memola/Canvas/Core/Canvas.swift | 6 +- Memola/Canvas/Core/Renderer.swift | 7 +- Memola/Canvas/Elements/Photo/Photo.swift | 2 +- Memola/Canvas/Tool/Core/Tool.swift | 15 +- .../ViewController/CanvasViewController.swift | 69 ++++++- .../View/Bridge/Views/DrawingView.swift | 10 +- Memola/Canvas/View/Canvas/CanvasView.swift | 10 +- .../Views/CameraView/CameraView.swift | 2 + .../Dashboard/Dashboard/DashboardView.swift | 4 + .../Dashboard/Details/Memos/MemosView.swift | 6 + .../Dashboard/Details/Shared/MemoGrid.swift | 6 +- .../Details/Shared/MemoPreview.swift | 4 +- .../Dashboard/Details/Trash/TrashView.swift | 6 + .../Features/Dashboard/Sidebar/Sidebar.swift | 8 +- .../Memo/ElementToolbar/ElementToolbar.swift | 22 +- Memola/Features/Memo/Memo/MemoView.swift | 2 + Memola/Features/Memo/PenDock/PenDock.swift | 18 ++ .../Memo/PhotoPreview/PhotoItem.swift | 6 +- .../Memo/PhotoPreview/PhotoPreview.swift | 4 +- Memola/Features/Memo/Toolbar/Toolbar.swift | 8 + Memola/{ => Utilies}/Extensions/Array++.swift | 0 .../Extensions/CGAffineTransform++.swift | 0 .../{ => Utilies}/Extensions/CGFloat++.swift | 0 .../{ => Utilies}/Extensions/CGPoint++.swift | 0 .../{ => Utilies}/Extensions/CGRect++.swift | 0 .../{ => Utilies}/Extensions/CGSize++.swift | 0 .../Extensions/Collection++.swift | 0 Memola/{ => Utilies}/Extensions/Color++.swift | 30 ++- Memola/{ => Utilies}/Extensions/Data++.swift | 0 Memola/{ => Utilies}/Extensions/Date++.swift | 0 Memola/{ => Utilies}/Extensions/Float++.swift | 0 .../Extensions/Image++.swift} | 16 +- .../Extensions/MTLDevice++.swift | 0 .../Extensions/MTLTexture++.swift | 0 .../Extensions/NSManagedObject++.swift | 0 .../Extensions/NSManagedObjectContext++.swift | 0 Memola/{ => Utilies}/Extensions/View++.swift | 0 .../Extensions/simd_float4x4++.swift | 0 Memola/Utilies/Platform/Platform.swift | 30 +++ 41 files changed, 367 insertions(+), 114 deletions(-) rename Memola/{ => Utilies}/Extensions/Array++.swift (100%) rename Memola/{ => Utilies}/Extensions/CGAffineTransform++.swift (100%) rename Memola/{ => Utilies}/Extensions/CGFloat++.swift (100%) rename Memola/{ => Utilies}/Extensions/CGPoint++.swift (100%) rename Memola/{ => Utilies}/Extensions/CGRect++.swift (100%) rename Memola/{ => Utilies}/Extensions/CGSize++.swift (100%) rename Memola/{ => Utilies}/Extensions/Collection++.swift (100%) rename Memola/{ => Utilies}/Extensions/Color++.swift (50%) rename Memola/{ => Utilies}/Extensions/Data++.swift (100%) rename Memola/{ => Utilies}/Extensions/Date++.swift (100%) rename Memola/{ => Utilies}/Extensions/Float++.swift (100%) rename Memola/{Extensions/UIImage++.swift => Utilies/Extensions/Image++.swift} (68%) rename Memola/{ => Utilies}/Extensions/MTLDevice++.swift (100%) rename Memola/{ => Utilies}/Extensions/MTLTexture++.swift (100%) rename Memola/{ => Utilies}/Extensions/NSManagedObject++.swift (100%) rename Memola/{ => Utilies}/Extensions/NSManagedObjectContext++.swift (100%) rename Memola/{ => Utilies}/Extensions/View++.swift (100%) rename Memola/{ => Utilies}/Extensions/simd_float4x4++.swift (100%) create mode 100644 Memola/Utilies/Platform/Platform.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 8ce3dd4..b8a89fa 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -21,17 +21,12 @@ EC1815082C2D980B00541369 /* Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1815072C2D980B00541369 /* Sort.swift */; }; EC18150A2C2DA09E00541369 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1815092C2DA09E00541369 /* Filter.swift */; }; EC18150D2C2DAC3700541369 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC18150C2C2DAC3700541369 /* Placeholder.swift */; }; - EC18150F2C2DB13200541369 /* Date++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC18150E2C2DB13200541369 /* Date++.swift */; }; EC1B783D2BFA0AC9005A34E2 /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1B783C2BFA0AC9005A34E2 /* Toolbar.swift */; }; EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */; }; EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */; }; EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF52C0F600D005DB0AF /* Box.swift */; }; EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF72C0F601A005DB0AF /* Node.swift */; }; - EC3565522BEFC65F00A4E0BF /* NSManagedObjectContext++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3565512BEFC65F00A4E0BF /* NSManagedObjectContext++.swift */; }; - EC3565542BEFC6AD00A4E0BF /* View++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3565532BEFC6AD00A4E0BF /* View++.swift */; }; - EC3565562BEFC7B300A4E0BF /* NSManagedObject++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */; }; EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */ = {isa = PBXBuildFile; fileRef = EC3565592BF060D900A4E0BF /* Quad.metal */; }; - EC35655C2BF0712A00A4E0BF /* Float++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC35655B2BF0712A00A4E0BF /* Float++.swift */; }; EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB112C1B2DD90008D976 /* ToolSelection.swift */; }; EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC42F7842C25267000E86E96 /* ElementGroup.swift */; }; EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; }; @@ -61,7 +56,6 @@ ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */; }; ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389D2BE601CB00A4542E /* QuadVertex.swift */; }; ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389F2BE601E400A4542E /* ViewPortVertex.swift */; }; - ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A22BE6020A00A4542E /* CGFloat++.swift */; }; ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A52BE6023F00A4542E /* GridUniforms.swift */; }; ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A72BE6025900A4542E /* GraphicUniforms.swift */; }; ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A92BE6026D00A4542E /* Uniforms.swift */; }; @@ -88,22 +82,11 @@ ECA738E02BE610B900A4542E /* EraserRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */; }; ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */; }; ECA738E42BE6110800A4542E /* Drawable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E32BE6110800A4542E /* Drawable.swift */; }; - ECA738E62BE611FD00A4542E /* CGRect++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E52BE611FD00A4542E /* CGRect++.swift */; }; - ECA738E82BE6120F00A4542E /* Color++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E72BE6120F00A4542E /* Color++.swift */; }; - ECA738EA2BE6122E00A4542E /* CGPoint++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E92BE6122E00A4542E /* CGPoint++.swift */; }; - ECA738EC2BE6124E00A4542E /* CGAffineTransform++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738EB2BE6124E00A4542E /* CGAffineTransform++.swift */; }; - ECA738EE2BE6125D00A4542E /* simd_float4x4++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738ED2BE6125D00A4542E /* simd_float4x4++.swift */; }; - ECA738F02BE6127700A4542E /* CGSize++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738EF2BE6127700A4542E /* CGSize++.swift */; }; - ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F12BE6128F00A4542E /* Collection++.swift */; }; - ECA738F42BE612A000A4542E /* Array++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F32BE612A000A4542E /* Array++.swift */; }; - ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; }; ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; }; ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; }; ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; }; ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE529A2C1D94A4006BDB3D /* CameraView.swift */; }; - ECBE529E2C1DAB21006BDB3D /* UIImage++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE529D2C1DAB21006BDB3D /* UIImage++.swift */; }; ECC995A32C1E8F2800B2699A /* PhotoItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECC995A22C1E8F2800B2699A /* PhotoItem.swift */; }; - ECC995A52C1EB4CC00B2699A /* Data++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECC995A42C1EB4CC00B2699A /* Data++.swift */; }; ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; }; ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; }; ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */; }; @@ -113,11 +96,29 @@ ECD12A952C1B1FA200B96E12 /* PhotoVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A942C1B1FA200B96E12 /* PhotoVertex.swift */; }; ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDAC07A2C318DBC0000ED77 /* ElementToolbar.swift */; }; ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDDD40C2C366B3B00DF9D5E /* PreviewRenderPass.swift */; }; - ECDDD40F2C368B2700DF9D5E /* MTLTexture++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDDD40E2C368B2700DF9D5E /* MTLTexture++.swift */; }; ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BC2C00AA170045C53D /* EraserStroke.swift */; }; ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BE2C00AB440045C53D /* Stroke.swift */; }; ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */; }; ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECEC01A72BEE11BA006DA24C /* QuadShape.swift */; }; + ECF7B2D02C39169C004D2C57 /* Array++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2BD2C39169C004D2C57 /* Array++.swift */; }; + ECF7B2D12C39169C004D2C57 /* CGAffineTransform++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2BE2C39169C004D2C57 /* CGAffineTransform++.swift */; }; + ECF7B2D22C39169C004D2C57 /* CGFloat++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2BF2C39169C004D2C57 /* CGFloat++.swift */; }; + ECF7B2D32C39169C004D2C57 /* CGPoint++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C02C39169C004D2C57 /* CGPoint++.swift */; }; + ECF7B2D42C39169C004D2C57 /* CGRect++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C12C39169C004D2C57 /* CGRect++.swift */; }; + ECF7B2D52C39169C004D2C57 /* CGSize++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C22C39169C004D2C57 /* CGSize++.swift */; }; + ECF7B2D62C39169C004D2C57 /* Collection++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C32C39169C004D2C57 /* Collection++.swift */; }; + ECF7B2D72C39169C004D2C57 /* Color++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C42C39169C004D2C57 /* Color++.swift */; }; + ECF7B2D82C39169C004D2C57 /* Data++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C52C39169C004D2C57 /* Data++.swift */; }; + ECF7B2D92C39169C004D2C57 /* Date++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C62C39169C004D2C57 /* Date++.swift */; }; + ECF7B2DA2C39169C004D2C57 /* Float++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C72C39169C004D2C57 /* Float++.swift */; }; + ECF7B2DB2C39169C004D2C57 /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C82C39169C004D2C57 /* MTLDevice++.swift */; }; + ECF7B2DC2C39169C004D2C57 /* MTLTexture++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2C92C39169C004D2C57 /* MTLTexture++.swift */; }; + ECF7B2DD2C39169C004D2C57 /* NSManagedObject++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2CA2C39169C004D2C57 /* NSManagedObject++.swift */; }; + ECF7B2DE2C39169C004D2C57 /* NSManagedObjectContext++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2CB2C39169C004D2C57 /* NSManagedObjectContext++.swift */; }; + ECF7B2DF2C39169C004D2C57 /* simd_float4x4++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2CC2C39169C004D2C57 /* simd_float4x4++.swift */; }; + 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 */; }; 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 */; }; @@ -141,17 +142,12 @@ EC1815072C2D980B00541369 /* Sort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sort.swift; sourceTree = ""; }; EC1815092C2DA09E00541369 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; EC18150C2C2DAC3700541369 /* Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = ""; }; - EC18150E2C2DB13200541369 /* Date++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date++.swift"; sourceTree = ""; }; EC1B783C2BFA0AC9005A34E2 /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = ""; }; EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyStroke.swift; sourceTree = ""; }; EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; EC2BEBF52C0F600D005DB0AF /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; EC2BEBF72C0F601A005DB0AF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; - EC3565512BEFC65F00A4E0BF /* NSManagedObjectContext++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext++.swift"; sourceTree = ""; }; - EC3565532BEFC6AD00A4E0BF /* View++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View++.swift"; sourceTree = ""; }; - EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObject++.swift"; sourceTree = ""; }; EC3565592BF060D900A4E0BF /* Quad.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Quad.metal; sourceTree = ""; }; - EC35655B2BF0712A00A4E0BF /* Float++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Float++.swift"; sourceTree = ""; }; EC37FB112C1B2DD90008D976 /* ToolSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSelection.swift; sourceTree = ""; }; EC42F7842C25267000E86E96 /* ElementGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementGroup.swift; sourceTree = ""; }; EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = ""; }; @@ -183,7 +179,6 @@ ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointGridVertex.swift; sourceTree = ""; }; ECA7389D2BE601CB00A4542E /* QuadVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadVertex.swift; sourceTree = ""; }; ECA7389F2BE601E400A4542E /* ViewPortVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPortVertex.swift; sourceTree = ""; }; - ECA738A22BE6020A00A4542E /* CGFloat++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat++.swift"; sourceTree = ""; }; ECA738A52BE6023F00A4542E /* GridUniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridUniforms.swift; sourceTree = ""; }; ECA738A72BE6025900A4542E /* GraphicUniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicUniforms.swift; sourceTree = ""; }; ECA738A92BE6026D00A4542E /* Uniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uniforms.swift; sourceTree = ""; }; @@ -210,22 +205,11 @@ ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserRenderPass.swift; sourceTree = ""; }; ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicRenderPass.swift; sourceTree = ""; }; ECA738E32BE6110800A4542E /* Drawable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Drawable.swift; sourceTree = ""; }; - ECA738E52BE611FD00A4542E /* CGRect++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect++.swift"; sourceTree = ""; }; - ECA738E72BE6120F00A4542E /* Color++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color++.swift"; sourceTree = ""; }; - ECA738E92BE6122E00A4542E /* CGPoint++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint++.swift"; sourceTree = ""; }; - ECA738EB2BE6124E00A4542E /* CGAffineTransform++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGAffineTransform++.swift"; sourceTree = ""; }; - ECA738ED2BE6125D00A4542E /* simd_float4x4++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "simd_float4x4++.swift"; sourceTree = ""; }; - ECA738EF2BE6127700A4542E /* CGSize++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize++.swift"; sourceTree = ""; }; - ECA738F12BE6128F00A4542E /* Collection++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection++.swift"; sourceTree = ""; }; - ECA738F32BE612A000A4542E /* Array++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array++.swift"; sourceTree = ""; }; - ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = ""; }; ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = ""; }; ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = ""; }; ECBE529A2C1D94A4006BDB3D /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = ""; }; - ECBE529D2C1DAB21006BDB3D /* UIImage++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage++.swift"; sourceTree = ""; }; ECC995A22C1E8F2800B2699A /* PhotoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoItem.swift; sourceTree = ""; }; - ECC995A42C1EB4CC00B2699A /* Data++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data++.swift"; sourceTree = ""; }; ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = ""; }; ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = ""; }; ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoObject.swift; sourceTree = ""; }; @@ -235,11 +219,29 @@ ECD12A942C1B1FA200B96E12 /* PhotoVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoVertex.swift; sourceTree = ""; }; ECDAC07A2C318DBC0000ED77 /* ElementToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementToolbar.swift; sourceTree = ""; }; ECDDD40C2C366B3B00DF9D5E /* PreviewRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewRenderPass.swift; sourceTree = ""; }; - ECDDD40E2C368B2700DF9D5E /* MTLTexture++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLTexture++.swift"; sourceTree = ""; }; ECE883BC2C00AA170045C53D /* EraserStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserStroke.swift; sourceTree = ""; }; ECE883BE2C00AB440045C53D /* Stroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stroke.swift; sourceTree = ""; }; ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeStyle.swift; sourceTree = ""; }; ECEC01A72BEE11BA006DA24C /* QuadShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadShape.swift; sourceTree = ""; }; + ECF7B2BD2C39169C004D2C57 /* Array++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array++.swift"; sourceTree = ""; }; + ECF7B2BE2C39169C004D2C57 /* CGAffineTransform++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGAffineTransform++.swift"; sourceTree = ""; }; + ECF7B2BF2C39169C004D2C57 /* CGFloat++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat++.swift"; sourceTree = ""; }; + ECF7B2C02C39169C004D2C57 /* CGPoint++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint++.swift"; sourceTree = ""; }; + ECF7B2C12C39169C004D2C57 /* CGRect++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect++.swift"; sourceTree = ""; }; + ECF7B2C22C39169C004D2C57 /* CGSize++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGSize++.swift"; sourceTree = ""; }; + ECF7B2C32C39169C004D2C57 /* Collection++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection++.swift"; sourceTree = ""; }; + ECF7B2C42C39169C004D2C57 /* Color++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Color++.swift"; sourceTree = ""; }; + ECF7B2C52C39169C004D2C57 /* Data++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data++.swift"; sourceTree = ""; }; + ECF7B2C62C39169C004D2C57 /* Date++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date++.swift"; sourceTree = ""; }; + ECF7B2C72C39169C004D2C57 /* Float++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Float++.swift"; sourceTree = ""; }; + ECF7B2C82C39169C004D2C57 /* MTLDevice++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = ""; }; + ECF7B2C92C39169C004D2C57 /* MTLTexture++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MTLTexture++.swift"; sourceTree = ""; }; + ECF7B2CA2C39169C004D2C57 /* NSManagedObject++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject++.swift"; sourceTree = ""; }; + ECF7B2CB2C39169C004D2C57 /* NSManagedObjectContext++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext++.swift"; sourceTree = ""; }; + ECF7B2CC2C39169C004D2C57 /* simd_float4x4++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "simd_float4x4++.swift"; sourceTree = ""; }; + 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 = ""; }; 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 = ""; }; @@ -455,13 +457,13 @@ children = ( ECA738762BE5EE4E00A4542E /* App */, ECA7387E2BE5FE4200A4542E /* Canvas */, - EC5050102BF670EE00B4D86E /* Config */, EC50500A2BF6672000B4D86E /* Components */, - ECA738A12BE601F700A4542E /* Extensions */, + EC5050102BF670EE00B4D86E /* Config */, ECA738772BE5EEE800A4542E /* Features */, ECA738FA2BE61B1700A4542E /* Persistence */, EC7F6BF12BE5E6E400A34A7B /* Preview Content */, ECA738802BE5FE6000A4542E /* Resources */, + ECF7B2E52C391DFA004D2C57 /* Utilies */, ); path = Memola; sourceTree = ""; @@ -613,31 +615,6 @@ path = Vertices; sourceTree = ""; }; - ECA738A12BE601F700A4542E /* Extensions */ = { - isa = PBXGroup; - children = ( - ECA738F32BE612A000A4542E /* Array++.swift */, - ECA738EB2BE6124E00A4542E /* CGAffineTransform++.swift */, - ECA738A22BE6020A00A4542E /* CGFloat++.swift */, - ECA738E92BE6122E00A4542E /* CGPoint++.swift */, - ECA738E52BE611FD00A4542E /* CGRect++.swift */, - ECA738EF2BE6127700A4542E /* CGSize++.swift */, - ECA738F12BE6128F00A4542E /* Collection++.swift */, - ECA738E72BE6120F00A4542E /* Color++.swift */, - ECA738F52BE612B700A4542E /* MTLDevice++.swift */, - ECA738ED2BE6125D00A4542E /* simd_float4x4++.swift */, - EC3565512BEFC65F00A4E0BF /* NSManagedObjectContext++.swift */, - EC3565532BEFC6AD00A4E0BF /* View++.swift */, - EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */, - EC35655B2BF0712A00A4E0BF /* Float++.swift */, - ECBE529D2C1DAB21006BDB3D /* UIImage++.swift */, - ECC995A42C1EB4CC00B2699A /* Data++.swift */, - EC18150E2C2DB13200541369 /* Date++.swift */, - ECDDD40E2C368B2700DF9D5E /* MTLTexture++.swift */, - ); - path = Extensions; - sourceTree = ""; - }; ECA738A42BE6022F00A4542E /* Uniforms */ = { isa = PBXGroup; children = ( @@ -871,6 +848,48 @@ path = Core; sourceTree = ""; }; + ECF7B2CF2C39169C004D2C57 /* Extensions */ = { + isa = PBXGroup; + children = ( + ECF7B2BD2C39169C004D2C57 /* Array++.swift */, + ECF7B2BE2C39169C004D2C57 /* CGAffineTransform++.swift */, + ECF7B2BF2C39169C004D2C57 /* CGFloat++.swift */, + ECF7B2C02C39169C004D2C57 /* CGPoint++.swift */, + ECF7B2C12C39169C004D2C57 /* CGRect++.swift */, + ECF7B2C22C39169C004D2C57 /* CGSize++.swift */, + ECF7B2C32C39169C004D2C57 /* Collection++.swift */, + ECF7B2C42C39169C004D2C57 /* Color++.swift */, + ECF7B2C52C39169C004D2C57 /* Data++.swift */, + ECF7B2C62C39169C004D2C57 /* Date++.swift */, + ECF7B2C72C39169C004D2C57 /* Float++.swift */, + ECF7B2C82C39169C004D2C57 /* MTLDevice++.swift */, + ECF7B2C92C39169C004D2C57 /* MTLTexture++.swift */, + ECF7B2CA2C39169C004D2C57 /* NSManagedObject++.swift */, + ECF7B2CB2C39169C004D2C57 /* NSManagedObjectContext++.swift */, + ECF7B2CC2C39169C004D2C57 /* simd_float4x4++.swift */, + ECF7B2CD2C39169C004D2C57 /* Image++.swift */, + ECF7B2CE2C39169C004D2C57 /* View++.swift */, + ); + path = Extensions; + sourceTree = ""; + }; + ECF7B2E22C39172D004D2C57 /* Platform */ = { + isa = PBXGroup; + children = ( + ECF7B2E32C39174D004D2C57 /* Platform.swift */, + ); + path = Platform; + sourceTree = ""; + }; + ECF7B2E52C391DFA004D2C57 /* Utilies */ = { + isa = PBXGroup; + children = ( + ECF7B2CF2C39169C004D2C57 /* Extensions */, + ECF7B2E22C39172D004D2C57 /* Platform */, + ); + path = Utilies; + sourceTree = ""; + }; ECFA151E2BEF21BE00455818 /* Objects */ = { isa = PBXGroup; children = ( @@ -981,73 +1000,71 @@ EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */, EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */, ECA738912BE600F500A4542E /* Cache.metal in Sources */, + ECF7B2D82C39169C004D2C57 /* Data++.swift in Sources */, ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */, ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */, EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */, EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */, - ECA738E62BE611FD00A4542E /* CGRect++.swift in Sources */, EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */, ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */, - ECA738E82BE6120F00A4542E /* Color++.swift in Sources */, ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */, ECA7387A2BE5EF0400A4542E /* MemosView.swift in Sources */, ECA738BA2BE60DEF00A4542E /* HistoryAction.swift in Sources */, - EC3565522BEFC65F00A4E0BF /* NSManagedObjectContext++.swift in Sources */, ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */, ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */, + ECF7B2D12C39169C004D2C57 /* CGAffineTransform++.swift in Sources */, EC1815082C2D980B00541369 /* Sort.swift in Sources */, ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */, ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */, + ECF7B2E12C39169C004D2C57 /* View++.swift in Sources */, ECA738DA2BE60FF100A4542E /* CacheRenderPass.swift in Sources */, + ECF7B2DA2C39169C004D2C57 /* Float++.swift in Sources */, ECA738CD2BE60F2F00A4542E /* PointGridContext.swift in Sources */, ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */, ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */, EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */, ECA738C62BE60E9D00A4542E /* EraserPenStyle.swift in Sources */, - ECA738EA2BE6122E00A4542E /* CGPoint++.swift in Sources */, ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */, ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */, - EC3565542BEFC6AD00A4E0BF /* View++.swift in Sources */, - ECBE529E2C1DAB21006BDB3D /* UIImage++.swift in Sources */, EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */, ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */, ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */, + ECF7B2DC2C39169C004D2C57 /* MTLTexture++.swift in Sources */, ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */, + ECF7B2DE2C39169C004D2C57 /* NSManagedObjectContext++.swift in Sources */, ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */, + ECF7B2D72C39169C004D2C57 /* Color++.swift in Sources */, EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */, ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */, + ECF7B2D52C39169C004D2C57 /* CGSize++.swift in Sources */, ECA739082BE623F300A4542E /* PenDock.swift in Sources */, ECA738CB2BE60F1900A4542E /* ViewPortContext.swift in Sources */, - ECA738EE2BE6125D00A4542E /* simd_float4x4++.swift in Sources */, ECA7388C2BE6009600A4542E /* Textures.swift in Sources */, ECFC51272BF8885700D0D051 /* ColorPicker.swift in Sources */, ECA738B82BE60DDC00A4542E /* HistoryEvent.swift in Sources */, EC0D14262BF7A8C9009BFE5F /* PenObject.swift in Sources */, ECA738952BE6012D00A4542E /* ViewPort.metal in Sources */, ECD12A952C1B1FA200B96E12 /* PhotoVertex.swift in Sources */, + ECF7B2DD2C39169C004D2C57 /* NSManagedObject++.swift in Sources */, ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */, ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */, EC0D14242BF79C98009BFE5F /* MemolaModel.xcdatamodeld in Sources */, - ECA738F02BE6127700A4542E /* CGSize++.swift in Sources */, ECFA15242BEF223300455818 /* GraphicContextObject.swift in Sources */, - EC3565562BEFC7B300A4E0BF /* NSManagedObject++.swift in Sources */, - ECA738EC2BE6124E00A4542E /* CGAffineTransform++.swift in Sources */, - EC35655C2BF0712A00A4E0BF /* Float++.swift in Sources */, ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */, ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */, ECA738DC2BE6108D00A4542E /* StrokeRenderPass.swift in Sources */, - ECA738F42BE612A000A4542E /* Array++.swift in Sources */, + ECF7B2D22C39169C004D2C57 /* CGFloat++.swift in Sources */, EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */, ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */, EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */, - EC18150F2C2DB13200541369 /* Date++.swift in Sources */, ECD12A8F2C1AEBA400B96E12 /* Photo.swift in Sources */, + ECF7B2D42C39169C004D2C57 /* CGRect++.swift in Sources */, + ECF7B2D62C39169C004D2C57 /* Collection++.swift in Sources */, EC0151232C306089008A115E /* Sidebar.swift in Sources */, ECD12A932C1B062000B96E12 /* Photo.metal in Sources */, ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */, EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */, ECA738C92BE60EF700A4542E /* GraphicContext.swift in Sources */, - ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */, EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */, ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */, EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */, @@ -1059,30 +1076,33 @@ EC01512A2C306935008A115E /* MemoGrid.swift in Sources */, ECA738C42BE60E8800A4542E /* MarkerPenStyle.swift in Sources */, EC0151262C3067B9008A115E /* TrashView.swift in Sources */, + ECF7B2D92C39169C004D2C57 /* Date++.swift in Sources */, ECA738BF2BE60E3400A4542E /* Pen.swift in Sources */, ECFA15282BEF225000455818 /* QuadObject.swift in Sources */, ECA738932BE6011100A4542E /* Stroke.metal in Sources */, ECA738B62BE60DCD00A4542E /* History.swift in Sources */, ECA738D22BE60F7B00A4542E /* PenStroke.swift in Sources */, EC01512E2C30727F008A115E /* MemoPreview.swift in Sources */, - ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */, EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */, - ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */, + ECF7B2E42C39174D004D2C57 /* Platform.swift in Sources */, ECA738C12BE60E5300A4542E /* PenStyle.swift in Sources */, + ECF7B2DF2C39169C004D2C57 /* simd_float4x4++.swift in Sources */, + ECF7B2D02C39169C004D2C57 /* Array++.swift in Sources */, ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */, ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */, EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */, EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */, EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */, - ECDDD40F2C368B2700DF9D5E /* MTLTexture++.swift in Sources */, - ECC995A52C1EB4CC00B2699A /* Data++.swift in Sources */, ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */, ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */, EC0D14282BF7BF20009BFE5F /* ContextMenuViewModifier.swift in Sources */, EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */, + ECF7B2DB2C39169C004D2C57 /* MTLDevice++.swift in Sources */, ECC995A32C1E8F2800B2699A /* PhotoItem.swift in Sources */, ECA738BC2BE60E0300A4542E /* Tool.swift in Sources */, + ECF7B2E02C39169C004D2C57 /* Image++.swift in Sources */, ECA738972BE6014200A4542E /* Graphic.metal in Sources */, + ECF7B2D32C39169C004D2C57 /* CGPoint++.swift in Sources */, ECA7388A2BE6006A00A4542E /* PipelineStates.swift in Sources */, EC18150D2C2DAC3700541369 /* Placeholder.swift in Sources */, ); @@ -1231,12 +1251,13 @@ "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; MTLLINKER_FLAGS = ""; MTL_COMPILER_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.example.Memola; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; @@ -1266,12 +1287,13 @@ "$(inherited)", "@executable_path/Frameworks", ); + MACOSX_DEPLOYMENT_TARGET = 14.0; MARKETING_VERSION = 1.0; MTLLINKER_FLAGS = ""; MTL_COMPILER_FLAGS = ""; PRODUCT_BUNDLE_IDENTIFIER = com.example.Memola; PRODUCT_NAME = "$(TARGET_NAME)"; - SUPPORTED_PLATFORMS = "iphoneos iphonesimulator"; + SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx"; SUPPORTS_MACCATALYST = NO; SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO; SWIFT_EMIT_LOC_STRINGS = YES; diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index c5aca74..ed5c025 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -13,7 +13,7 @@ struct MemolaApp: App { WindowGroup { DashboardView() .persistence(\.viewContext) - .onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in + .onReceive(NotificationCenter.default.publisher(for: Platform.Application.willTerminateNotification)) { _ in withPersistenceSync(\.viewContext) { context in try context.saveIfNeeded() } diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index 1798f51..f1546b5 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -85,7 +85,11 @@ extension Canvas { func save(for memoObject: MemoObject, completion: @escaping () -> Void) { state = .closing let previewImage = renderer?.drawPreview(on: self) + #if os(macOS) + #warning("TODO: implement for macos") + #else memoObject.preview = previewImage?.jpegData(compressionQuality: 0.8) + #endif withPersistenceSync(\.viewContext) { context in try context.saveIfNeeded() } @@ -135,7 +139,7 @@ extension Canvas { self.previewTransform = simd_float4x4(transform) } - func updateClipBounds(_ scrollView: UIScrollView, on drawingView: DrawingView) { + func updateClipBounds(_ scrollView: Platform.ScrollView, on drawingView: DrawingView) { let ratio = drawingView.ratio let bounds = scrollView.convert(scrollView.bounds, to: drawingView) clipBounds = CGRect(origin: bounds.origin.muliply(by: ratio), size: bounds.size.multiply(by: ratio)) diff --git a/Memola/Canvas/Core/Renderer.swift b/Memola/Canvas/Core/Renderer.swift index e5292b9..7d43ceb 100644 --- a/Memola/Canvas/Core/Renderer.swift +++ b/Memola/Canvas/Core/Renderer.swift @@ -110,7 +110,7 @@ final class Renderer { viewPortRenderPass.draw(into: commandBuffer, on: canvas, with: self) } - func drawPreview(on canvas: Canvas) -> UIImage? { + func drawPreview(on canvas: Canvas) -> Platform.Image? { guard let commandBuffer = commandQueue.makeCommandBuffer() else { NSLog("[Memola] - Unable to create command buffer") return nil @@ -124,6 +124,11 @@ final class Renderer { guard let cgImage = previewRenderPass.previewTexture?.getImage() else { return nil } + #if os(macOS) + #warning("TODO: implement here") + return nil + #else return UIImage(cgImage: cgImage, scale: 1.0, orientation: .downMirrored) + #endif } } diff --git a/Memola/Canvas/Elements/Photo/Photo.swift b/Memola/Canvas/Elements/Photo/Photo.swift index cf2fdaa..ed6a77c 100644 --- a/Memola/Canvas/Elements/Photo/Photo.swift +++ b/Memola/Canvas/Elements/Photo/Photo.swift @@ -12,7 +12,7 @@ final class Photo: @unchecked Sendable, Equatable { var id: UUID = UUID() var size: CGSize var origin: CGPoint - var image: UIImage? + var image: Platform.Image? var url: URL? var bounds: [CGFloat] var createdAt: Date diff --git a/Memola/Canvas/Tool/Core/Tool.swift b/Memola/Canvas/Tool/Core/Tool.swift index 38d9b9e..cb906f7 100644 --- a/Memola/Canvas/Tool/Core/Tool.swift +++ b/Memola/Canvas/Tool/Core/Tool.swift @@ -127,7 +127,7 @@ public class Tool: NSObject, ObservableObject { } } - func selectPhoto(_ image: UIImage, for canvasID: NSManagedObjectID) { + func selectPhoto(_ image: Platform.Image, for canvasID: NSManagedObjectID) { guard let (resizedImage, dimension) = resizePhoto(of: image) else { return } let photoItem = bookmarkPhoto(of: resizedImage, and: image, in: dimension, with: canvasID) withAnimation { @@ -136,7 +136,7 @@ public class Tool: NSObject, ObservableObject { } } - private func resizePhoto(of image: UIImage) -> (UIImage, CGSize)? { + private func resizePhoto(of image: Platform.Image) -> (Platform.Image, CGSize)? { let targetSize = CGSize(width: 512, height: 512) let size = image.size let widthRatio = targetSize.width / size.width @@ -147,6 +147,9 @@ public class Tool: NSObject, ObservableObject { ) let rect = CGRect(origin: .zero, size: targetSize) + #if os(macOS) + return (image, dimension) + #else UIGraphicsBeginImageContextWithOptions(targetSize, true, 1.0) image.draw(in: rect) let newImage = UIGraphicsGetImageFromCurrentImageContext() @@ -155,10 +158,16 @@ public class Tool: NSObject, ObservableObject { guard let newImage else { return nil } return (newImage, dimension) + #endif } - private func bookmarkPhoto(of image: UIImage, and previewImage: UIImage, in dimension: CGSize, with canvasID: NSManagedObjectID) -> PhotoItem? { + 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() + #else guard let data = image.jpegData(compressionQuality: 1) else { return nil } + #endif let fileManager = FileManager.default guard let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else { return nil diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 3875932..c1342d4 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -10,14 +10,14 @@ import SwiftUI import MetalKit import Foundation -class CanvasViewController: UIViewController { +class CanvasViewController: Platform.ViewController { let drawingView: DrawingView - let scrollView: UIScrollView = UIScrollView() + let scrollView: Platform.ScrollView = Platform.ScrollView() var renderView: MTKView { drawingView.renderView } - var photoInsertGesture: UITapGestureRecognizer? + var photoInsertGesture: Platform.TapGestureRecognizer? let tool: Tool let canvas: Canvas @@ -47,6 +47,28 @@ class CanvasViewController: UIViewController { configureListeners() } + #if os(macOS) + override func viewWillAppear() { + super.viewWillAppear() + resizeDocumentView() + updateDocumentBounds() + loadMemo() + } + + override func viewDidLayout() { + super.viewDidLayout() + drawingView.disableUserInteraction() + drawingView.updateDrawableSize(with: view.frame.size) + renderer.resize(on: renderView, to: renderView.drawableSize) + renderView.draw() + drawingView.enableUserInteraction() + } + + override func viewDidDisappear() { + super.viewDidDisappear() + history.resetRedo() + } + #else override func viewWillAppear(_ animated: Bool) { super.viewWillAppear(animated) resizeDocumentView() @@ -67,11 +89,16 @@ class CanvasViewController: UIViewController { super.viewDidDisappear(animated) history.resetRedo() } + #endif } extension CanvasViewController { func configureViews() { + #if os(macOS) + #warning("TODO: implement for macos") + #else view.backgroundColor = .white + renderView.autoResizeDrawable = false renderView.enableSetNeedsDisplay = true renderView.translatesAutoresizingMaskIntoConstraints = false @@ -105,9 +132,13 @@ extension CanvasViewController { scrollView.addSubview(drawingView) drawingView.backgroundColor = .clear drawingView.isUserInteractionEnabled = false + #endif } func resizeDocumentView(to newSize: CGSize? = nil) { + #if os(macOS) + #warning("TODO: implement for macos") + #else scrollView.layoutIfNeeded() let size = canvas.size @@ -130,17 +161,25 @@ extension CanvasViewController { scrollView.setContentOffset(point, animated: true) drawingView.updateDrawableSize(with: view.frame.size) + #endif } 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 } func updateDocumentBounds() { + #if os(macOS) + #warning("TODO: implement for macos") + #else var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.zoomScale) let xDelta = bounds.minX * 0.0 let yDelta = bounds.minY * 0.0 @@ -152,6 +191,7 @@ extension CanvasViewController { if canvas.state == .loaded { canvas.loadStrokes(bounds) } + #endif } } @@ -231,13 +271,17 @@ extension CanvasViewController: MTKViewDelegate { extension CanvasViewController { func configureGestures() { - let photoInsertGesture = UITapGestureRecognizer(target: self, action: #selector(recognizeTapGesture)) + let photoInsertGesture = Platform.TapGestureRecognizer(target: self, action: #selector(recognizeTapGesture)) + #if os(macOS) + photoInsertGesture.numberOfClicksRequired = 1 + #else photoInsertGesture.numberOfTapsRequired = 1 + #endif self.photoInsertGesture = photoInsertGesture scrollView.addGestureRecognizer(photoInsertGesture) } - @objc func recognizeTapGesture(_ gesture: UITapGestureRecognizer) { + @objc func recognizeTapGesture(_ gesture: Platform.TapGestureRecognizer) { guard let photoItem = tool.selectedPhotoItem else { return } withAnimation { tool.selectedPhotoItem = nil @@ -249,6 +293,8 @@ extension CanvasViewController { } } +#if os(macOS) +#else extension CanvasViewController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { drawingView @@ -294,6 +340,7 @@ extension CanvasViewController: UIScrollViewDelegate { draggingEnded() } } +#endif extension CanvasViewController { func magnificationStarted() { @@ -352,20 +399,32 @@ extension CanvasViewController { enablesDrawing = false enablesPhotoInsertion = true } + #if os(macOS) + #warning("TODO: implement for macos") + #else scrollView.isScrollEnabled = enablesScrolling drawingView.isUserInteractionEnabled = enablesDrawing photoInsertGesture?.isEnabled = enablesPhotoInsertion enablesDrawing ? drawingView.enableUserInteraction() : drawingView.disableUserInteraction() + #endif } } extension CanvasViewController { func zoomChanged(_ zoomScale: CGFloat) { + #if os(macOS) + #warning("TODO: implement for macos") + #else scrollView.setZoomScale(zoomScale, animated: true) + #endif } func lockModeChanged(_ state: Bool) { + #if os(macOS) + #warning("TODO: implement for macos") + #else scrollView.pinchGestureRecognizer?.isEnabled = !state + #endif } func gridModeChanged(_ mode: GridMode) { diff --git a/Memola/Canvas/View/Bridge/Views/DrawingView.swift b/Memola/Canvas/View/Bridge/Views/DrawingView.swift index 2e98577..be3a87a 100644 --- a/Memola/Canvas/View/Bridge/Views/DrawingView.swift +++ b/Memola/Canvas/View/Bridge/Views/DrawingView.swift @@ -5,11 +5,11 @@ // Created by Dscyre Scotti on 5/4/24. // -import UIKit +import SwiftUI import MetalKit import Foundation -class DrawingView: UIView { +class DrawingView: Platform.View { let tool: Tool let canvas: Canvas let history: History @@ -36,7 +36,10 @@ class DrawingView: UIView { func updateDrawableSize(with size: CGSize) { renderView.drawableSize = size.multiply(by: 2) } - + + #if os(macOS) + #warning("TODO: to implement touch events") + #else override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { !canvas.hasValidStroke } @@ -78,6 +81,7 @@ class DrawingView: UIView { let point = touch.preciseLocation(in: self) touchEnded(at: point) } + #endif func touchBegan(at point: CGPoint) { guard !disablesUserInteraction else { return } diff --git a/Memola/Canvas/View/Canvas/CanvasView.swift b/Memola/Canvas/View/Canvas/CanvasView.swift index d697358..9c03657 100644 --- a/Memola/Canvas/View/Canvas/CanvasView.swift +++ b/Memola/Canvas/View/Canvas/CanvasView.swift @@ -7,14 +7,22 @@ import SwiftUI -struct CanvasView: UIViewControllerRepresentable { +struct CanvasView: Platform.ViewControllerRepresentable { @ObservedObject var tool: Tool @ObservedObject var canvas: Canvas @ObservedObject var history: History + #if os(macOS) + func makeNSViewController(context: Context) -> CanvasViewController { + CanvasViewController(tool: tool, canvas: canvas, history: history) + } + + func updateNSViewController(_ nsViewController: CanvasViewController, context: Context) { } + #else func makeUIViewController(context: Context) -> CanvasViewController { CanvasViewController(tool: tool, canvas: canvas, history: history) } func updateUIViewController(_ uiViewController: CanvasViewController, context: Context) { } + #endif } diff --git a/Memola/Components/Views/CameraView/CameraView.swift b/Memola/Components/Views/CameraView/CameraView.swift index 4be3e8b..523a4ea 100644 --- a/Memola/Components/Views/CameraView/CameraView.swift +++ b/Memola/Components/Views/CameraView/CameraView.swift @@ -7,6 +7,7 @@ import SwiftUI +#if os(iOS) struct CameraView: UIViewControllerRepresentable { @Binding var image: UIImage? @@ -44,3 +45,4 @@ struct CameraView: UIViewControllerRepresentable { } } } +#endif diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index c6aa549..68cc0b3 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -26,6 +26,9 @@ struct DashboardView: View { MemosView(memo: $memo) } } + #if os(macOS) + #warning("TODO: implement for macOS") + #else .fullScreenCover(item: $memo) { memo in MemoView(memo: memo) .onDisappear { @@ -35,5 +38,6 @@ struct DashboardView: View { } } } + #endif } } diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 53e71ae..726c629 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -46,9 +46,14 @@ struct MemosView: View { memoCard(memoObject, cellWidth) } .navigationTitle(horizontalSizeClass == .compact ? "Memos" : "") + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) .toolbar { + #if os(macOS) + #warning("TODO: implement for macos") + #else if horizontalSizeClass == .regular { ToolbarItem(placement: .topBarLeading) { Text("Memola") @@ -111,6 +116,7 @@ struct MemosView: View { } } } + #endif } .onChange(of: sort) { oldValue, newValue in memoObjects.sortDescriptors = newValue.memoSortDescriptors diff --git a/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift b/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift index 319fc14..2b4fa69 100644 --- a/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift +++ b/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift @@ -42,6 +42,10 @@ struct MemoGrid: View { } } } - .background(Color(uiColor: .secondarySystemBackground)) + #if os(macOS) + .background(Color(color: .windowBackgroundColor)) + #else + .background(Color(color: .secondarySystemBackground)) + #endif } } diff --git a/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift b/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift index c5fc94c..b57f80f 100644 --- a/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift +++ b/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift @@ -21,8 +21,8 @@ struct MemoPreview: View { var body: some View { Group { - if let preview, let previewImage = UIImage(data: preview) { - Image(uiImage: previewImage) + if let preview, let previewImage = Platform.Image(data: preview) { + Image(image: previewImage) .resizable() .aspectRatio(contentMode: .fit) } else { diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 815e068..f5af75e 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -46,9 +46,14 @@ struct TrashView: View { memoCard(memoObject, cellWidth) } .navigationTitle(horizontalSizeClass == .compact ? "Trash" : "") + #if os(iOS) .navigationBarTitleDisplayMode(.inline) + #endif .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) .toolbar { + #if os(macOS) + #warning("TODO: implement for macos") + #else if horizontalSizeClass == .regular { ToolbarItem(placement: .topBarLeading) { Text("Memola") @@ -56,6 +61,7 @@ struct TrashView: View { .fontWeight(.bold) } } + #endif } .onChange(of: query) { oldValue, newValue in updatePredicate() diff --git a/Memola/Features/Dashboard/Sidebar/Sidebar.swift b/Memola/Features/Dashboard/Sidebar/Sidebar.swift index 06eb780..0314677 100644 --- a/Memola/Features/Dashboard/Sidebar/Sidebar.swift +++ b/Memola/Features/Dashboard/Sidebar/Sidebar.swift @@ -38,9 +38,15 @@ struct Sidebar: View { .listStyle(.sidebar) .navigationTitle(horizontalSizeClass == .compact ? "Memola" : "") .scrollContentBackground(.hidden) - .background(Color(uiColor: .secondarySystemBackground)) + #if os(macOS) + .background(Color(color: .windowBackgroundColor)) + #else + .background(Color(color: .secondarySystemBackground)) + #endif .navigationSplitViewColumnWidth(min: 250, ideal: 250, max: 250) + #if os(iOS) .navigationBarTitleDisplayMode(horizontalSizeClass == .compact ? .automatic : .inline) + #endif } } diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index d3c62a3..3d2873c 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -43,6 +43,7 @@ struct ElementToolbar: View { .padding(.bottom, 10) } } + #if os(iOS) .fullScreenCover(isPresented: $opensCamera) { let image: Binding = Binding { tool.selectedPhotoItem?.image @@ -55,8 +56,8 @@ struct ElementToolbar: View { } .alert("Camera Access Denied", isPresented: $isCameraAccessDenied) { Button { - if let url = URL(string: UIApplication.openSettingsURLString + "&path=CAMERA/\(String(describing: Bundle.main.bundleIdentifier))") { - UIApplication.shared.open(url) + if let url = URL(string: Platform.Application.openSettingsURLString + "&path=CAMERA/\(String(describing: Bundle.main.bundleIdentifier))") { + Platform.Application.shared.open(url) } } label: { Text("Open Settings") @@ -65,12 +66,13 @@ struct ElementToolbar: View { } message: { Text("Memola requires access to the camera to capture photos. Please open Settings and enable camera access.") } + #endif .onChange(of: photosPickerItem) { oldValue, newValue in if newValue != nil { Task { tool.isLoadingPhoto = true let data = try? await newValue?.loadTransferable(type: Data.self) - if let data, let image = UIImage(data: data) { + if let data, let image = Platform.Image(data: data) { tool.selectPhoto(image, for: canvas.canvasID) } photosPickerItem = nil @@ -93,7 +95,9 @@ struct ElementToolbar: View { .foregroundStyle(tool.selection == .hand ? Color.white : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif .background { if tool.selection == .hand { Color.accentColor @@ -113,7 +117,9 @@ struct ElementToolbar: View { .foregroundStyle(tool.selection == .pen ? Color.white : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif .background { if tool.selection == .pen { Color.accentColor @@ -133,7 +139,9 @@ struct ElementToolbar: View { .foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif .background { if tool.selection == .photo { Color.accentColor @@ -179,7 +187,9 @@ struct ElementToolbar: View { .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif Button { withAnimation { tool.selectTool(.photo) @@ -190,7 +200,9 @@ struct ElementToolbar: View { .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif } .background { RoundedRectangle(cornerRadius: 8) @@ -211,14 +223,18 @@ struct ElementToolbar: View { .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { Image(systemName: "photo.fill.on.rectangle.fill") .contentShape(.circle) .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif } } diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 722e431..13c9d2d 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -158,7 +158,9 @@ struct MemoView: View { .clipShape(.rect(cornerRadius: 8)) .padding(10) } + #if os(iOS) .hoverEffect(.lift) + #endif .transition(.move(edge: .bottom).combined(with: .blurReplace)) } } diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index f053c3a..677980f 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -201,7 +201,11 @@ struct PenDock: View { } preview: { penPreview(pen) .drawingGroup() + #if os(macOS) + #warning("TODO: implement for macos") + #else .contentShape(.contextMenuPreview, .rect(cornerRadius: 10)) + #endif } .onDrag(if: pen.strokeStyle != .eraser) { tool.draggedPen = pen @@ -274,7 +278,11 @@ struct PenDock: View { } preview: { penPreview(pen) .drawingGroup() + #if os(macOS) + #warning("TODO: implement for macos") + #else .contentShape(.contextMenuPreview, .rect(cornerRadius: 10)) + #endif } .onDrag(if: pen.strokeStyle != .eraser) { tool.draggedPen = pen @@ -356,7 +364,9 @@ struct PenDock: View { .drawingGroup() } .buttonStyle(.plain) + #if os(iOS) .hoverEffect(.lift) + #endif .popover(isPresented: $opensColorPicker) { let color = Binding( get: { pen.color }, @@ -397,8 +407,10 @@ struct PenDock: View { .frame(width: size + 2, height: size + 2) } } + #if os(iOS) .hoverEffect(.lift) .pickerStyle(.wheel) + #endif .frame(width: width * factor - 18, height: 35) .onChange(of: pen.thickness) { _, _ in withPersistence(\.viewContext) { context in @@ -429,8 +441,10 @@ struct PenDock: View { .frame(width: size + 2, height: size + 2) } } + #if os(iOS) .hoverEffect(.lift) .pickerStyle(.wheel) + #endif .frame(width: 50, height: 30) .onChange(of: pen.thickness) { _, _ in withPersistence(\.viewContext) { context in @@ -453,7 +467,9 @@ struct PenDock: View { } } .foregroundStyle(.green) + #if os(iOS) .hoverEffect(.lift) + #endif } func penPreview(_ pen: Pen) -> some View { @@ -532,7 +548,9 @@ struct PenDock: View { .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif .contentTransition(.symbolEffect(.replace)) } diff --git a/Memola/Features/Memo/PhotoPreview/PhotoItem.swift b/Memola/Features/Memo/PhotoPreview/PhotoItem.swift index 79e2539..821d7fb 100644 --- a/Memola/Features/Memo/PhotoPreview/PhotoItem.swift +++ b/Memola/Features/Memo/PhotoPreview/PhotoItem.swift @@ -5,13 +5,13 @@ // Created by Dscyre Scotti on 6/16/24. // -import UIKit +import SwiftUI import Foundation struct PhotoItem: Identifiable, Equatable { var id: URL - let image: UIImage - let previewImage: UIImage + let image: Platform.Image + let previewImage: Platform.Image let dimension: CGSize let bookmark: Data diff --git a/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift index beb923e..440d2f1 100644 --- a/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift +++ b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift @@ -14,7 +14,7 @@ struct PhotoPreview: View { @ObservedObject var tool: Tool var body: some View { - Image(uiImage: photoItem.previewImage) + Image(image: photoItem.previewImage) .resizable() .scaledToFit() .frame(width: horizontalSizeClass == .compact ? 80 : nil, height: horizontalSizeClass == .compact ? nil : 100) @@ -40,7 +40,9 @@ struct PhotoPreview: View { } } .foregroundStyle(.red) + #if os(iOS) .hoverEffect(.lift) + #endif .offset(x: -12, y: -12) } .padding(10) diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 1f41fbd..8ea359e 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -66,7 +66,9 @@ struct Toolbar: View { .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif .disabled(textFieldState) .transition(.move(edge: .top).combined(with: .blurReplace)) } @@ -103,7 +105,9 @@ struct Toolbar: View { Image(systemName: "arrow.uturn.backward.circle") .contentShape(.circle) } + #if os(iOS) .hoverEffect(.lift) + #endif .disabled(history.undoDisabled) Button { history.historyPublisher.send(.redo) @@ -111,7 +115,9 @@ struct Toolbar: View { Image(systemName: "arrow.uturn.forward.circle") .contentShape(.circle) } + #if os(iOS) .hoverEffect(.lift) + #endif .disabled(history.redoDisabled) } .frame(width: size * 2, height: size) @@ -142,7 +148,9 @@ struct Toolbar: View { .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) } + #if os(iOS) .hoverEffect(.lift) + #endif .contentTransition(.symbolEffect(.replace)) .transition(.move(edge: .top).combined(with: .blurReplace)) } diff --git a/Memola/Extensions/Array++.swift b/Memola/Utilies/Extensions/Array++.swift similarity index 100% rename from Memola/Extensions/Array++.swift rename to Memola/Utilies/Extensions/Array++.swift diff --git a/Memola/Extensions/CGAffineTransform++.swift b/Memola/Utilies/Extensions/CGAffineTransform++.swift similarity index 100% rename from Memola/Extensions/CGAffineTransform++.swift rename to Memola/Utilies/Extensions/CGAffineTransform++.swift diff --git a/Memola/Extensions/CGFloat++.swift b/Memola/Utilies/Extensions/CGFloat++.swift similarity index 100% rename from Memola/Extensions/CGFloat++.swift rename to Memola/Utilies/Extensions/CGFloat++.swift diff --git a/Memola/Extensions/CGPoint++.swift b/Memola/Utilies/Extensions/CGPoint++.swift similarity index 100% rename from Memola/Extensions/CGPoint++.swift rename to Memola/Utilies/Extensions/CGPoint++.swift diff --git a/Memola/Extensions/CGRect++.swift b/Memola/Utilies/Extensions/CGRect++.swift similarity index 100% rename from Memola/Extensions/CGRect++.swift rename to Memola/Utilies/Extensions/CGRect++.swift diff --git a/Memola/Extensions/CGSize++.swift b/Memola/Utilies/Extensions/CGSize++.swift similarity index 100% rename from Memola/Extensions/CGSize++.swift rename to Memola/Utilies/Extensions/CGSize++.swift diff --git a/Memola/Extensions/Collection++.swift b/Memola/Utilies/Extensions/Collection++.swift similarity index 100% rename from Memola/Extensions/Collection++.swift rename to Memola/Utilies/Extensions/Collection++.swift diff --git a/Memola/Extensions/Color++.swift b/Memola/Utilies/Extensions/Color++.swift similarity index 50% rename from Memola/Extensions/Color++.swift rename to Memola/Utilies/Extensions/Color++.swift index 332db55..493769c 100644 --- a/Memola/Extensions/Color++.swift +++ b/Memola/Utilies/Extensions/Color++.swift @@ -8,8 +8,16 @@ import SwiftUI extension Color { + init(color: Platform.Color) { + #if os(macOS) + self = Color(nsColor: color) + #else + self = Color(uiColor: color) + #endif + } + var components: [CGFloat] { - let color = UIColor(self) + let color = Platform.Color(self) return color.components } @@ -20,6 +28,16 @@ extension Color { extension Color { var hsba: (hue: Double, saturation: Double, brightness: Double, alpha: Double) { + #if os(macOS) + #warning("TODO: need double check") + let nsColor = NSColor(self) + var hue: CGFloat = 0 + var saturation: CGFloat = 0 + var brightness: CGFloat = 0 + var alpha: CGFloat = 0 + nsColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) + return (hue, saturation, brightness, alpha) + #else let uiColor = UIColor(self) var hue: CGFloat = 0 var saturation: CGFloat = 0 @@ -27,13 +45,21 @@ extension Color { var alpha: CGFloat = 0 uiColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha) return (hue, saturation, brightness, alpha) + #endif } } -extension UIColor { +extension Platform.Color { var components: [CGFloat] { + #if os(macOS) + #warning("TODO: need double check") + let nsColor: NSColor = self + let ciColor: CIColor = .init(color: nsColor) ?? CIColor(red: 0, green: 0, blue: 0) + return [ciColor.red, ciColor.green, ciColor.blue, ciColor.alpha] + #else let uiColor: UIColor = self let ciColor: CIColor = .init(color: uiColor) return [ciColor.red, ciColor.green, ciColor.blue, ciColor.alpha] + #endif } } diff --git a/Memola/Extensions/Data++.swift b/Memola/Utilies/Extensions/Data++.swift similarity index 100% rename from Memola/Extensions/Data++.swift rename to Memola/Utilies/Extensions/Data++.swift diff --git a/Memola/Extensions/Date++.swift b/Memola/Utilies/Extensions/Date++.swift similarity index 100% rename from Memola/Extensions/Date++.swift rename to Memola/Utilies/Extensions/Date++.swift diff --git a/Memola/Extensions/Float++.swift b/Memola/Utilies/Extensions/Float++.swift similarity index 100% rename from Memola/Extensions/Float++.swift rename to Memola/Utilies/Extensions/Float++.swift diff --git a/Memola/Extensions/UIImage++.swift b/Memola/Utilies/Extensions/Image++.swift similarity index 68% rename from Memola/Extensions/UIImage++.swift rename to Memola/Utilies/Extensions/Image++.swift index 983ad0f..0f9fc16 100644 --- a/Memola/Extensions/UIImage++.swift +++ b/Memola/Utilies/Extensions/Image++.swift @@ -1,13 +1,24 @@ // -// UIImage++.swift +// Image++.swift // Memola // // Created by Dscyre Scotti on 6/15/24. // -import UIKit +import SwiftUI import Foundation +extension Image { + init(image: Platform.Image) { + #if os(macOS) + self = Image(nsImage: image) + #else + self = Image(uiImage: image) + #endif + } +} + +#if os(iOS) extension UIImage { func imageWithUpOrientation() -> UIImage? { switch imageOrientation { @@ -22,3 +33,4 @@ extension UIImage { } } } +#endif diff --git a/Memola/Extensions/MTLDevice++.swift b/Memola/Utilies/Extensions/MTLDevice++.swift similarity index 100% rename from Memola/Extensions/MTLDevice++.swift rename to Memola/Utilies/Extensions/MTLDevice++.swift diff --git a/Memola/Extensions/MTLTexture++.swift b/Memola/Utilies/Extensions/MTLTexture++.swift similarity index 100% rename from Memola/Extensions/MTLTexture++.swift rename to Memola/Utilies/Extensions/MTLTexture++.swift diff --git a/Memola/Extensions/NSManagedObject++.swift b/Memola/Utilies/Extensions/NSManagedObject++.swift similarity index 100% rename from Memola/Extensions/NSManagedObject++.swift rename to Memola/Utilies/Extensions/NSManagedObject++.swift diff --git a/Memola/Extensions/NSManagedObjectContext++.swift b/Memola/Utilies/Extensions/NSManagedObjectContext++.swift similarity index 100% rename from Memola/Extensions/NSManagedObjectContext++.swift rename to Memola/Utilies/Extensions/NSManagedObjectContext++.swift diff --git a/Memola/Extensions/View++.swift b/Memola/Utilies/Extensions/View++.swift similarity index 100% rename from Memola/Extensions/View++.swift rename to Memola/Utilies/Extensions/View++.swift diff --git a/Memola/Extensions/simd_float4x4++.swift b/Memola/Utilies/Extensions/simd_float4x4++.swift similarity index 100% rename from Memola/Extensions/simd_float4x4++.swift rename to Memola/Utilies/Extensions/simd_float4x4++.swift diff --git a/Memola/Utilies/Platform/Platform.swift b/Memola/Utilies/Platform/Platform.swift new file mode 100644 index 0000000..db900e3 --- /dev/null +++ b/Memola/Utilies/Platform/Platform.swift @@ -0,0 +1,30 @@ +// +// Platform.swift +// Memola +// +// Created by Dscyre Scotti on 7/6/24. +// + +import SwiftUI + +enum Platform { + #if os(macOS) + typealias View = NSView + typealias Color = NSColor + typealias Image = NSImage + typealias ScrollView = NSScrollView + typealias Application = NSApplication + typealias ViewController = NSViewController + typealias TapGestureRecognizer = NSClickGestureRecognizer + typealias ViewControllerRepresentable = NSViewControllerRepresentable + #else + typealias View = UIView + typealias Color = UIColor + typealias Image = UIImage + typealias ScrollView = UIScrollView + typealias Application = UIApplication + typealias ViewController = UIViewController + typealias TapGestureRecognizer = UITapGestureRecognizer + typealias ViewControllerRepresentable = UIViewControllerRepresentable + #endif +} From cdee0d189d9d71322e78eb2e9b2cb59abb64e317 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sat, 6 Jul 2024 15:15:14 +0700 Subject: [PATCH 02/22] feat: update memos and trash view --- Memola/App/MemolaApp.swift | 8 ++++ .../Dashboard/Dashboard/DashboardView.swift | 5 +-- .../Dashboard/Details/Memos/MemosView.swift | 45 ++++++++++++++----- .../Dashboard/Details/Trash/TrashView.swift | 8 +++- Memola/Features/Memo/PenDock/PenDock.swift | 10 ++--- 5 files changed, 56 insertions(+), 20 deletions(-) diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index ed5c025..a1175c8 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -21,6 +21,14 @@ struct MemolaApp: App { try context.saveIfNeeded() } } + #if os(macOS) + .frame(minWidth: 1000, minHeight: 600) + #endif } + #if os(macOS) + .defaultPosition(.center) + .windowResizability(.contentSize) + .defaultSize(width: 1200, height: 800) + #endif } } diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index 68cc0b3..e637218 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -26,9 +26,7 @@ struct DashboardView: View { MemosView(memo: $memo) } } - #if os(macOS) - #warning("TODO: implement for macOS") - #else + #if os(iOS) .fullScreenCover(item: $memo) { memo in MemoView(memo: memo) .onDisappear { @@ -38,6 +36,7 @@ struct DashboardView: View { } } } + #else #endif } } diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 726c629..df282a7 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -31,8 +31,8 @@ struct MemosView: View { let standard = UserDefaults.standard var descriptors: [SortDescriptor] = [] var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")] - let sort = Sort(rawValue: standard.value(forKey: "memola.memo-objects.sort") as? String ?? "") ?? .recent - let filter = Filter(rawValue: standard.value(forKey: "memola.memo-objects.filter") as? String ?? "") ?? .none + let sort = Sort(rawValue: standard.value(forKey: "memola.memo-objects.memos.sort") as? String ?? "") ?? .recent + let filter = Filter(rawValue: standard.value(forKey: "memola.memo-objects.memos.filter") as? String ?? "") ?? .none if filter == .favorites { predicates.append(NSPredicate(format: "isFavorite = YES")) } @@ -52,7 +52,32 @@ struct MemosView: View { .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) .toolbar { #if os(macOS) - #warning("TODO: implement for macos") + ToolbarItem(placement: .navigation) { + Text("Memola") + .font(.title3) + .fontWeight(.bold) + } + ToolbarItemGroup(placement: .primaryAction) { + HStack(spacing: 5) { + Button { + createMemo(title: "Untitled") + } label: { + Image(systemName: "square.and.pencil") + } + Picker("", selection: $sort) { + ForEach(Sort.all) { sort in + Text(sort.name) + .tag(sort) + } + } + Picker("", selection: $filter) { + ForEach(Filter.all) { filter in + Text(filter.name) + .tag(filter) + } + } + } + } #else if horizontalSizeClass == .regular { ToolbarItem(placement: .topBarLeading) { @@ -78,14 +103,12 @@ struct MemosView: View { .tag(sort) } } - .pickerStyle(.automatic) Picker("", selection: $filter) { ForEach(Filter.all) { filter in Text(filter.name) .tag(filter) } } - .pickerStyle(.automatic) } } label: { Image(systemName: "ellipsis.circle") @@ -109,10 +132,10 @@ struct MemosView: View { .tag(filter) } } - .pickerStyle(.automatic) } label: { Image(systemName: "line.3.horizontal.decrease.circle") } + .hoverEffect(.lift) } } } @@ -130,10 +153,6 @@ struct MemosView: View { .onReceive(timer) { date in currentDate = date } - .onAppear { - memoObjects.sortDescriptors = sort.memoSortDescriptors - updatePredicate() - } } func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View { @@ -144,11 +163,13 @@ struct MemosView: View { openMemo(for: memoObject) } label: { Label("Open", systemImage: "doc.text") + .labelStyle(.titleAndIcon) } Button(role: .destructive) { markAsTrash(for: memoObject) } label: { Label("Delete", systemImage: "trash") + .labelStyle(.titleAndIcon) } } .overlay(alignment: .topTrailing) { @@ -158,7 +179,11 @@ struct MemosView: View { .animation(.easeInOut, value: memoObject.isFavorite) .frame(width: 20, height: 20) .padding(5) + #if os(macOS) + .background(.gray) + #else .background(.gray.tertiary) + #endif .cornerRadius(5) .contentShape(Rectangle()) .onTapGesture { diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index f5af75e..14ca604 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -52,7 +52,11 @@ struct TrashView: View { .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) .toolbar { #if os(macOS) - #warning("TODO: implement for macos") + ToolbarItem(placement: .navigation) { + Text("Memola") + .font(.title3) + .fontWeight(.bold) + } #else if horizontalSizeClass == .regular { ToolbarItem(placement: .topBarLeading) { @@ -101,11 +105,13 @@ struct TrashView: View { restoreMemo(for: memoObject) } label: { Label("Restore", systemImage: "square.and.arrow.down") + .labelStyle(.titleAndIcon) } Button(role: .destructive) { deletedMemo = memoObject } label: { Label("Delete Permanently", systemImage: "trash") + .labelStyle(.titleAndIcon) } } } details: { diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index 677980f..6c90d29 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -201,10 +201,9 @@ struct PenDock: View { } preview: { penPreview(pen) .drawingGroup() - #if os(macOS) - #warning("TODO: implement for macos") - #else + #if os(iOS) .contentShape(.contextMenuPreview, .rect(cornerRadius: 10)) + #else #endif } .onDrag(if: pen.strokeStyle != .eraser) { @@ -278,10 +277,9 @@ struct PenDock: View { } preview: { penPreview(pen) .drawingGroup() - #if os(macOS) - #warning("TODO: implement for macos") - #else + #if os(iOS) .contentShape(.contextMenuPreview, .rect(cornerRadius: 10)) + #else #endif } .onDrag(if: pen.strokeStyle != .eraser) { From 59b8f18a61693aa65c890e6b82ae2a1dab1067ae Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sat, 6 Jul 2024 15:53:21 +0700 Subject: [PATCH 03/22] feat: set up window group for memo view --- Memola/App/MemolaApp.swift | 13 ++++++++++ .../Dashboard/Dashboard/DashboardView.swift | 14 +++++++++++ .../Dashboard/Details/Memos/MemosView.swift | 25 +++++++++++++++++++ .../Dashboard/Details/Trash/TrashView.swift | 22 +++++++++++++++- Memola/Persistence/Core/Persistence.swift | 8 ++++++ 5 files changed, 81 insertions(+), 1 deletion(-) diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index a1175c8..93f81ed 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -30,5 +30,18 @@ struct MemolaApp: App { .windowResizability(.contentSize) .defaultSize(width: 1200, height: 800) #endif + WindowGroup(id: "memo-view", for: URL.self) { url in + if let url = url.wrappedValue, let memo = Persistence.loadMemo(of: url) { + MemoView(memo: memo) + #if os(macOS) + .frame(minWidth: 1000, minHeight: 600) + #endif + } + } + #if os(macOS) + .defaultPosition(.center) + .windowResizability(.contentSize) + .defaultSize(width: 1200, height: 800) + #endif } } diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index e637218..e027341 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -10,7 +10,9 @@ import SwiftUI struct DashboardView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass + #if os(iOS) @State var memo: MemoObject? + #endif @State var sidebarItem: SidebarItem? = .memos var body: some View { @@ -19,11 +21,23 @@ struct DashboardView: View { } detail: { switch sidebarItem { case .memos: + #if os(macOS) + MemosView() + #else MemosView(memo: $memo) + #endif case .trash: + #if os(macOS) + TrashView(sidebarItem: $sidebarItem) + #else TrashView(memo: $memo, sidebarItem: $sidebarItem) + #endif default: + #if os(macOS) + MemosView() + #else MemosView(memo: $memo) + #endif } } #if os(iOS) diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index df282a7..28165ff 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -8,6 +8,9 @@ import SwiftUI struct MemosView: View { + #if os(macOS) + @Environment(\.openWindow) var openWindow + #endif @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -15,7 +18,9 @@ struct MemosView: View { @State var query: String = "" @State var currentDate: Date = .now + #if os(iOS) @Binding var memo: MemoObject? + #endif @AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent @AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none @@ -26,6 +31,21 @@ struct MemosView: View { query.isEmpty ? .memoEmpty : .memoNotFound } + #if os(macOS) + init() { + let standard = UserDefaults.standard + var descriptors: [SortDescriptor] = [] + var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")] + let sort = Sort(rawValue: standard.value(forKey: "memola.memo-objects.memos.sort") as? String ?? "") ?? .recent + let filter = Filter(rawValue: standard.value(forKey: "memola.memo-objects.memos.filter") as? String ?? "") ?? .none + if filter == .favorites { + predicates.append(NSPredicate(format: "isFavorite = YES")) + } + descriptors = sort.memoSortDescriptors + let predicate = NSCompoundPredicate(type: .and, subpredicates: predicates) + _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) + } + #else init(memo: Binding) { _memo = memo let standard = UserDefaults.standard @@ -40,6 +60,7 @@ struct MemosView: View { let predicate = NSCompoundPredicate(type: .and, subpredicates: predicates) _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } + #endif var body: some View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in @@ -252,7 +273,11 @@ struct MemosView: View { } func openMemo(for memo: MemoObject) { + #if os(macOS) + openWindow(id: "memo-view", value: memo.objectID.uriRepresentation()) + #else self.memo = memo + #endif } func updatePredicate() { diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 14ca604..0d7d628 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -8,6 +8,9 @@ import SwiftUI struct TrashView: View { + #if os(macOS) + @Environment(\.openWindow) var openWindow + #endif @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -16,13 +19,23 @@ struct TrashView: View { @State var restoredMemo: MemoObject? @State var deletedMemo: MemoObject? + #if os(iOS) @Binding var memo: MemoObject? + #endif @Binding var sidebarItem: SidebarItem? var placeholder: Placeholder.Info { query.isEmpty ? .trashEmpty : .trashNotFound } + #if os(macOS) + init(sidebarItem: Binding) { + _sidebarItem = sidebarItem + let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)] + let predicate = NSPredicate(format: "isTrash = YES") + _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) + } + #else init(memo: Binding, sidebarItem: Binding) { _memo = memo _sidebarItem = sidebarItem @@ -30,6 +43,7 @@ struct TrashView: View { let predicate = NSPredicate(format: "isTrash = YES") _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } + #endif var body: some View { let restoresMemo = Binding { @@ -146,7 +160,13 @@ struct TrashView: View { func restoreAndOpenMemo(for memo: MemoObject?) { restoreMemo(for: memo) self.sidebarItem = .memos - self.memo = memo + if let memo { + #if os(macOS) + openWindow(id: "memo-view", value: memo.objectID.uriRepresentation()) + #else + self.memo = memo + #endif + } } func deleteMemo(for memo: MemoObject?) { diff --git a/Memola/Persistence/Core/Persistence.swift b/Memola/Persistence/Core/Persistence.swift index 5ebac21..15c19f3 100644 --- a/Memola/Persistence/Core/Persistence.swift +++ b/Memola/Persistence/Core/Persistence.swift @@ -77,6 +77,14 @@ final class Persistence { fatalError("[Memola]: \(error.localizedDescription)") } }() + + static func loadMemo(of url: URL) -> MemoObject? { + let viewContext = shared.viewContext + guard let objectID = viewContext.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: url) else { + return nil + } + return viewContext.object(with: objectID) as? MemoObject + } } // MARK: - Global Method From cbd2e4c4849656c49377c59aaa70cdb1b592e93f Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sat, 6 Jul 2024 20:44:19 +0700 Subject: [PATCH 04/22] 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? { From 739aaa059b1d8fbdc812cecc05b066760ef72f24 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 7 Jul 2024 13:37:45 +0700 Subject: [PATCH 05/22] feat: sync render view port with scrollview --- Memola.xcodeproj/project.pbxproj | 20 ++++++-- .../Buffers/Vertices/PointGridVertex.swift | 4 -- Memola/Canvas/Core/Canvas.swift | 17 ++++--- Memola/Canvas/Core/Renderer.swift | 2 +- .../ViewController/CanvasViewController.swift | 48 ++++++++++++------- .../NSCenterClipView.swift} | 4 +- .../Views/AppKit/NSSyncScrollView.swift | 29 +++++++++++ Memola/Utilies/Platform/Platform.swift | 2 +- 8 files changed, 91 insertions(+), 35 deletions(-) rename Memola/Canvas/View/Bridge/Views/{CenterClipView.swift => AppKit/NSCenterClipView.swift} (91%) create mode 100644 Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 3dd6735..c5886b2 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -37,6 +37,7 @@ EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; }; EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; }; EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; }; + EC8C9DCE2C39882500A8F3C4 /* NSSyncScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8C9DCD2C39882500A8F3C4 /* NSSyncScrollView.swift */; }; EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */; }; EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */; }; EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */; }; @@ -119,7 +120,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 */; }; + ECF7B2E72C39544E004D2C57 /* NSCenterClipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2E62C39544E004D2C57 /* NSCenterClipView.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 */; }; @@ -161,6 +162,7 @@ EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = ""; }; EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + EC8C9DCD2C39882500A8F3C4 /* NSSyncScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSSyncScrollView.swift; sourceTree = ""; }; EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridMode.swift; sourceTree = ""; }; EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridVertex.swift; sourceTree = ""; }; EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridContext.swift; sourceTree = ""; }; @@ -243,7 +245,7 @@ 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 = ""; }; + ECF7B2E62C39544E004D2C57 /* NSCenterClipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSCenterClipView.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 = ""; }; @@ -322,8 +324,8 @@ EC1437B42BE748E60022C903 /* Views */ = { isa = PBXGroup; children = ( + EC8C9DCC2C3987FD00A8F3C4 /* AppKit */, ECA738AC2BE60CC600A4542E /* DrawingView.swift */, - ECF7B2E62C39544E004D2C57 /* CenterClipView.swift */, ); path = Views; sourceTree = ""; @@ -481,6 +483,15 @@ path = "Preview Content"; sourceTree = ""; }; + EC8C9DCC2C3987FD00A8F3C4 /* AppKit */ = { + isa = PBXGroup; + children = ( + ECF7B2E62C39544E004D2C57 /* NSCenterClipView.swift */, + EC8C9DCD2C39882500A8F3C4 /* NSSyncScrollView.swift */, + ); + path = AppKit; + sourceTree = ""; + }; EC8F54AA2C2ACD9D001C7C74 /* Grid */ = { isa = PBXGroup; children = ( @@ -1038,6 +1049,7 @@ ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */, ECF7B2DE2C39169C004D2C57 /* NSManagedObjectContext++.swift in Sources */, ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */, + EC8C9DCE2C39882500A8F3C4 /* NSSyncScrollView.swift in Sources */, ECF7B2D72C39169C004D2C57 /* Color++.swift in Sources */, EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */, ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */, @@ -1058,7 +1070,7 @@ ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */, ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */, ECA738DC2BE6108D00A4542E /* StrokeRenderPass.swift in Sources */, - ECF7B2E72C39544E004D2C57 /* CenterClipView.swift in Sources */, + ECF7B2E72C39544E004D2C57 /* NSCenterClipView.swift in Sources */, ECF7B2D22C39169C004D2C57 /* CGFloat++.swift in Sources */, EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */, ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */, diff --git a/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift b/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift index ccae5af..4861397 100644 --- a/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift +++ b/Memola/Canvas/Buffers/Vertices/PointGridVertex.swift @@ -10,11 +10,7 @@ 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 8ab5e9f..2c58dbf 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -110,8 +110,9 @@ extension Canvas { 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) + let drawingViewBounds = drawingView.bounds + var targetRect = drawingView.convert(drawingViewBounds, to: renderView) + targetRect.origin.y = renderView.bounds.height - targetRect.maxY #else let targetRect = drawingView.convert(drawingView.bounds, to: renderView) #endif @@ -145,19 +146,23 @@ extension Canvas { } func updateClipBounds(_ scrollView: Platform.ScrollView, on drawingView: DrawingView) { + #if os(macOS) + let ratio = drawingView.ratio + var bounds = scrollView.convert(scrollView.bounds, to: drawingView) + bounds.origin.y = drawingView.bounds.height - (bounds.origin.y + bounds.height) + clipBounds = CGRect(origin: bounds.origin.muliply(by: ratio), size: bounds.size.multiply(by: ratio)) + #else let ratio = drawingView.ratio let bounds = scrollView.convert(scrollView.bounds, to: drawingView) clipBounds = CGRect(origin: bounds.origin.muliply(by: ratio), size: bounds.size.multiply(by: ratio)) + #endif } } // MARK: - Zoom Scale extension Canvas { func setZoomScale(_ zoomScale: CGFloat) { - DispatchQueue.main.async { [weak self] in - guard let self else { return } - self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale) - } + self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale) } } diff --git a/Memola/Canvas/Core/Renderer.swift b/Memola/Canvas/Core/Renderer.swift index b1434bb..3409ade 100644 --- a/Memola/Canvas/Core/Renderer.swift +++ b/Memola/Canvas/Core/Renderer.swift @@ -112,7 +112,7 @@ final class Renderer { func drawPreview(on canvas: Canvas) -> Platform.Image? { guard let commandBuffer = commandQueue.makeCommandBuffer() else { - NSLog("[Memola] - Unable to create command buffer") + NSLog("[Memola] - Unable to create command buffer for preview") return nil } strokeRenderPass.eraserRenderPass = eraserRenderPass diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 81e482f..074536e 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -48,14 +48,11 @@ class CanvasViewController: Platform.ViewController { } #if os(macOS) - override func viewWillLayout() { - super.viewWillLayout() - resizeDocumentView() - updateDocumentBounds() - } override func viewWillAppear() { super.viewWillAppear() + resizeDocumentView() + updateDocumentBounds() loadMemo() } @@ -132,9 +129,9 @@ extension CanvasViewController { scrollView.isScrollEnabled = true scrollView.showsVerticalScrollIndicator = true scrollView.showsHorizontalScrollIndicator = true - scrollView.delegate = self scrollView.backgroundColor = .clear #endif + scrollView.delegate = self scrollView.translatesAutoresizingMaskIntoConstraints = false view.addSubview(scrollView) @@ -146,7 +143,7 @@ extension CanvasViewController { ]) #if os(macOS) - scrollView.contentView = CenterClipView() + scrollView.contentView = NSCenterClipView() scrollView.contentView.drawsBackground = false scrollView.documentView = drawingView #else @@ -171,10 +168,14 @@ extension CanvasViewController { let height = size.height * scale let newFrame = CGRect(x: 0, y: 0, width: width, height: height) drawingView.frame = newFrame + + DispatchQueue.main.async { [unowned canvas] in + canvas.setZoomScale(canvas.defaultZoomScale) + } #if os(macOS) - let rect = scrollView.documentVisibleRect - let center = NSPoint(x: rect.midX, y: rect.midY) + scrollView.contentView.setBoundsSize(newFrame.size) + let center = NSPoint(x: newFrame.midX, y: newFrame.midY) scrollView.setMagnification(canvas.defaultZoomScale, centeredAt: center) #else scrollView.setZoomScale(canvas.defaultZoomScale, animated: true) @@ -189,7 +190,6 @@ extension CanvasViewController { scrollView.setContentOffset(point, animated: true) #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) @@ -204,7 +204,10 @@ extension CanvasViewController { func updateDocumentBounds() { #if os(macOS) - var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.magnification) + let ratio = drawingView.ratio + var bounds = scrollView.convert(scrollView.bounds, to: drawingView) + bounds.origin.y = drawingView.bounds.height - (bounds.origin.y + bounds.height) + bounds = CGRect(origin: bounds.origin.muliply(by: ratio), size: bounds.size.multiply(by: ratio)) #else var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.zoomScale) #endif @@ -226,6 +229,7 @@ extension CanvasViewController { #if os(macOS) NotificationCenter.default.publisher(for: NSScrollView.didEndLiveMagnifyNotification, object: scrollView) .sink { [weak self] _ in + self?.updateDocumentBounds() self?.magnificationEnded() } .store(in: &cancellables) @@ -241,6 +245,7 @@ extension CanvasViewController { .store(in: &cancellables) NotificationCenter.default.publisher(for: NSScrollView.didEndLiveScrollNotification, object: scrollView) .sink { [weak self] _ in + self?.updateDocumentBounds() self?.draggingEnded() } .store(in: &cancellables) @@ -334,7 +339,12 @@ extension CanvasViewController { withAnimation { tool.selectedPhotoItem = nil } + #if os(macOS) + let pointInLeftBottomOrigin = gesture.location(in: drawingView) + let point = CGPoint(x: pointInLeftBottomOrigin.x, y: drawingView.bounds.height - pointInLeftBottomOrigin.y) + #else let point = gesture.location(in: drawingView) + #endif let photo = canvas.insertPhoto(at: point.muliply(by: drawingView.ratio), photoItem: photoItem) history.addUndo(.photo(photo)) drawingView.draw() @@ -342,6 +352,16 @@ extension CanvasViewController { } #if os(macOS) +extension CanvasViewController: NSSyncScrollViewDelegate { + func scrollViewDidZoom(_ scrollView: NSSyncScrollView) { + canvas.setZoomScale(scrollView.magnification) + renderView.draw() + } + + func scrollViewDidScroll(_ scrollView: NSSyncScrollView) { + renderView.draw() + } +} #else extension CanvasViewController: UIScrollViewDelegate { func viewForZooming(in scrollView: UIScrollView) -> UIView? { @@ -392,9 +412,6 @@ 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) @@ -403,9 +420,6 @@ extension CanvasViewController { } func magnificationEnded() { - #if os(macOS) - canvas.setZoomScale(scrollView.magnification) - #endif renderer.setUpdatesViewPort(false) renderer.setRedrawsGraphicRender() renderView.draw() diff --git a/Memola/Canvas/View/Bridge/Views/CenterClipView.swift b/Memola/Canvas/View/Bridge/Views/AppKit/NSCenterClipView.swift similarity index 91% rename from Memola/Canvas/View/Bridge/Views/CenterClipView.swift rename to Memola/Canvas/View/Bridge/Views/AppKit/NSCenterClipView.swift index 1a4ba12..a9c48dd 100644 --- a/Memola/Canvas/View/Bridge/Views/CenterClipView.swift +++ b/Memola/Canvas/View/Bridge/Views/AppKit/NSCenterClipView.swift @@ -1,5 +1,5 @@ // -// CenterClipView.swift +// NSCenterClipView.swift // Memola // // Created by Dscyre Scotti on 7/6/24. @@ -8,7 +8,7 @@ #if canImport(AppKit) import AppKit -class CenterClipView: NSClipView { +class NSCenterClipView: NSClipView { override func constrainBoundsRect(_ proposedBounds: NSRect) -> NSRect { var rect = super.constrainBoundsRect(proposedBounds) if let containerView = self.documentView { diff --git a/Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift b/Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift new file mode 100644 index 0000000..280cfc4 --- /dev/null +++ b/Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift @@ -0,0 +1,29 @@ +// +// NSSyncScrollView.swift +// Memola +// +// Created by Dscyre Scotti on 7/6/24. +// + +#if canImport(AppKit) +import AppKit + +protocol NSSyncScrollViewDelegate: AnyObject { + func scrollViewDidZoom(_ scrollView: NSSyncScrollView) + func scrollViewDidScroll(_ scrollView: NSSyncScrollView) +} + +class NSSyncScrollView: NSScrollView { + weak var delegate: NSSyncScrollViewDelegate? + + override func magnify(with event: NSEvent) { + super.magnify(with: event) + delegate?.scrollViewDidZoom(self) + } + + override func scrollWheel(with event: NSEvent) { + super.scrollWheel(with: event) + delegate?.scrollViewDidScroll(self) + } +} +#endif diff --git a/Memola/Utilies/Platform/Platform.swift b/Memola/Utilies/Platform/Platform.swift index db900e3..0b8c939 100644 --- a/Memola/Utilies/Platform/Platform.swift +++ b/Memola/Utilies/Platform/Platform.swift @@ -12,7 +12,7 @@ enum Platform { typealias View = NSView typealias Color = NSColor typealias Image = NSImage - typealias ScrollView = NSScrollView + typealias ScrollView = NSSyncScrollView typealias Application = NSApplication typealias ViewController = NSViewController typealias TapGestureRecognizer = NSClickGestureRecognizer From 708a2b07587c25292cfe0d3575bde39531bec7ba Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 7 Jul 2024 14:11:18 +0700 Subject: [PATCH 06/22] feat: add mouse touch event for stroke drawing --- .../View/Bridge/Views/DrawingView.swift | 21 ++++++++++++++++++- 1 file changed, 20 insertions(+), 1 deletion(-) diff --git a/Memola/Canvas/View/Bridge/Views/DrawingView.swift b/Memola/Canvas/View/Bridge/Views/DrawingView.swift index be3a87a..73e7ff2 100644 --- a/Memola/Canvas/View/Bridge/Views/DrawingView.swift +++ b/Memola/Canvas/View/Bridge/Views/DrawingView.swift @@ -38,7 +38,26 @@ class DrawingView: Platform.View { } #if os(macOS) - #warning("TODO: to implement touch events") + override func mouseDown(with event: NSEvent) { + super.mouseDown(with: event) + let pointInLeftBottomOrigin = convert(event.locationInWindow, from: nil) + let point = CGPoint(x: pointInLeftBottomOrigin.x, y: bounds.height - pointInLeftBottomOrigin.y) + touchBegan(at: point) + } + + override func mouseDragged(with event: NSEvent) { + super.mouseDragged(with: event) + let pointInLeftBottomOrigin = convert(event.locationInWindow, from: nil) + let point = CGPoint(x: pointInLeftBottomOrigin.x, y: bounds.height - pointInLeftBottomOrigin.y) + touchMoved(to: point) + } + + override func mouseUp(with event: NSEvent) { + super.mouseUp(with: event) + let pointInLeftBottomOrigin = convert(event.locationInWindow, from: nil) + let point = CGPoint(x: pointInLeftBottomOrigin.x, y: bounds.height - pointInLeftBottomOrigin.y) + touchEnded(at: point) + } #else override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool { !canvas.hasValidStroke From ab7d24fb4eef2e90fc9d6aea1dfc80eb9634c504 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 7 Jul 2024 17:23:00 +0700 Subject: [PATCH 07/22] feat: fine tune memo canvas view for macos runtime --- Memola.xcodeproj/project.pbxproj | 2 + Memola/Canvas/Core/Canvas.swift | 6 + .../ViewController/CanvasViewController.swift | 12 +- .../Memo/ElementToolbar/ElementToolbar.swift | 33 ++++- Memola/Features/Memo/Memo/MemoView.swift | 46 ++++++- Memola/Features/Memo/PenDock/PenDock.swift | 126 ++++++++++-------- Memola/Features/Memo/Toolbar/Toolbar.swift | 49 +++++-- 7 files changed, 189 insertions(+), 85 deletions(-) diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index c5886b2..89feef7 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -1254,6 +1254,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Memola/Memola.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\""; @@ -1291,6 +1292,7 @@ ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon; ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor; CODE_SIGN_ENTITLEMENTS = Memola/Memola.entitlements; + "CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development"; CODE_SIGN_STYLE = Automatic; CURRENT_PROJECT_VERSION = 1; DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\""; diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index 2c58dbf..663b9a0 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -162,7 +162,13 @@ extension Canvas { // MARK: - Zoom Scale extension Canvas { func setZoomScale(_ zoomScale: CGFloat) { + #if os(macOS) self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale) + #else + DispatchQueue.main.async { [unowned self] in + self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale) + } + #endif } } diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 074536e..93a2fea 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -168,16 +168,16 @@ extension CanvasViewController { let height = size.height * scale let newFrame = CGRect(x: 0, y: 0, width: width, height: height) drawingView.frame = newFrame - + + #if os(macOS) DispatchQueue.main.async { [unowned canvas] in canvas.setZoomScale(canvas.defaultZoomScale) } - - #if os(macOS) scrollView.contentView.setBoundsSize(newFrame.size) let center = NSPoint(x: newFrame.midX, y: newFrame.midY) scrollView.setMagnification(canvas.defaultZoomScale, centeredAt: center) #else + canvas.setZoomScale(canvas.defaultZoomScale) scrollView.setZoomScale(canvas.defaultZoomScale, animated: true) centerDocumentView(to: newSize) #endif @@ -427,9 +427,6 @@ extension CanvasViewController { } func draggingStarted() { - #if os(macOS) - canvas.setZoomScale(scrollView.magnification) - #endif guard !renderer.updatesViewPort else { return } canvas.updateClipBounds(scrollView, on: drawingView) drawingView.disableUserInteraction() @@ -437,9 +434,6 @@ extension CanvasViewController { } func draggingEnded() { - #if os(macOS) - canvas.setZoomScale(scrollView.magnification) - #endif renderer.setUpdatesViewPort(false) renderer.setRedrawsGraphicRender() renderView.draw() diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index 3d2873c..e1c45bd 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -23,6 +23,9 @@ struct ElementToolbar: View { var body: some View { Group { + #if os(macOS) + regularToolbar + #else if horizontalSizeClass == .regular { regularToolbar } else { @@ -42,6 +45,7 @@ struct ElementToolbar: View { } .padding(.bottom, 10) } + #endif } #if os(iOS) .fullScreenCover(isPresented: $opensCamera) { @@ -94,9 +98,12 @@ struct ElementToolbar: View { .frame(width: size, height: size) .foregroundStyle(tool.selection == .hand ? Color.white : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif .background { if tool.selection == .hand { @@ -116,9 +123,12 @@ struct ElementToolbar: View { .frame(width: size, height: size) .foregroundStyle(tool.selection == .pen ? Color.white : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif .background { if tool.selection == .pen { @@ -138,9 +148,12 @@ struct ElementToolbar: View { .frame(width: size, height: size) .foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif .background { if tool.selection == .photo { @@ -159,13 +172,13 @@ struct ElementToolbar: View { .transition(.blurReplace.animation(.easeIn(duration: 0.1))) } } - .background { - if tool.selection == .photo { - RoundedRectangle(cornerRadius: 8) - .fill(Color.white.tertiary) - .transition(.move(edge: .leading).combined(with: .opacity).animation(.easeIn(duration: 0.1))) - } - } +// .background { +// if tool.selection == .photo { +// RoundedRectangle(cornerRadius: 8) +// .fill(Color.white.tertiary) +// .transition(.move(edge: .leading).combined(with: .opacity).animation(.easeIn(duration: 0.1))) +// } +// } } .background { RoundedRectangle(cornerRadius: 8) @@ -222,18 +235,24 @@ struct ElementToolbar: View { .contentShape(.circle) .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { Image(systemName: "photo.fill.on.rectangle.fill") .contentShape(.circle) .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif } } diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 13c9d2d..9ebb954 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -31,11 +31,15 @@ struct MemoView: View { var body: some View { Group { + #if os(macOS) + canvasView + #else if horizontalSizeClass == .regular { canvasView } else { compactCanvasView } + #endif } .overlay(alignment: .top) { Toolbar(size: size, memo: memo, tool: tool, canvas: canvas, history: history) @@ -66,7 +70,6 @@ struct MemoView: View { switch tool.selection { case .pen: PenDock(tool: tool, canvas: canvas, size: size) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) case .photo: if let photoItem = tool.selectedPhotoItem { PhotoPreview(photoItem: photoItem, tool: tool) @@ -106,7 +109,7 @@ struct MemoView: View { } } .overlay(alignment: .bottom) { - if tool.selection != .hand { + if tool.selection != .hand && !canvas.locksCanvas { Button { withAnimation { tool.selectTool(.hand) @@ -121,6 +124,7 @@ struct MemoView: View { .contentShape(.capsule) } .offset(y: 5) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) } } } @@ -132,6 +136,40 @@ struct MemoView: View { let zoomScale: CGFloat = (((canvas.zoomScale - canvas.minimumZoomScale) * (upperBound - lowerBound) / (canvas.maximumZoomScale - canvas.minimumZoomScale)) + lowerBound).rounded() let zoomScales: [Int] = [400, 200, 100, 75, 50, 25, 10] if !canvas.locksCanvas { + #if os(macOS) + Menu { + ForEach(zoomScales, id: \.self) { scale in + Button { + let zoomScale = ((CGFloat(scale) - lowerBound) * (canvas.maximumZoomScale - canvas.minimumZoomScale) / (upperBound - lowerBound)) + canvas.minimumZoomScale + canvas.zoomPublisher.send(zoomScale) + } label: { + Label { + Text(scale, format: .percent) + } icon: { + if CGFloat(scale) == zoomScale { + Image(systemName: "checkmark") + } + } + .font(.headline) + } + } + } label: { + Text(zoomScale / 100, format: .percent) + .font(.subheadline) + .frame(height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + .menuIndicator(.hidden) + .frame(width: 50, height: size) + .padding(.leading, 12) + .background(.regularMaterial) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + .menuStyle(.borderlessButton) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) + .padding(10) + #else Menu { ForEach(zoomScales, id: \.self) { scale in Button { @@ -156,12 +194,12 @@ struct MemoView: View { .frame(height: size) .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) .padding(10) } - #if os(iOS) .hoverEffect(.lift) - #endif .transition(.move(edge: .bottom).combined(with: .blurReplace)) + #endif } } diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index 6c90d29..58a7b0a 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -23,8 +23,28 @@ struct PenDock: View { @State var refreshScrollId: UUID = UUID() @State var opensColorPicker: Bool = false + #if os(macOS) + @State var showsThinknessPicker: Bool = false + #endif var body: some View { + #if os(macOS) + ZStack(alignment: .bottomTrailing) { + if !canvas.locksCanvas { + VStack(alignment: .trailing) { + penPropertyTool + penItemList + } + .fixedSize() + .frame(maxHeight: .infinity) + .padding(10) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) + } + lockButton + .padding(10) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) + } + #else if horizontalSizeClass == .regular { ZStack(alignment: .bottomTrailing) { if !canvas.locksCanvas { @@ -73,6 +93,7 @@ struct PenDock: View { .transition(.move(edge: .trailing).combined(with: .blurReplace)) } } + #endif } @ViewBuilder @@ -319,12 +340,12 @@ struct PenDock: View { var compactPenPropertyTool: some View { if let pen = tool.selectedPen { HStack(spacing: 10) { - compactPenThicknessPicker(pen) - .frame(width: width) + penThicknessPicker(pen) + .frame(width: size) .rotationEffect(.degrees(-90)) if pen.strokeStyle == .marker { penColorPicker(pen) - .frame(width: width) + .frame(width: size) .transition(.move(edge: .trailing).combined(with: .opacity)) } } @@ -353,7 +374,8 @@ struct PenDock: View { } .background(baseColor) .clipShape(.rect(cornerRadius: 8)) - .frame(height: horizontalSizeClass == .compact ? 30 : 25) + .contentShape(.rect(cornerRadius: 8)) + .frame(height: size) .overlay { RoundedRectangle(cornerRadius: 8) .stroke(Color.gray, lineWidth: 0.4) @@ -389,64 +411,50 @@ struct PenDock: View { let maximum: CGFloat = pen.style.thickness.max let start: CGFloat = 4 let end: CGFloat = 10 - let selection = Binding( - get: { pen.thickness }, - set: { - pen.thickness = $0 - tool.objectWillChange.send() - } - ) - Picker("", selection: selection) { - ForEach(pen.style.thicknessSteps, id: \.self) { step in - let size = ((step - minimum) * (end - start) / (maximum - minimum)) + start - (0.5 / step) - Circle() - .fill(.black) - .frame(width: size, height: size) - .frame(width: size + 2, height: size + 2) - } - } - #if os(iOS) - .hoverEffect(.lift) - .pickerStyle(.wheel) - #endif - .frame(width: width * factor - 18, height: 35) - .onChange(of: pen.thickness) { _, _ in - withPersistence(\.viewContext) { context in - try context.saveIfNeeded() - } - } - } - - @ViewBuilder - func compactPenThicknessPicker(_ pen: Pen) -> some View { - let minimum: CGFloat = pen.style.thickness.min - let maximum: CGFloat = pen.style.thickness.max - let start: CGFloat = 4 - let end: CGFloat = 7 - let selection = Binding( + let selection = Binding( get: { pen.thickness }, set: { - pen.thickness = $0 + pen.thickness = $0 ?? .zero tool.objectWillChange.send() } ) - Picker("", selection: selection) { - ForEach(pen.style.thicknessSteps, id: \.self) { step in - let size = ((step - minimum) * (end - start) / (maximum - minimum)) + start - (0.5 / step) - Circle() - .fill(.black) - .frame(width: size, height: size) - .frame(width: size + 2, height: size + 2) - } - } - #if os(iOS) - .hoverEffect(.lift) - .pickerStyle(.wheel) + #if os(macOS) + let _width = width * factor - 38 + #else + let _width = horizontalSizeClass == .compact ? self.size : width * factor - 38 #endif - .frame(width: 50, height: 30) - .onChange(of: pen.thickness) { _, _ in - withPersistence(\.viewContext) { context in - try context.saveIfNeeded() + ScrollViewReader { proxy in + ScrollView(showsIndicators: false) { + LazyVStack(spacing: 0) { + ForEach(pen.style.thicknessSteps, id: \.self) { step in + let size = ((step - minimum) * (end - start) / (maximum - minimum)) + start - (0.5 / step) + Circle() + .foregroundStyle(.primary) + .frame(width: size, height: size) + .frame(width: _width, height: self.size) + .contentShape(.rect) + .id(step) + } + } + } + #if os(macOS) + .frame(height: size) + #else + .frame(width: _width, height: size) + #endif + .background(.gray.quaternary) + .clipShape(.rect(cornerRadius: 8)) + .scrollPosition(id: selection, anchor: .center) + .scrollTargetLayout() + .scrollTargetBehavior(.viewAligned) + .scrollIndicators(.hidden) + .onAppear { + proxy.scrollTo(selection.wrappedValue) + } + .onChange(of: pen.thickness) { _, _ in + withPersistence(\.viewContext) { context in + try context.saveIfNeeded() + } } } } @@ -467,6 +475,8 @@ struct PenDock: View { .foregroundStyle(.green) #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif } @@ -541,13 +551,15 @@ struct PenDock: View { } } label: { Image(systemName: canvas.locksCanvas ? "lock.fill" : "lock.open.fill") - .contentShape(.circle) .frame(width: size, height: size) .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif .contentTransition(.symbolEffect(.replace)) } diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 8ea359e..05dc21c 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -41,9 +41,15 @@ struct Toolbar: View { } } .frame(maxWidth: .infinity, alignment: .leading) + #if os(macOS) + if !canvas.locksCanvas { + ElementToolbar(size: size, tool: tool, canvas: canvas) + } + #else if !canvas.locksCanvas, horizontalSizeClass == .regular { ElementToolbar(size: size, tool: tool, canvas: canvas) } + #endif HStack(spacing: 5) { if !canvas.locksCanvas { gridModeControl @@ -61,13 +67,15 @@ struct Toolbar: View { closeMemo() } label: { Image(systemName: "xmark") - .contentShape(.circle) .frame(width: size, height: size) .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif .disabled(textFieldState) .transition(.move(edge: .top).combined(with: .blurReplace)) @@ -98,29 +106,34 @@ struct Toolbar: View { } var historyControl: some View { - HStack { + HStack(spacing: 0) { Button { history.historyPublisher.send(.undo) } label: { Image(systemName: "arrow.uturn.backward.circle") - .contentShape(.circle) + .frame(width: size, height: size) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif .disabled(history.undoDisabled) Button { history.historyPublisher.send(.redo) } label: { Image(systemName: "arrow.uturn.forward.circle") - .contentShape(.circle) + .frame(width: size, height: size) + .contentShape(.rect(cornerRadius: 8)) } #if os(iOS) .hoverEffect(.lift) + #else + .buttonStyle(.plain) #endif .disabled(history.redoDisabled) } - .frame(width: size * 2, height: size) .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) .disabled(textFieldState) @@ -128,6 +141,27 @@ struct Toolbar: View { } var gridModeControl: some View { + #if os(macOS) + Button { + switch canvas.gridMode { + case .none: + canvas.gridMode = .point + case .point: + canvas.gridMode = .line + case .line: + canvas.gridMode = .none + } + } label: { + Image(systemName: canvas.gridMode.icon) + .frame(width: size, height: size) + .background(.regularMaterial) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + .buttonStyle(.plain) + .contentTransition(.symbolEffect(.replace)) + .transition(.move(edge: .top).combined(with: .blurReplace)) + #else Menu { ForEach(GridMode.all, id: \.self) { mode in Button { @@ -143,16 +177,15 @@ struct Toolbar: View { } } label: { Image(systemName: canvas.gridMode.icon) - .contentShape(.circle) .frame(width: size, height: size) .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } - #if os(iOS) .hoverEffect(.lift) - #endif .contentTransition(.symbolEffect(.replace)) .transition(.move(edge: .top).combined(with: .blurReplace)) + #endif } func closeMemo() { From aafb2c74a21ab8edf095743d0a6f1b6224262a25 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 7 Jul 2024 18:35:10 +0700 Subject: [PATCH 08/22] feat: open memo object over dasboardview instead of new window --- Memola.xcodeproj/project.pbxproj | 4 ++ Memola/App/MemolaApp.swift | 14 +---- .../Dashboard/Dashboard/DashboardView.swift | 51 +++++++++++------- .../Dashboard/Dashboard/MemoManager.swift | 37 +++++++++++++ .../Dashboard/Details/Memos/MemosView.swift | 30 +---------- .../Dashboard/Details/Trash/TrashView.swift | 22 +------- .../Memo/ElementToolbar/ElementToolbar.swift | 12 ++--- Memola/Features/Memo/Memo/MemoView.swift | 2 +- Memola/Features/Memo/PenDock/PenDock.swift | 54 ++++++++++--------- Memola/Features/Memo/Toolbar/Toolbar.swift | 6 ++- 10 files changed, 114 insertions(+), 118 deletions(-) create mode 100644 Memola/Features/Dashboard/Dashboard/MemoManager.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 89feef7..53aee4e 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -28,6 +28,7 @@ EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF72C0F601A005DB0AF /* Node.swift */; }; EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */ = {isa = PBXBuildFile; fileRef = EC3565592BF060D900A4E0BF /* Quad.metal */; }; EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB112C1B2DD90008D976 /* ToolSelection.swift */; }; + EC3D67CC2C3AAD5E00359400 /* MemoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3D67CB2C3AAD5E00359400 /* MemoManager.swift */; }; EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC42F7842C25267000E86E96 /* ElementGroup.swift */; }; EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; }; EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */; }; @@ -151,6 +152,7 @@ EC2BEBF72C0F601A005DB0AF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; EC3565592BF060D900A4E0BF /* Quad.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Quad.metal; sourceTree = ""; }; EC37FB112C1B2DD90008D976 /* ToolSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSelection.swift; sourceTree = ""; }; + EC3D67CB2C3AAD5E00359400 /* MemoManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoManager.swift; sourceTree = ""; }; EC42F7842C25267000E86E96 /* ElementGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementGroup.swift; sourceTree = ""; }; EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = ""; }; EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDropDelegate.swift; sourceTree = ""; }; @@ -279,6 +281,7 @@ EC01511C2C305C99008A115E /* Dashboard */ = { isa = PBXGroup; children = ( + EC3D67CB2C3AAD5E00359400 /* MemoManager.swift */, EC01511D2C305CA9008A115E /* DashboardView.swift */, ); path = Dashboard; @@ -1021,6 +1024,7 @@ ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */, EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */, EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */, + EC3D67CC2C3AAD5E00359400 /* MemoManager.swift in Sources */, EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */, ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */, ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */, diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index 93f81ed..d262564 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -29,19 +29,7 @@ struct MemolaApp: App { .defaultPosition(.center) .windowResizability(.contentSize) .defaultSize(width: 1200, height: 800) - #endif - WindowGroup(id: "memo-view", for: URL.self) { url in - if let url = url.wrappedValue, let memo = Persistence.loadMemo(of: url) { - MemoView(memo: memo) - #if os(macOS) - .frame(minWidth: 1000, minHeight: 600) - #endif - } - } - #if os(macOS) - .defaultPosition(.center) - .windowResizability(.contentSize) - .defaultSize(width: 1200, height: 800) + .windowToolbarStyle(.unifiedCompact) #endif } } diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index e027341..5dec179 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -10,38 +10,54 @@ import SwiftUI struct DashboardView: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass - #if os(iOS) - @State var memo: MemoObject? - #endif + @StateObject var memoManager: MemoManager = .shared + @State var sidebarItem: SidebarItem? = .memos + @Namespace var namespace + var body: some View { + #if os(macOS) NavigationSplitView { Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) } detail: { switch sidebarItem { case .memos: - #if os(macOS) MemosView() - #else - MemosView(memo: $memo) - #endif case .trash: - #if os(macOS) TrashView(sidebarItem: $sidebarItem) - #else - TrashView(memo: $memo, sidebarItem: $sidebarItem) - #endif default: - #if os(macOS) MemosView() - #else - MemosView(memo: $memo) - #endif } } - #if os(iOS) - .fullScreenCover(item: $memo) { memo in + .toolbar(memoManager.memoObject == nil ? .visible : .hidden, for: .windowToolbar) + .toolbarBackground(memoManager.memoObject == nil ? .clear : Color(nsColor: .windowBackgroundColor), for: .windowToolbar) + .overlay { + if let memo = memoManager.memoObject { + MemoView(memo: memo) + .onDisappear { + withPersistence(\.viewContext) { context in + try context.saveIfNeeded() + context.refreshAllObjects() + } + } + .transition(.move(edge: .bottom)) + } + } + #else + NavigationSplitView { + Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) + } detail: { + switch sidebarItem { + case .memos: + MemosView() + case .trash: + TrashView(sidebarItem: $sidebarItem) + default: + MemosView() + } + } + .fullScreenCover(item: $memoManager.memo) { memo in MemoView(memo: memo) .onDisappear { withPersistence(\.viewContext) { context in @@ -50,7 +66,6 @@ struct DashboardView: View { } } } - #else #endif } } diff --git a/Memola/Features/Dashboard/Dashboard/MemoManager.swift b/Memola/Features/Dashboard/Dashboard/MemoManager.swift new file mode 100644 index 0000000..e2adfdd --- /dev/null +++ b/Memola/Features/Dashboard/Dashboard/MemoManager.swift @@ -0,0 +1,37 @@ +// +// MemoManager.swift +// Memola +// +// Created by Dscyre Scotti on 7/7/24. +// + +import SwiftUI +import Foundation + +class MemoManager: ObservableObject { + static let shared: MemoManager = .init() + + @Published var memoObject: MemoObject? + + private init() { } + + func openMemo(_ memoObject: MemoObject?) { + #if os(macOS) + withAnimation(.easeOut) { + self.memoObject = memoObject + } + #else + self.memoObject = memoObject + #endif + } + + func closeMemo() { + #if os(macOS) + withAnimation(.easeOut) { + self.memoObject = nil + } + #else + self.memoObject = nil + #endif + } +} diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 28165ff..0edbd98 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -8,9 +8,6 @@ import SwiftUI struct MemosView: View { - #if os(macOS) - @Environment(\.openWindow) var openWindow - #endif @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -18,10 +15,6 @@ struct MemosView: View { @State var query: String = "" @State var currentDate: Date = .now - #if os(iOS) - @Binding var memo: MemoObject? - #endif - @AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent @AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none @@ -31,7 +24,6 @@ struct MemosView: View { query.isEmpty ? .memoEmpty : .memoNotFound } - #if os(macOS) init() { let standard = UserDefaults.standard var descriptors: [SortDescriptor] = [] @@ -45,22 +37,6 @@ struct MemosView: View { let predicate = NSCompoundPredicate(type: .and, subpredicates: predicates) _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } - #else - init(memo: Binding) { - _memo = memo - let standard = UserDefaults.standard - var descriptors: [SortDescriptor] = [] - var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")] - let sort = Sort(rawValue: standard.value(forKey: "memola.memo-objects.memos.sort") as? String ?? "") ?? .recent - let filter = Filter(rawValue: standard.value(forKey: "memola.memo-objects.memos.filter") as? String ?? "") ?? .none - if filter == .favorites { - predicates.append(NSPredicate(format: "isFavorite = YES")) - } - descriptors = sort.memoSortDescriptors - let predicate = NSCompoundPredicate(type: .and, subpredicates: predicates) - _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) - } - #endif var body: some View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in @@ -273,11 +249,7 @@ struct MemosView: View { } func openMemo(for memo: MemoObject) { - #if os(macOS) - openWindow(id: "memo-view", value: memo.objectID.uriRepresentation()) - #else - self.memo = memo - #endif + MemoManager.shared.openMemo(memo) } func updatePredicate() { diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 0d7d628..c484474 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -8,9 +8,6 @@ import SwiftUI struct TrashView: View { - #if os(macOS) - @Environment(\.openWindow) var openWindow - #endif @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -19,31 +16,18 @@ struct TrashView: View { @State var restoredMemo: MemoObject? @State var deletedMemo: MemoObject? - #if os(iOS) - @Binding var memo: MemoObject? - #endif @Binding var sidebarItem: SidebarItem? var placeholder: Placeholder.Info { query.isEmpty ? .trashEmpty : .trashNotFound } - #if os(macOS) init(sidebarItem: Binding) { _sidebarItem = sidebarItem let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)] let predicate = NSPredicate(format: "isTrash = YES") _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) } - #else - init(memo: Binding, sidebarItem: Binding) { - _memo = memo - _sidebarItem = sidebarItem - let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)] - let predicate = NSPredicate(format: "isTrash = YES") - _memoObjects = FetchRequest(sortDescriptors: descriptors, predicate: predicate) - } - #endif var body: some View { let restoresMemo = Binding { @@ -161,11 +145,7 @@ struct TrashView: View { restoreMemo(for: memo) self.sidebarItem = .memos if let memo { - #if os(macOS) - openWindow(id: "memo-view", value: memo.objectID.uriRepresentation()) - #else - self.memo = memo - #endif + MemoManager.shared.openMemo(memo) } } diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index e1c45bd..7e03b12 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -156,11 +156,13 @@ struct ElementToolbar: View { .buttonStyle(.plain) #endif .background { + #if os(iOS) if tool.selection == .photo { Color.accentColor .clipShape(.rect(cornerRadius: 8)) .matchedGeometryEffect(id: "element.toolbar.bg", in: namespace) } + #endif if tool.selection != .photo { Color.clear .matchedGeometryEffect(id: "element.toolbar.photo.options", in: namespace) @@ -172,13 +174,6 @@ struct ElementToolbar: View { .transition(.blurReplace.animation(.easeIn(duration: 0.1))) } } -// .background { -// if tool.selection == .photo { -// RoundedRectangle(cornerRadius: 8) -// .fill(Color.white.tertiary) -// .transition(.move(edge: .leading).combined(with: .opacity).animation(.easeIn(duration: 0.1))) -// } -// } } .background { RoundedRectangle(cornerRadius: 8) @@ -228,6 +223,7 @@ struct ElementToolbar: View { var photoOption: some View { HStack(spacing: 0) { + #if os(iOS) Button { openCamera() } label: { @@ -237,9 +233,7 @@ struct ElementToolbar: View { .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) } - #if os(iOS) .hoverEffect(.lift) - #else .buttonStyle(.plain) #endif PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 9ebb954..4d47e91 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -15,10 +15,10 @@ struct MemoView: View { @StateObject var canvas: Canvas @StateObject var history: History - @State var memo: MemoObject @State var title: String @FocusState var textFieldState: Bool + let memo: MemoObject let size: CGFloat = 32 init(memo: MemoObject) { diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index 58a7b0a..e60947b 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -62,35 +62,37 @@ struct PenDock: View { .transition(.move(edge: .trailing).combined(with: .blurReplace)) } } else { - ZStack(alignment: .bottomTrailing) { - if !canvas.locksCanvas { - GeometryReader { proxy in - HStack(alignment: .bottom, spacing: 10) { - newPenButton - .frame(height: height * factor - 18) - compactPenItemList - .fixedSize(horizontal: false, vertical: true) - compactPenPropertyTool + GeometryReader { proxy in + ZStack(alignment: .bottomTrailing) { + if !canvas.locksCanvas { + GeometryReader { proxy in + HStack(alignment: .bottom, spacing: 10) { + newPenButton + .frame(height: height * factor - 18) + compactPenItemList + .fixedSize(horizontal: false, vertical: true) + compactPenPropertyTool + } + .padding(.horizontal, 10) + .clipped() + .background(alignment: .bottom) { + RoundedRectangle(cornerRadius: 8) + .fill(.regularMaterial) + .frame(height: height * factor - 18) + } + .padding(.horizontal, 10) + .padding(.bottom, 20) + .frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom) + .frame(maxWidth: .infinity) } - .padding(.horizontal, 10) - .clipped() - .background(alignment: .bottom) { - RoundedRectangle(cornerRadius: 8) - .fill(.regularMaterial) - .frame(height: height * factor - 18) - } - .padding(.horizontal, 10) - .padding(.bottom, 20) - .frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom) - .frame(maxWidth: .infinity) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) } - .transition(.move(edge: .bottom).combined(with: .blurReplace)) + lockButton + .frame(maxWidth: .infinity, alignment: .bottomTrailing) + .padding(10) + .offset(y: canvas.locksCanvas || proxy.size.width > proxy.size.height ? 0 : -(height * factor - size + 30)) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) } - lockButton - .frame(maxWidth: .infinity, alignment: .bottomTrailing) - .padding(10) - .offset(y: canvas.locksCanvas ? 0 : -(height * factor - size + 30)) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) } } #endif diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 05dc21c..24629c2 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -17,11 +17,11 @@ struct Toolbar: View { @ObservedObject var history: History @State var title: String - @State var memo: MemoObject @FocusState var textFieldState: Bool let size: CGFloat + let memo: MemoObject init(size: CGFloat, memo: MemoObject, tool: Tool, canvas: Canvas, history: History) { self.size = size @@ -190,7 +190,11 @@ struct Toolbar: View { func closeMemo() { canvas.save(for: memo) { + #if os(macOS) + MemoManager.shared.closeMemo() + #else dismiss() + #endif } } } From a5ef50eb24fc4e98e5a5d6dfe202d63af7de61b8 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Tue, 9 Jul 2024 19:50:22 +0700 Subject: [PATCH 09/22] feat: remove lock button --- Memola/Canvas/Core/Canvas.swift | 1 - .../ViewController/CanvasViewController.swift | 5 - .../Dashboard/Dashboard/DashboardView.swift | 2 +- .../Memo/ElementToolbar/ElementToolbar.swift | 1 - Memola/Features/Memo/Memo/MemoView.swift | 122 +++++++++--------- Memola/Features/Memo/PenDock/PenDock.swift | 110 +++++----------- Memola/Features/Memo/Toolbar/Toolbar.swift | 18 +-- 7 files changed, 99 insertions(+), 160 deletions(-) diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index 663b9a0..589be72 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -32,7 +32,6 @@ final class Canvas: ObservableObject, Identifiable, @unchecked Sendable { @Published var state: State = .initial @Published var zoomScale: CGFloat = .zero - @Published var locksCanvas: Bool = false @Published var gridMode: GridMode = .point diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 93a2fea..819a7c1 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -260,11 +260,6 @@ extension CanvasViewController { self?.zoomChanged(zoomScale) } .store(in: &cancellables) - canvas.$locksCanvas - .sink { [weak self] state in - self?.lockModeChanged(state) - } - .store(in: &cancellables) canvas.$gridMode .delay(for: .milliseconds(100), scheduler: DispatchQueue.main) .sink { [weak self] mode in diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index 5dec179..519439b 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -57,7 +57,7 @@ struct DashboardView: View { MemosView() } } - .fullScreenCover(item: $memoManager.memo) { memo in + .fullScreenCover(item: $memoManager.memoObject) { memo in MemoView(memo: memo) .onDisappear { withPersistence(\.viewContext) { context in diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index 7e03b12..fa0c8d3 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -234,7 +234,6 @@ struct ElementToolbar: View { .contentShape(.rect(cornerRadius: 8)) } .hoverEffect(.lift) - .buttonStyle(.plain) #endif PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { Image(systemName: "photo.fill.on.rectangle.fill") diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 4d47e91..0ad9ad3 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -109,7 +109,7 @@ struct MemoView: View { } } .overlay(alignment: .bottom) { - if tool.selection != .hand && !canvas.locksCanvas { + if tool.selection != .hand { Button { withAnimation { tool.selectTool(.hand) @@ -135,72 +135,70 @@ struct MemoView: View { let lowerBound: CGFloat = 10 let zoomScale: CGFloat = (((canvas.zoomScale - canvas.minimumZoomScale) * (upperBound - lowerBound) / (canvas.maximumZoomScale - canvas.minimumZoomScale)) + lowerBound).rounded() let zoomScales: [Int] = [400, 200, 100, 75, 50, 25, 10] - if !canvas.locksCanvas { - #if os(macOS) - Menu { - ForEach(zoomScales, id: \.self) { scale in - Button { - let zoomScale = ((CGFloat(scale) - lowerBound) * (canvas.maximumZoomScale - canvas.minimumZoomScale) / (upperBound - lowerBound)) + canvas.minimumZoomScale - canvas.zoomPublisher.send(zoomScale) - } label: { - Label { - Text(scale, format: .percent) - } icon: { - if CGFloat(scale) == zoomScale { - Image(systemName: "checkmark") - } + #if os(macOS) + Menu { + ForEach(zoomScales, id: \.self) { scale in + Button { + let zoomScale = ((CGFloat(scale) - lowerBound) * (canvas.maximumZoomScale - canvas.minimumZoomScale) / (upperBound - lowerBound)) + canvas.minimumZoomScale + canvas.zoomPublisher.send(zoomScale) + } label: { + Label { + Text(scale, format: .percent) + } icon: { + if CGFloat(scale) == zoomScale { + Image(systemName: "checkmark") } - .font(.headline) } + .font(.headline) } - } label: { - Text(zoomScale / 100, format: .percent) - .font(.subheadline) - .frame(height: size) - .clipShape(.rect(cornerRadius: 8)) - .contentShape(.rect(cornerRadius: 8)) } - .menuIndicator(.hidden) - .frame(width: 50, height: size) - .padding(.leading, 12) - .background(.regularMaterial) - .clipShape(.rect(cornerRadius: 8)) - .contentShape(.rect(cornerRadius: 8)) - .menuStyle(.borderlessButton) - .transition(.move(edge: .bottom).combined(with: .blurReplace)) - .padding(10) - #else - Menu { - ForEach(zoomScales, id: \.self) { scale in - Button { - let zoomScale = ((CGFloat(scale) - lowerBound) * (canvas.maximumZoomScale - canvas.minimumZoomScale) / (upperBound - lowerBound)) + canvas.minimumZoomScale - canvas.zoomPublisher.send(zoomScale) - } label: { - Label { - Text(scale, format: .percent) - } icon: { - if CGFloat(scale) == zoomScale { - Image(systemName: "checkmark") - } - } - .font(.headline) - } - } - } label: { - Text(zoomScale / 100, format: .percent) - .frame(width: 45) - .font(.subheadline) - .padding(.horizontal, size / 2.5) - .frame(height: size) - .background(.regularMaterial) - .clipShape(.rect(cornerRadius: 8)) - .contentShape(.rect(cornerRadius: 8)) - .padding(10) - } - .hoverEffect(.lift) - .transition(.move(edge: .bottom).combined(with: .blurReplace)) - #endif + } label: { + Text(zoomScale / 100, format: .percent) + .font(.subheadline) + .frame(height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) } + .menuIndicator(.hidden) + .frame(width: 50, height: size) + .padding(.leading, 12) + .background(.regularMaterial) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + .menuStyle(.borderlessButton) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) + .padding(10) + #else + Menu { + ForEach(zoomScales, id: \.self) { scale in + Button { + let zoomScale = ((CGFloat(scale) - lowerBound) * (canvas.maximumZoomScale - canvas.minimumZoomScale) / (upperBound - lowerBound)) + canvas.minimumZoomScale + canvas.zoomPublisher.send(zoomScale) + } label: { + Label { + Text(scale, format: .percent) + } icon: { + if CGFloat(scale) == zoomScale { + Image(systemName: "checkmark") + } + } + .font(.headline) + } + } + } label: { + Text(zoomScale / 100, format: .percent) + .frame(width: 45) + .font(.subheadline) + .padding(.horizontal, size / 2.5) + .frame(height: size) + .background(.regularMaterial) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + .padding(10) + } + .hoverEffect(.lift) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) + #endif } func loadingIndicator(_ title: String) -> some View { diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index e60947b..2ffeb30 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -29,71 +29,46 @@ struct PenDock: View { var body: some View { #if os(macOS) - ZStack(alignment: .bottomTrailing) { - if !canvas.locksCanvas { - VStack(alignment: .trailing) { - penPropertyTool - penItemList - } - .fixedSize() - .frame(maxHeight: .infinity) - .padding(10) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) - } - lockButton - .padding(10) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) + VStack(alignment: .trailing) { + penPropertyTool + penItemList } + .fixedSize() + .frame(maxHeight: .infinity) + .padding(10) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) #else if horizontalSizeClass == .regular { - ZStack(alignment: .bottomTrailing) { - if !canvas.locksCanvas { - VStack(alignment: .trailing) { - penPropertyTool - penItemList - } - .fixedSize() - .frame(maxHeight: .infinity) - .padding(10) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) - } - lockButton - .padding(10) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) + VStack(alignment: .trailing) { + penPropertyTool + penItemList } + .fixedSize() + .frame(maxHeight: .infinity) + .padding(10) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) } else { GeometryReader { proxy in - ZStack(alignment: .bottomTrailing) { - if !canvas.locksCanvas { - GeometryReader { proxy in - HStack(alignment: .bottom, spacing: 10) { - newPenButton - .frame(height: height * factor - 18) - compactPenItemList - .fixedSize(horizontal: false, vertical: true) - compactPenPropertyTool - } - .padding(.horizontal, 10) - .clipped() - .background(alignment: .bottom) { - RoundedRectangle(cornerRadius: 8) - .fill(.regularMaterial) - .frame(height: height * factor - 18) - } - .padding(.horizontal, 10) - .padding(.bottom, 20) - .frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom) - .frame(maxWidth: .infinity) - } - .transition(.move(edge: .bottom).combined(with: .blurReplace)) - } - lockButton - .frame(maxWidth: .infinity, alignment: .bottomTrailing) - .padding(10) - .offset(y: canvas.locksCanvas || proxy.size.width > proxy.size.height ? 0 : -(height * factor - size + 30)) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) + HStack(alignment: .bottom, spacing: 10) { + newPenButton + .frame(height: height * factor - 18) + compactPenItemList + .fixedSize(horizontal: false, vertical: true) + compactPenPropertyTool } + .padding(.horizontal, 10) + .clipped() + .background(alignment: .bottom) { + RoundedRectangle(cornerRadius: 8) + .fill(.regularMaterial) + .frame(height: height * factor - 18) + } + .padding(.horizontal, 10) + .padding(.bottom, 20) + .frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom) + .frame(maxWidth: .infinity) } + .transition(.move(edge: .bottom).combined(with: .blurReplace)) } #endif } @@ -331,7 +306,6 @@ struct PenDock: View { RoundedRectangle(cornerRadius: 8) .fill(.regularMaterial) } - .transition(.move(edge: .trailing).combined(with: .blurReplace)) } else { Color.clear .frame(width: width * factor - 18, height: 50) @@ -546,26 +520,6 @@ struct PenDock: View { } } - var lockButton: some View { - Button { - withAnimation { - canvas.locksCanvas.toggle() - } - } label: { - Image(systemName: canvas.locksCanvas ? "lock.fill" : "lock.open.fill") - .frame(width: size, height: size) - .background(.regularMaterial) - .clipShape(.rect(cornerRadius: 8)) - .contentShape(.rect(cornerRadius: 8)) - } - #if os(iOS) - .hoverEffect(.lift) - #else - .buttonStyle(.plain) - #endif - .contentTransition(.symbolEffect(.replace)) - } - func createNewPen() { let pen = PenObject.createObject(\.viewContext, penStyle: .marker) var selectedPen = tool.selectedPen diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 24629c2..ce6bb34 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -35,26 +35,20 @@ struct Toolbar: View { var body: some View { HStack(spacing: 5) { HStack(spacing: 5) { - if !canvas.locksCanvas { - closeButton - titleField - } + closeButton + titleField } .frame(maxWidth: .infinity, alignment: .leading) #if os(macOS) - if !canvas.locksCanvas { - ElementToolbar(size: size, tool: tool, canvas: canvas) - } + ElementToolbar(size: size, tool: tool, canvas: canvas) #else - if !canvas.locksCanvas, horizontalSizeClass == .regular { + if horizontalSizeClass == .regular { ElementToolbar(size: size, tool: tool, canvas: canvas) } #endif HStack(spacing: 5) { - if !canvas.locksCanvas { - gridModeControl - historyControl - } + gridModeControl + historyControl } .frame(maxWidth: .infinity, alignment: .trailing) } From f36cc3e20cff12b82be9a8ab2274ec5e20be5a6e Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Tue, 9 Jul 2024 22:08:42 +0700 Subject: [PATCH 10/22] feat: revamp element tool bar --- .../Memo/ElementToolbar/ElementToolbar.swift | 38 +++++-- Memola/Features/Memo/Memo/MemoView.swift | 38 +++---- Memola/Features/Memo/PenDock/PenDock.swift | 105 ++++++++++++------ 3 files changed, 119 insertions(+), 62 deletions(-) diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index fa0c8d3..a1428d8 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -32,18 +32,10 @@ struct ElementToolbar: View { ZStack(alignment: .bottom) { if tool.selection == .photo { photoOption - .background { - RoundedRectangle(cornerRadius: 8) - .fill(.regularMaterial) - } - .padding(.bottom, 10) - .frame(maxWidth: .infinity) - .transition(.move(edge: .bottom).combined(with: .blurReplace)) } else { compactToolbar } } - .padding(.bottom, 10) } #endif } @@ -228,7 +220,6 @@ struct ElementToolbar: View { openCamera() } label: { Image(systemName: "camera.fill") - .contentShape(.circle) .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) @@ -237,7 +228,6 @@ struct ElementToolbar: View { #endif PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { Image(systemName: "photo.fill.on.rectangle.fill") - .contentShape(.circle) .frame(width: size, height: size) .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) @@ -247,7 +237,35 @@ struct ElementToolbar: View { #else .buttonStyle(.plain) #endif + if horizontalSizeClass == .compact { + Divider() + .padding(.vertical, 4) + .frame(height: size) + .foregroundStyle(Color.accentColor) + Button { + withAnimation { + tool.selectTool(.hand) + } + } label: { + Image(systemName: "xmark") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + #if os(iOS) + .hoverEffect(.lift) + #else + .buttonStyle(.plain) + #endif + } } + .background { + RoundedRectangle(cornerRadius: 8) + .fill(.regularMaterial) + } + .padding(.bottom, 10) + .frame(maxWidth: .infinity) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) } func openCamera() { diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 0ad9ad3..332d3df 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -108,25 +108,25 @@ struct MemoView: View { .transition(.move(edge: .bottom).combined(with: .blurReplace)) } } - .overlay(alignment: .bottom) { - if tool.selection != .hand { - Button { - withAnimation { - tool.selectTool(.hand) - } - } label: { - Image(systemName: "chevron.compact.down") - .font(.headline) - .frame(width: 80) - .padding(5) - .background(.regularMaterial) - .clipShape(.capsule) - .contentShape(.capsule) - } - .offset(y: 5) - .transition(.move(edge: .bottom).combined(with: .blurReplace)) - } - } +// .overlay(alignment: .bottom) { +// if tool.selection != .hand { +// Button { +// withAnimation { +// tool.selectTool(.hand) +// } +// } label: { +// Image(systemName: "chevron.compact.down") +// .font(.headline) +// .frame(width: 80) +// .padding(5) +// .background(.regularMaterial) +// .clipShape(.capsule) +// .contentShape(.capsule) +// } +// .offset(y: 5) +// .transition(.move(edge: .bottom).combined(with: .blurReplace)) +// } +// } } @ViewBuilder diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index 2ffeb30..94c8c61 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -14,10 +14,10 @@ struct PenDock: View { let size: CGFloat var width: CGFloat { - horizontalSizeClass == .compact ? 30 : 90 + horizontalSizeClass == .compact ? 25 : 90 } var height: CGFloat { - horizontalSizeClass == .compact ? 90 : 30 + horizontalSizeClass == .compact ? 75 : 30 } var factor: CGFloat = 0.9 @@ -55,6 +55,29 @@ struct PenDock: View { compactPenItemList .fixedSize(horizontal: false, vertical: true) compactPenPropertyTool + HStack(spacing: 5) { + Divider() + .padding(.vertical, 4) + .frame(height: size) + .foregroundStyle(Color.accentColor) + .frame(height: height * factor - 18) + Button { + withAnimation { + tool.selectTool(.hand) + } + } label: { + Image(systemName: "xmark") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + #if os(iOS) + .hoverEffect(.lift) + #else + .buttonStyle(.plain) + #endif + .frame(height: height * factor - 18) + } } .padding(.horizontal, 10) .clipped() @@ -63,8 +86,7 @@ struct PenDock: View { .fill(.regularMaterial) .frame(height: height * factor - 18) } - .padding(.horizontal, 10) - .padding(.bottom, 20) + .padding([.horizontal, .bottom], 10) .frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom) .frame(maxWidth: .infinity) } @@ -75,30 +97,35 @@ struct PenDock: View { @ViewBuilder var penItemList: some View { - ScrollViewReader { proxy in - ScrollView(.vertical, showsIndicators: false) { - LazyVStack(spacing: 0) { - ForEach(tool.pens) { pen in - penItem(pen) - .id(pen.id) - .scrollTransition { content, phase in - content - .scaleEffect(phase.isIdentity ? 1 : 0.04, anchor: .trailing) - } + VStack(alignment: .trailing, spacing: 0) { + ScrollViewReader { proxy in + ScrollView(.vertical, showsIndicators: false) { + LazyVStack(spacing: 0) { + ForEach(tool.pens) { pen in + penItem(pen) + .id(pen.id) + .scrollTransition { content, phase in + content + .scaleEffect(phase.isIdentity ? 1 : 0.04, anchor: .trailing) + } + } + } + .padding(.vertical, 10) + .id(refreshScrollId) + } + .onReceive(tool.scrollPublisher) { id in + DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { + withAnimation { + proxy.scrollTo(id) + } } } + } + newPenButton .padding(.vertical, 10) - .id(refreshScrollId) - } - .onReceive(tool.scrollPublisher) { id in - DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) { - withAnimation { - proxy.scrollTo(id) - } - } - } + .frame(width: width * factor - 18) } - .frame(maxHeight: ((height * factor + 10) * 6) + 20) + .frame(maxHeight: ((height * factor + 10) * 7) + 20) .fixedSize() .background(alignment: .trailing) { RoundedRectangle(cornerRadius: 8) @@ -106,10 +133,6 @@ struct PenDock: View { .frame(width: width * factor - 18) } .clipShape(.rect(cornerRadii: .init(bottomTrailing: 8, topTrailing: 8))) - .overlay(alignment: .bottomLeading) { - newPenButton - .offset(x: 15, y: 10) - } } @ViewBuilder @@ -126,7 +149,7 @@ struct PenDock: View { } } } - .padding(.horizontal, 10) + .padding(.horizontal, 5) .id(refreshScrollId) } .onReceive(tool.scrollPublisher) { id in @@ -235,7 +258,7 @@ struct PenDock: View { tool.selectPen(pen) } } - .padding(.horizontal, 10) + .padding(.horizontal, 5) .contextMenu(if: pen.strokeStyle != .eraser) { ControlGroup { Button { @@ -273,7 +296,7 @@ struct PenDock: View { } .controlGroupStyle(.menu) } preview: { - penPreview(pen) + compactPenPreview(pen) .drawingGroup() #if os(iOS) .contentShape(.contextMenuPreview, .rect(cornerRadius: 10)) @@ -284,7 +307,7 @@ struct PenDock: View { tool.draggedPen = pen return NSItemProvider(contentsOf: URL(string: pen.id)) ?? NSItemProvider() } preview: { - penPreview(pen) + compactPenPreview(pen) .contentShape(.dragPreview, .rect(cornerRadius: 10)) } .onDrop(of: [.item], delegate: PenDropDelegate(id: pen.id, tool: tool, action: { refreshScrollId = UUID() })) @@ -440,7 +463,7 @@ struct PenDock: View { createNewPen() } label: { Image(systemName: "plus.circle.fill") - .font(.title2) + .font(.headline) .padding(1) .contentShape(.circle) .background { @@ -472,6 +495,22 @@ struct PenDock: View { .padding(.leading, 10) } + func compactPenPreview(_ pen: Pen) -> some View { + ZStack { + if let tip = pen.style.compactIcon.tip { + Image(tip) + .resizable() + .renderingMode(.template) + .foregroundStyle(Color.rgba(from: pen.rgba)) + } + Image(pen.style.compactIcon.base) + .resizable() + } + .frame(width: width * factor, height: height * factor) + .padding(.top, 5) + .padding(.horizontal, 5) + } + func penShadow(_ pen: Pen) -> some View { ZStack { Group { From 742b32720061e7b5b7072ac24d5ae551d8259aae Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Thu, 11 Jul 2024 01:09:34 +0700 Subject: [PATCH 11/22] feat: refine canvas tool bar --- .../Memo/ElementToolbar/ElementToolbar.swift | 8 +- Memola/Features/Memo/Memo/MemoView.swift | 10 +- Memola/Features/Memo/PenDock/PenDock.swift | 116 +++++++++--------- Memola/Features/Memo/Toolbar/Toolbar.swift | 9 +- 4 files changed, 68 insertions(+), 75 deletions(-) diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index a1428d8..8a92378 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -11,7 +11,7 @@ import AVFoundation struct ElementToolbar: View { @Environment(\.horizontalSizeClass) var horizontalSizeClass - let size: CGFloat + let size: CGFloat = 40 @ObservedObject var tool: Tool @ObservedObject var canvas: Canvas @@ -32,6 +32,9 @@ struct ElementToolbar: View { ZStack(alignment: .bottom) { if tool.selection == .photo { photoOption + .padding(.bottom, 10) + .frame(maxWidth: .infinity) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) } else { compactToolbar } @@ -263,9 +266,6 @@ struct ElementToolbar: View { RoundedRectangle(cornerRadius: 8) .fill(.regularMaterial) } - .padding(.bottom, 10) - .frame(maxWidth: .infinity) - .transition(.move(edge: .bottom).combined(with: .blurReplace)) } func openCamera() { diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 332d3df..885baa6 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -19,7 +19,7 @@ struct MemoView: View { @FocusState var textFieldState: Bool let memo: MemoObject - let size: CGFloat = 32 + let size: CGFloat = 40 init(memo: MemoObject) { self.memo = memo @@ -42,7 +42,7 @@ struct MemoView: View { #endif } .overlay(alignment: .top) { - Toolbar(size: size, memo: memo, tool: tool, canvas: canvas, history: history) + Toolbar(memo: memo, tool: tool, canvas: canvas, history: history) } .disabled(textFieldState || tool.isLoadingPhoto) .disabled(canvas.state == .loading || canvas.state == .closing) @@ -69,7 +69,7 @@ struct MemoView: View { .overlay(alignment: .bottomTrailing) { switch tool.selection { case .pen: - PenDock(tool: tool, canvas: canvas, size: size) + PenDock(tool: tool, canvas: canvas) case .photo: if let photoItem = tool.selectedPhotoItem { PhotoPreview(photoItem: photoItem, tool: tool) @@ -90,7 +90,7 @@ struct MemoView: View { .overlay(alignment: .bottom) { switch tool.selection { case .pen: - PenDock(tool: tool, canvas: canvas, size: size) + PenDock(tool: tool, canvas: canvas) .transition(.move(edge: .bottom).combined(with: .blurReplace)) case .photo: if let photoItem = tool.selectedPhotoItem { @@ -104,7 +104,7 @@ struct MemoView: View { } .overlay(alignment: .bottom) { if tool.selection != .pen { - ElementToolbar(size: size, tool: tool, canvas: canvas) + ElementToolbar(tool: tool, canvas: canvas) .transition(.move(edge: .bottom).combined(with: .blurReplace)) } } diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index 94c8c61..aa1a9b6 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -12,14 +12,14 @@ struct PenDock: View { @ObservedObject var tool: Tool @ObservedObject var canvas: Canvas - let size: CGFloat + let size: CGFloat = 40 + let penPropertySize: CGFloat = 32 var width: CGFloat { - horizontalSizeClass == .compact ? 25 : 90 + horizontalSizeClass == .compact ? size / 2 : size } var height: CGFloat { - horizontalSizeClass == .compact ? 75 : 30 + horizontalSizeClass == .compact ? size : size / 2 } - var factor: CGFloat = 0.9 @State var refreshScrollId: UUID = UUID() @State var opensColorPicker: Bool = false @@ -29,38 +29,45 @@ struct PenDock: View { var body: some View { #if os(macOS) - VStack(alignment: .trailing) { - penPropertyTool - penItemList + GeometryReader { proxy in + VStack(alignment: .trailing, spacing: 5) { + penPropertyTool + penItemList + .frame(maxWidth: proxy.size.width * 0.4) + } + .fixedSize() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing) } - .fixedSize() - .frame(maxHeight: .infinity) .padding(10) .transition(.move(edge: .trailing).combined(with: .blurReplace)) #else if horizontalSizeClass == .regular { - VStack(alignment: .trailing) { - penPropertyTool - penItemList + GeometryReader { proxy in + VStack(alignment: .trailing, spacing: 5) { + penPropertyTool + penItemList + .frame(maxHeight: proxy.size.height * 0.4) + } + .fixedSize() + .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing) } - .fixedSize() - .frame(maxHeight: .infinity) .padding(10) .transition(.move(edge: .trailing).combined(with: .blurReplace)) } else { GeometryReader { proxy in HStack(alignment: .bottom, spacing: 10) { newPenButton - .frame(height: height * factor - 18) + .padding(.leading, 10) + .frame(height: height) compactPenItemList .fixedSize(horizontal: false, vertical: true) - compactPenPropertyTool - HStack(spacing: 5) { + HStack(spacing: 0) { + compactPenPropertyTool Divider() .padding(.vertical, 4) .frame(height: size) .foregroundStyle(Color.accentColor) - .frame(height: height * factor - 18) + .padding(.leading, 8) Button { withAnimation { tool.selectTool(.hand) @@ -76,15 +83,13 @@ struct PenDock: View { #else .buttonStyle(.plain) #endif - .frame(height: height * factor - 18) } } - .padding(.horizontal, 10) .clipped() .background(alignment: .bottom) { RoundedRectangle(cornerRadius: 8) .fill(.regularMaterial) - .frame(height: height * factor - 18) + .frame(height: height) } .padding([.horizontal, .bottom], 10) .frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom) @@ -100,7 +105,7 @@ struct PenDock: View { VStack(alignment: .trailing, spacing: 0) { ScrollViewReader { proxy in ScrollView(.vertical, showsIndicators: false) { - LazyVStack(spacing: 0) { + LazyVStack(spacing: 5) { ForEach(tool.pens) { pen in penItem(pen) .id(pen.id) @@ -110,7 +115,7 @@ struct PenDock: View { } } } - .padding(.vertical, 10) + .padding(.vertical, 5) .id(refreshScrollId) } .onReceive(tool.scrollPublisher) { id in @@ -122,17 +127,14 @@ struct PenDock: View { } } newPenButton - .padding(.vertical, 10) - .frame(width: width * factor - 18) + .padding(.vertical, 5) + .frame(width: width) } - .frame(maxHeight: ((height * factor + 10) * 7) + 20) - .fixedSize() .background(alignment: .trailing) { RoundedRectangle(cornerRadius: 8) .fill(.regularMaterial) - .frame(width: width * factor - 18) + .frame(width: width) } - .clipShape(.rect(cornerRadii: .init(bottomTrailing: 8, topTrailing: 8))) } @ViewBuilder @@ -174,15 +176,10 @@ struct PenDock: View { Image(pen.style.icon.base) .resizable() } - .frame(width: width * factor, height: height * factor) + .frame(width: width * 1.2, height: height * 0.9) .padding(.vertical, 5) - .contentShape(.rect(cornerRadii: .init(topLeading: 10, bottomLeading: 10))) - .onTapGesture { - if tool.selectedPen !== pen { - tool.selectPen(pen) - } - } .padding(.leading, 10) + .contentShape(.rect) .contextMenu(if: pen.strokeStyle != .eraser) { ControlGroup { Button { @@ -235,7 +232,13 @@ struct PenDock: View { .contentShape(.dragPreview, .rect(cornerRadius: 10)) } .onDrop(of: [.item], delegate: PenDropDelegate(id: pen.id, tool: tool, action: { refreshScrollId = UUID() })) - .offset(x: tool.selectedPen === pen ? 0 : 25) + .offset(x: tool.selectedPen === pen ? 0 : 16) + .contentShape(.rect) + .onTapGesture { + if tool.selectedPen !== pen { + tool.selectPen(pen) + } + } } func compactPenItem(_ pen: Pen) -> some View { @@ -250,7 +253,7 @@ struct PenDock: View { Image(pen.style.compactIcon.base) .resizable() } - .frame(width: width * factor, height: height * factor) + .frame(width: width * 0.9, height: height * 1.2) .padding(.top, 5) .contentShape(.rect(cornerRadii: .init(topLeading: 10, bottomLeading: 10))) .onTapGesture { @@ -258,7 +261,7 @@ struct PenDock: View { tool.selectPen(pen) } } - .padding(.horizontal, 5) + .padding(.horizontal, 6) .contextMenu(if: pen.strokeStyle != .eraser) { ControlGroup { Button { @@ -311,7 +314,7 @@ struct PenDock: View { .contentShape(.dragPreview, .rect(cornerRadius: 10)) } .onDrop(of: [.item], delegate: PenDropDelegate(id: pen.id, tool: tool, action: { refreshScrollId = UUID() })) - .offset(y: tool.selectedPen === pen ? 0 : 25) + .offset(y: tool.selectedPen === pen ? 0 : 16) } @ViewBuilder @@ -323,32 +326,32 @@ struct PenDock: View { } penThicknessPicker(pen) } - .padding(10) - .frame(width: width * factor - 18) + .padding(.vertical, 5) + .frame(width: width) .background { RoundedRectangle(cornerRadius: 8) .fill(.regularMaterial) } } else { Color.clear - .frame(width: width * factor - 18, height: 50) + .frame(width: width, height: 50) } } @ViewBuilder var compactPenPropertyTool: some View { if let pen = tool.selectedPen { - HStack(spacing: 10) { + HStack(spacing: 8) { penThicknessPicker(pen) - .frame(width: size) + .frame(width: penPropertySize) .rotationEffect(.degrees(-90)) if pen.strokeStyle == .marker { penColorPicker(pen) - .frame(width: size) + .frame(width: penPropertySize) .transition(.move(edge: .trailing).combined(with: .opacity)) } } - .frame(height: height * factor - 18) + .frame(height: height) } } @@ -374,7 +377,7 @@ struct PenDock: View { .background(baseColor) .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) - .frame(height: size) + .frame(width: penPropertySize, height: penPropertySize) .overlay { RoundedRectangle(cornerRadius: 8) .stroke(Color.gray, lineWidth: 0.4) @@ -417,11 +420,6 @@ struct PenDock: View { tool.objectWillChange.send() } ) - #if os(macOS) - let _width = width * factor - 38 - #else - let _width = horizontalSizeClass == .compact ? self.size : width * factor - 38 - #endif ScrollViewReader { proxy in ScrollView(showsIndicators: false) { LazyVStack(spacing: 0) { @@ -430,17 +428,13 @@ struct PenDock: View { Circle() .foregroundStyle(.primary) .frame(width: size, height: size) - .frame(width: _width, height: self.size) + .frame(width: penPropertySize, height: penPropertySize) .contentShape(.rect) .id(step) } } } - #if os(macOS) - .frame(height: size) - #else - .frame(width: _width, height: size) - #endif + .frame(width: penPropertySize, height: penPropertySize) .background(.gray.quaternary) .clipShape(.rect(cornerRadius: 8)) .scrollPosition(id: selection, anchor: .center) @@ -490,7 +484,7 @@ struct PenDock: View { Image(pen.style.icon.base) .resizable() } - .frame(width: width * factor, height: height * factor) + .frame(width: width * 1.2, height: height * 0.9) .padding(.vertical, 5) .padding(.leading, 10) } @@ -506,7 +500,7 @@ struct PenDock: View { Image(pen.style.compactIcon.base) .resizable() } - .frame(width: width * factor, height: height * factor) + .frame(width: width * 0.9, height: height * 1.2) .padding(.top, 5) .padding(.horizontal, 5) } diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index ce6bb34..4022cef 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -20,11 +20,10 @@ struct Toolbar: View { @FocusState var textFieldState: Bool - let size: CGFloat + let size: CGFloat = 40 let memo: MemoObject - init(size: CGFloat, memo: MemoObject, tool: Tool, canvas: Canvas, history: History) { - self.size = size + init(memo: MemoObject, tool: Tool, canvas: Canvas, history: History) { self.memo = memo self.tool = tool self.canvas = canvas @@ -40,10 +39,10 @@ struct Toolbar: View { } .frame(maxWidth: .infinity, alignment: .leading) #if os(macOS) - ElementToolbar(size: size, tool: tool, canvas: canvas) + ElementToolbar(tool: tool, canvas: canvas) #else if horizontalSizeClass == .regular { - ElementToolbar(size: size, tool: tool, canvas: canvas) + ElementToolbar(tool: tool, canvas: canvas) } #endif HStack(spacing: 5) { From 65311c6ec94bed5a3837d86c3fe5e6372331e504 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Thu, 11 Jul 2024 20:26:26 +0700 Subject: [PATCH 12/22] feat: add dark mode support for ipad --- Memola.xcodeproj/project.pbxproj | 12 ++ .../Memo/ElementToolbar/ElementToolbar.swift | 142 +------------ Memola/Features/Memo/Memo/MemoView.swift | 11 +- Memola/Features/Memo/PenDock/PenDock.swift | 1 + .../Features/Memo/PhotoDock/PhotoDock.swift | 198 ++++++++++++++++++ .../AccentColor.colorset/Contents.json | 27 +++ 6 files changed, 253 insertions(+), 138 deletions(-) create mode 100644 Memola/Features/Memo/PhotoDock/PhotoDock.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 53aee4e..45a81fc 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -38,6 +38,7 @@ EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; }; EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; }; EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; }; + EC86C5822C4010CC00C07D21 /* PhotoDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC86C5812C4010CC00C07D21 /* PhotoDock.swift */; }; EC8C9DCE2C39882500A8F3C4 /* NSSyncScrollView.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8C9DCD2C39882500A8F3C4 /* NSSyncScrollView.swift */; }; EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */; }; EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */; }; @@ -164,6 +165,7 @@ EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = ""; }; EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = ""; }; + EC86C5812C4010CC00C07D21 /* PhotoDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoDock.swift; sourceTree = ""; }; EC8C9DCD2C39882500A8F3C4 /* NSSyncScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSSyncScrollView.swift; sourceTree = ""; }; EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridMode.swift; sourceTree = ""; }; EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridVertex.swift; sourceTree = ""; }; @@ -486,6 +488,14 @@ path = "Preview Content"; sourceTree = ""; }; + EC86C5802C4010BE00C07D21 /* PhotoDock */ = { + isa = PBXGroup; + children = ( + EC86C5812C4010CC00C07D21 /* PhotoDock.swift */, + ); + path = PhotoDock; + sourceTree = ""; + }; EC8C9DCC2C3987FD00A8F3C4 /* AppKit */ = { isa = PBXGroup; children = ( @@ -533,6 +543,7 @@ ECA7387B2BE5EF3500A4542E /* Memo */ = { isa = PBXGroup; children = ( + EC86C5802C4010BE00C07D21 /* PhotoDock */, ECDAC0792C318DAF0000ED77 /* ElementToolbar */, ECBE52942C1D58F5006BDB3D /* PhotoPreview */, EC1B783B2BFA0AAC005A34E2 /* Toolbar */, @@ -1029,6 +1040,7 @@ ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */, ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */, ECA7387A2BE5EF0400A4542E /* MemosView.swift in Sources */, + EC86C5822C4010CC00C07D21 /* PhotoDock.swift in Sources */, ECA738BA2BE60DEF00A4542E /* HistoryAction.swift in Sources */, ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */, ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */, diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index 8a92378..9ecd486 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -10,17 +10,13 @@ import PhotosUI import AVFoundation struct ElementToolbar: View { + @Environment(\.colorScheme) var colorScheme @Environment(\.horizontalSizeClass) var horizontalSizeClass + let size: CGFloat = 40 @ObservedObject var tool: Tool @ObservedObject var canvas: Canvas - @State var opensCamera: Bool = false - @State var isCameraAccessDenied: Bool = false - @State var photosPickerItem: PhotosPickerItem? - - @Namespace var namespace - var body: some View { Group { #if os(macOS) @@ -31,10 +27,7 @@ struct ElementToolbar: View { } else { ZStack(alignment: .bottom) { if tool.selection == .photo { - photoOption - .padding(.bottom, 10) - .frame(maxWidth: .infinity) - .transition(.move(edge: .bottom).combined(with: .blurReplace)) + PhotoDock(tool: tool, canvas: canvas) } else { compactToolbar } @@ -42,42 +35,7 @@ struct ElementToolbar: View { } #endif } - #if os(iOS) - .fullScreenCover(isPresented: $opensCamera) { - let image: Binding = Binding { - tool.selectedPhotoItem?.image - } set: { image in - guard let image else { return } - tool.selectPhoto(image, for: canvas.canvasID) - } - CameraView(image: image, canvas: canvas) - .ignoresSafeArea() - } - .alert("Camera Access Denied", isPresented: $isCameraAccessDenied) { - Button { - if let url = URL(string: Platform.Application.openSettingsURLString + "&path=CAMERA/\(String(describing: Bundle.main.bundleIdentifier))") { - Platform.Application.shared.open(url) - } - } label: { - Text("Open Settings") - } - Button("Cancel", role: .cancel) { } - } message: { - Text("Memola requires access to the camera to capture photos. Please open Settings and enable camera access.") - } - #endif - .onChange(of: photosPickerItem) { oldValue, newValue in - if newValue != nil { - Task { - tool.isLoadingPhoto = true - let data = try? await newValue?.loadTransferable(type: Data.self) - if let data, let image = Platform.Image(data: data) { - tool.selectPhoto(image, for: canvas.canvasID) - } - photosPickerItem = nil - } - } - } + } var regularToolbar: some View { @@ -91,7 +49,7 @@ struct ElementToolbar: View { .fontWeight(.heavy) .contentShape(.circle) .frame(width: size, height: size) - .foregroundStyle(tool.selection == .hand ? Color.white : Color.accentColor) + .foregroundStyle(tool.selection == .hand ? colorScheme == .light ? Color.white : Color.black : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) } @@ -104,7 +62,6 @@ struct ElementToolbar: View { if tool.selection == .hand { Color.accentColor .clipShape(.rect(cornerRadius: 8)) - .matchedGeometryEffect(id: "element.toolbar.bg", in: namespace) } } Button { @@ -116,7 +73,7 @@ struct ElementToolbar: View { .fontWeight(.heavy) .contentShape(.circle) .frame(width: size, height: size) - .foregroundStyle(tool.selection == .pen ? Color.white : Color.accentColor) + .foregroundStyle(tool.selection == .pen ? colorScheme == .light ? Color.white : Color.black : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) } @@ -129,7 +86,6 @@ struct ElementToolbar: View { if tool.selection == .pen { Color.accentColor .clipShape(.rect(cornerRadius: 8)) - .matchedGeometryEffect(id: "element.toolbar.bg", in: namespace) } } HStack(spacing: 0) { @@ -141,7 +97,7 @@ struct ElementToolbar: View { Image(systemName: "photo") .contentShape(.circle) .frame(width: size, height: size) - .foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor) + .foregroundStyle(tool.selection == .photo ? colorScheme == .light ? Color.white : Color.black : Color.accentColor) .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) } @@ -151,22 +107,10 @@ struct ElementToolbar: View { .buttonStyle(.plain) #endif .background { - #if os(iOS) if tool.selection == .photo { Color.accentColor .clipShape(.rect(cornerRadius: 8)) - .matchedGeometryEffect(id: "element.toolbar.bg", in: namespace) } - #endif - if tool.selection != .photo { - Color.clear - .matchedGeometryEffect(id: "element.toolbar.photo.options", in: namespace) - } - } - if tool.selection == .photo { - photoOption - .matchedGeometryEffect(id: "element.toolbar.photo.options", in: namespace) - .transition(.blurReplace.animation(.easeIn(duration: 0.1))) } } } @@ -216,75 +160,5 @@ struct ElementToolbar: View { .transition(.move(edge: .bottom).combined(with: .blurReplace)) } - var photoOption: some View { - HStack(spacing: 0) { - #if os(iOS) - Button { - openCamera() - } label: { - Image(systemName: "camera.fill") - .frame(width: size, height: size) - .clipShape(.rect(cornerRadius: 8)) - .contentShape(.rect(cornerRadius: 8)) - } - .hoverEffect(.lift) - #endif - PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { - Image(systemName: "photo.fill.on.rectangle.fill") - .frame(width: size, height: size) - .clipShape(.rect(cornerRadius: 8)) - .contentShape(.rect(cornerRadius: 8)) - } - #if os(iOS) - .hoverEffect(.lift) - #else - .buttonStyle(.plain) - #endif - if horizontalSizeClass == .compact { - Divider() - .padding(.vertical, 4) - .frame(height: size) - .foregroundStyle(Color.accentColor) - Button { - withAnimation { - tool.selectTool(.hand) - } - } label: { - Image(systemName: "xmark") - .frame(width: size, height: size) - .clipShape(.rect(cornerRadius: 8)) - .contentShape(.rect(cornerRadius: 8)) - } - #if os(iOS) - .hoverEffect(.lift) - #else - .buttonStyle(.plain) - #endif - } - } - .background { - RoundedRectangle(cornerRadius: 8) - .fill(.regularMaterial) - } - } - - func openCamera() { - let status = AVCaptureDevice.authorizationStatus(for: .video) - switch status { - case .notDetermined: - AVCaptureDevice.requestAccess(for: .video) { status in - withAnimation { - if status { - opensCamera = true - } else { - isCameraAccessDenied = true - } - } - } - case .authorized: - opensCamera = true - default: - isCameraAccessDenied = true - } - } + } diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 885baa6..6152646 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -66,14 +66,17 @@ struct MemoView: View { var canvasView: some View { CanvasView(tool: tool, canvas: canvas, history: history) .ignoresSafeArea() - .overlay(alignment: .bottomTrailing) { + .overlay(alignment: .trailing) { switch tool.selection { case .pen: PenDock(tool: tool, canvas: canvas) case .photo: - if let photoItem = tool.selectedPhotoItem { - PhotoPreview(photoItem: photoItem, tool: tool) - .transition(.move(edge: .trailing).combined(with: .blurReplace)) + ZStack(alignment: .bottomTrailing) { + PhotoDock(tool: tool, canvas: canvas) + if let photoItem = tool.selectedPhotoItem { + PhotoPreview(photoItem: photoItem, tool: tool) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) + } } default: EmptyView() diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index aa1a9b6..0f2d2e0 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -130,6 +130,7 @@ struct PenDock: View { .padding(.vertical, 5) .frame(width: width) } + .padding(.vertical, 3) .background(alignment: .trailing) { RoundedRectangle(cornerRadius: 8) .fill(.regularMaterial) diff --git a/Memola/Features/Memo/PhotoDock/PhotoDock.swift b/Memola/Features/Memo/PhotoDock/PhotoDock.swift new file mode 100644 index 0000000..5c8db9a --- /dev/null +++ b/Memola/Features/Memo/PhotoDock/PhotoDock.swift @@ -0,0 +1,198 @@ +// +// PhotoDock.swift +// Memola +// +// Created by Dscyre Scotti on 7/11/24. +// + +import SwiftUI +import PhotosUI + +struct PhotoDock: View { + @Environment(\.horizontalSizeClass) var horizontalSizeClass + + let size: CGFloat = 40 + + @ObservedObject var tool: Tool + @ObservedObject var canvas: Canvas + + @State var opensCamera: Bool = false + @State var isCameraAccessDenied: Bool = false + @State var photosPickerItem: PhotosPickerItem? + + var body: some View { + Group { + if horizontalSizeClass == .regular { + photoOption + } else { + compactPhotoOption + } + } + #if os(iOS) + .fullScreenCover(isPresented: $opensCamera) { + let image: Binding = Binding { + tool.selectedPhotoItem?.image + } set: { image in + guard let image else { return } + tool.selectPhoto(image, for: canvas.canvasID) + } + CameraView(image: image, canvas: canvas) + .ignoresSafeArea() + } + .alert("Camera Access Denied", isPresented: $isCameraAccessDenied) { + Button { + if let url = URL(string: Platform.Application.openSettingsURLString + "&path=CAMERA/\(String(describing: Bundle.main.bundleIdentifier))") { + Platform.Application.shared.open(url) + } + } label: { + Text("Open Settings") + } + Button("Cancel", role: .cancel) { } + } message: { + Text("Memola requires access to the camera to capture photos. Please open Settings and enable camera access.") + } + #endif + .onChange(of: photosPickerItem) { oldValue, newValue in + if newValue != nil { + Task { + tool.isLoadingPhoto = true + let data = try? await newValue?.loadTransferable(type: Data.self) + if let data, let image = Platform.Image(data: data) { + tool.selectPhoto(image, for: canvas.canvasID) + } + photosPickerItem = nil + } + } + } + } + + var photoOption: some View { + VStack(spacing: 0) { + #if os(iOS) + Button { + openCamera() + } label: { + Image(systemName: "camera.fill") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + .hoverEffect(.lift) + #endif + PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { + Image(systemName: "photo.fill.on.rectangle.fill") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + #if os(iOS) + .hoverEffect(.lift) + #else + .buttonStyle(.plain) + #endif + if horizontalSizeClass == .compact { + Divider() + .padding(.vertical, 4) + .frame(height: size) + .foregroundStyle(Color.accentColor) + Button { + withAnimation { + tool.selectTool(.hand) + } + } label: { + Image(systemName: "xmark") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + #if os(iOS) + .hoverEffect(.lift) + #else + .buttonStyle(.plain) + #endif + } + } + .background { + RoundedRectangle(cornerRadius: 8) + .fill(.regularMaterial) + } + .padding(.trailing, 10) + .frame(maxHeight: .infinity) + .transition(.move(edge: .trailing).combined(with: .blurReplace)) + } + + var compactPhotoOption: some View { + HStack(spacing: 0) { + #if os(iOS) + Button { + openCamera() + } label: { + Image(systemName: "camera.fill") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + .hoverEffect(.lift) + #endif + PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) { + Image(systemName: "photo.fill.on.rectangle.fill") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + #if os(iOS) + .hoverEffect(.lift) + #else + .buttonStyle(.plain) + #endif + if horizontalSizeClass == .compact { + Divider() + .padding(.vertical, 4) + .frame(height: size) + .foregroundStyle(Color.accentColor) + Button { + withAnimation { + tool.selectTool(.hand) + } + } label: { + Image(systemName: "xmark") + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + .contentShape(.rect(cornerRadius: 8)) + } + #if os(iOS) + .hoverEffect(.lift) + #else + .buttonStyle(.plain) + #endif + } + } + .background { + RoundedRectangle(cornerRadius: 8) + .fill(.regularMaterial) + } + .padding(.bottom, 10) + .frame(maxWidth: .infinity) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) + } + + func openCamera() { + let status = AVCaptureDevice.authorizationStatus(for: .video) + switch status { + case .notDetermined: + AVCaptureDevice.requestAccess(for: .video) { status in + withAnimation { + if status { + opensCamera = true + } else { + isCameraAccessDenied = true + } + } + } + case .authorized: + opensCamera = true + default: + isCameraAccessDenied = true + } + } +} diff --git a/Memola/Resources/Assets/Assets.xcassets/AccentColor.colorset/Contents.json b/Memola/Resources/Assets/Assets.xcassets/AccentColor.colorset/Contents.json index eb87897..462f7c0 100644 --- a/Memola/Resources/Assets/Assets.xcassets/AccentColor.colorset/Contents.json +++ b/Memola/Resources/Assets/Assets.xcassets/AccentColor.colorset/Contents.json @@ -1,6 +1,33 @@ { "colors" : [ { + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xF5", + "green" : "0x7E", + "red" : "0x00" + } + }, + "idiom" : "universal" + }, + { + "appearances" : [ + { + "appearance" : "luminosity", + "value" : "dark" + } + ], + "color" : { + "color-space" : "extended-srgb", + "components" : { + "alpha" : "1.000", + "blue" : "0xFF", + "green" : "0xCE", + "red" : "0x99" + } + }, "idiom" : "universal" } ], From 007d4ab755b83441e531dc7ccf2b715c64615ee2 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Thu, 11 Jul 2024 21:09:57 +0700 Subject: [PATCH 13/22] feat: update accent color for macos --- .../Memo/ElementToolbar/ElementToolbar.swift | 5 ++++- Memola/Features/Memo/Memo/MemoView.swift | 21 ++----------------- .../Features/Memo/PhotoDock/PhotoDock.swift | 5 +++++ Memola/Features/Memo/Toolbar/Toolbar.swift | 2 ++ 4 files changed, 13 insertions(+), 20 deletions(-) diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index 9ecd486..9c352b6 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -28,6 +28,9 @@ struct ElementToolbar: View { ZStack(alignment: .bottom) { if tool.selection == .photo { PhotoDock(tool: tool, canvas: canvas) + .padding(.bottom, 10) + .frame(maxWidth: .infinity) + .transition(.move(edge: .bottom).combined(with: .blurReplace)) } else { compactToolbar } @@ -35,7 +38,7 @@ struct ElementToolbar: View { } #endif } - + .foregroundStyle(Color.accentColor) } var regularToolbar: some View { diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 6152646..1891047 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -78,6 +78,7 @@ struct MemoView: View { .transition(.move(edge: .trailing).combined(with: .blurReplace)) } } + .frame(maxWidth: .infinity, alignment: .trailing) default: EmptyView() } @@ -111,25 +112,6 @@ struct MemoView: View { .transition(.move(edge: .bottom).combined(with: .blurReplace)) } } -// .overlay(alignment: .bottom) { -// if tool.selection != .hand { -// Button { -// withAnimation { -// tool.selectTool(.hand) -// } -// } label: { -// Image(systemName: "chevron.compact.down") -// .font(.headline) -// .frame(width: 80) -// .padding(5) -// .background(.regularMaterial) -// .clipShape(.capsule) -// .contentShape(.capsule) -// } -// .offset(y: 5) -// .transition(.move(edge: .bottom).combined(with: .blurReplace)) -// } -// } } @ViewBuilder @@ -157,6 +139,7 @@ struct MemoView: View { } } label: { Text(zoomScale / 100, format: .percent) + .foregroundStyle(Color.accentColor) .font(.subheadline) .frame(height: size) .clipShape(.rect(cornerRadius: 8)) diff --git a/Memola/Features/Memo/PhotoDock/PhotoDock.swift b/Memola/Features/Memo/PhotoDock/PhotoDock.swift index 5c8db9a..bdc0983 100644 --- a/Memola/Features/Memo/PhotoDock/PhotoDock.swift +++ b/Memola/Features/Memo/PhotoDock/PhotoDock.swift @@ -22,12 +22,17 @@ struct PhotoDock: View { var body: some View { Group { + #if os(macOS) + photoOption + #else if horizontalSizeClass == .regular { photoOption } else { compactPhotoOption } + #endif } + .foregroundStyle(Color.accentColor) #if os(iOS) .fullScreenCover(isPresented: $opensCamera) { let image: Binding = Binding { diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 4022cef..b6f0d59 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -36,6 +36,7 @@ struct Toolbar: View { HStack(spacing: 5) { closeButton titleField + .foregroundStyle(Color.primary) } .frame(maxWidth: .infinity, alignment: .leading) #if os(macOS) @@ -53,6 +54,7 @@ struct Toolbar: View { } .font(.subheadline) .padding(10) + .foregroundStyle(Color.accentColor) } var closeButton: some View { From c2aa087a556e50cbb345d8fb3677297c4d2749f0 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Fri, 12 Jul 2024 22:02:28 +0700 Subject: [PATCH 14/22] feat: add new memo shortcut --- Memola.xcodeproj/project.pbxproj | 82 ++++++++++++++++++- Memola/App/AppDelegate.swift | 19 +++++ Memola/App/MemolaApp.swift | 10 +++ .../Geometries/Stroke/Strokes/PenStroke.swift | 4 +- .../Dashboard/Dashboard/MemoManager.swift | 1 + .../Dashboard/Details/Memos/MemosView.swift | 16 ++++ .../Dashboard/Details/Trash/TrashView.swift | 2 + Memola/Persistence/Core/Persistence.swift | 7 ++ Memola/Persistence/Objects/PenObject.swift | 2 +- Memola/Shortcut/Commands/AppCommands.swift | 21 +++++ Memola/Shortcut/Commands/EditCommands.swift | 30 +++++++ Memola/Shortcut/Commands/FileCommands.swift | 26 ++++++ Memola/Shortcut/Commands/ViewCommands.swift | 14 ++++ Memola/Shortcut/Core/Shortcut.swift | 25 ++++++ Memola/Shortcut/Core/Shortcuts.swift | 14 ++++ .../EnvironmentKeys/ShortcutKey.swift | 18 ++++ Memola/Utilies/AppScene/ActiveSceneKey.swift | 19 +++++ Memola/Utilies/AppScene/AppScene.swift | 14 ++++ 18 files changed, 321 insertions(+), 3 deletions(-) create mode 100644 Memola/App/AppDelegate.swift create mode 100644 Memola/Shortcut/Commands/AppCommands.swift create mode 100644 Memola/Shortcut/Commands/EditCommands.swift create mode 100644 Memola/Shortcut/Commands/FileCommands.swift create mode 100644 Memola/Shortcut/Commands/ViewCommands.swift create mode 100644 Memola/Shortcut/Core/Shortcut.swift create mode 100644 Memola/Shortcut/Core/Shortcuts.swift create mode 100644 Memola/Shortcut/EnvironmentKeys/ShortcutKey.swift create mode 100644 Memola/Utilies/AppScene/ActiveSceneKey.swift create mode 100644 Memola/Utilies/AppScene/AppScene.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 45a81fc..039dc18 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -22,6 +22,16 @@ EC18150A2C2DA09E00541369 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1815092C2DA09E00541369 /* Filter.swift */; }; EC18150D2C2DAC3700541369 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC18150C2C2DAC3700541369 /* Placeholder.swift */; }; EC1B783D2BFA0AC9005A34E2 /* Toolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1B783C2BFA0AC9005A34E2 /* Toolbar.swift */; }; + EC2002D52C416033002EBD5F /* FileCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D42C416033002EBD5F /* FileCommands.swift */; }; + EC2002D72C4160EF002EBD5F /* EditCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D62C4160EF002EBD5F /* EditCommands.swift */; }; + EC2002D92C4161ED002EBD5F /* ViewCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D82C4161ED002EBD5F /* ViewCommands.swift */; }; + EC2002DB2C4162E5002EBD5F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */; }; + EC2002DD2C4163E8002EBD5F /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002DC2C4163E8002EBD5F /* AppCommands.swift */; }; + EC2002E12C416470002EBD5F /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002E02C416470002EBD5F /* Shortcut.swift */; }; + EC2002E52C416551002EBD5F /* Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002E42C416551002EBD5F /* Shortcuts.swift */; }; + EC2002E92C4167C5002EBD5F /* ShortcutKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002E82C4167C5002EBD5F /* ShortcutKey.swift */; }; + EC2002ED2C417B68002EBD5F /* AppScene.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002EC2C417B68002EBD5F /* AppScene.swift */; }; + EC2002F02C417BF1002EBD5F /* ActiveSceneKey.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002EF2C417BF1002EBD5F /* ActiveSceneKey.swift */; }; EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */; }; EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */; }; EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF52C0F600D005DB0AF /* Box.swift */; }; @@ -147,6 +157,16 @@ EC1815092C2DA09E00541369 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = ""; }; EC18150C2C2DAC3700541369 /* Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = ""; }; EC1B783C2BFA0AC9005A34E2 /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = ""; }; + EC2002D42C416033002EBD5F /* FileCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCommands.swift; sourceTree = ""; }; + EC2002D62C4160EF002EBD5F /* EditCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCommands.swift; sourceTree = ""; }; + EC2002D82C4161ED002EBD5F /* ViewCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCommands.swift; sourceTree = ""; }; + EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; + EC2002DC2C4163E8002EBD5F /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = ""; }; + EC2002E02C416470002EBD5F /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = ""; }; + EC2002E42C416551002EBD5F /* Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcuts.swift; sourceTree = ""; }; + EC2002E82C4167C5002EBD5F /* ShortcutKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutKey.swift; sourceTree = ""; }; + EC2002EC2C417B68002EBD5F /* AppScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScene.swift; sourceTree = ""; }; + EC2002EF2C417BF1002EBD5F /* ActiveSceneKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSceneKey.swift; sourceTree = ""; }; EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyStroke.swift; sourceTree = ""; }; EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = ""; }; EC2BEBF52C0F600D005DB0AF /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = ""; }; @@ -369,6 +389,53 @@ path = Toolbar; sourceTree = ""; }; + EC2002D32C416002002EBD5F /* Commands */ = { + isa = PBXGroup; + children = ( + EC2002DC2C4163E8002EBD5F /* AppCommands.swift */, + EC2002D62C4160EF002EBD5F /* EditCommands.swift */, + EC2002D42C416033002EBD5F /* FileCommands.swift */, + EC2002D82C4161ED002EBD5F /* ViewCommands.swift */, + ); + path = Commands; + sourceTree = ""; + }; + EC2002DE2C41645A002EBD5F /* Shortcut */ = { + isa = PBXGroup; + children = ( + EC2002D32C416002002EBD5F /* Commands */, + EC2002DF2C416466002EBD5F /* Core */, + EC2002E72C4167B1002EBD5F /* EnvironmentKeys */, + ); + path = Shortcut; + sourceTree = ""; + }; + EC2002DF2C416466002EBD5F /* Core */ = { + isa = PBXGroup; + children = ( + EC2002E02C416470002EBD5F /* Shortcut.swift */, + EC2002E42C416551002EBD5F /* Shortcuts.swift */, + ); + path = Core; + sourceTree = ""; + }; + EC2002E72C4167B1002EBD5F /* EnvironmentKeys */ = { + isa = PBXGroup; + children = ( + EC2002E82C4167C5002EBD5F /* ShortcutKey.swift */, + ); + path = EnvironmentKeys; + sourceTree = ""; + }; + EC2002EE2C417BBF002EBD5F /* AppScene */ = { + isa = PBXGroup; + children = ( + EC2002EC2C417B68002EBD5F /* AppScene.swift */, + EC2002EF2C417BF1002EBD5F /* ActiveSceneKey.swift */, + ); + path = AppScene; + sourceTree = ""; + }; EC2BEBF22C0F5FE1005DB0AF /* RTree */ = { isa = PBXGroup; children = ( @@ -466,12 +533,13 @@ EC7F6BEA2BE5E6E300A34A7B /* Memola */ = { isa = PBXGroup; children = ( - ECF7B2E82C395A8E004D2C57 /* Memola.entitlements */, ECA738762BE5EE4E00A4542E /* App */, + EC2002DE2C41645A002EBD5F /* Shortcut */, ECA7387E2BE5FE4200A4542E /* Canvas */, EC50500A2BF6672000B4D86E /* Components */, EC5050102BF670EE00B4D86E /* Config */, ECA738772BE5EEE800A4542E /* Features */, + ECF7B2E82C395A8E004D2C57 /* Memola.entitlements */, ECA738FA2BE61B1700A4542E /* Persistence */, EC7F6BF12BE5E6E400A34A7B /* Preview Content */, ECA738802BE5FE6000A4542E /* Resources */, @@ -517,6 +585,7 @@ isa = PBXGroup; children = ( EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */, + EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */, ); path = App; sourceTree = ""; @@ -914,6 +983,7 @@ ECF7B2E52C391DFA004D2C57 /* Utilies */ = { isa = PBXGroup; children = ( + EC2002EE2C417BBF002EBD5F /* AppScene */, ECF7B2CF2C39169C004D2C57 /* Extensions */, ECF7B2E22C39172D004D2C57 /* Platform */, ); @@ -1035,7 +1105,9 @@ ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */, EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */, EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */, + EC2002F02C417BF1002EBD5F /* ActiveSceneKey.swift in Sources */, EC3D67CC2C3AAD5E00359400 /* MemoManager.swift in Sources */, + EC2002E12C416470002EBD5F /* Shortcut.swift in Sources */, EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */, ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */, ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */, @@ -1045,6 +1117,8 @@ ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */, ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */, ECF7B2D12C39169C004D2C57 /* CGAffineTransform++.swift in Sources */, + EC2002E52C416551002EBD5F /* Shortcuts.swift in Sources */, + EC2002DD2C4163E8002EBD5F /* AppCommands.swift in Sources */, EC1815082C2D980B00541369 /* Sort.swift in Sources */, ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */, ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */, @@ -1082,6 +1156,7 @@ ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */, ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */, EC0D14242BF79C98009BFE5F /* MemolaModel.xcdatamodeld in Sources */, + EC2002ED2C417B68002EBD5F /* AppScene.swift in Sources */, ECFA15242BEF223300455818 /* GraphicContextObject.swift in Sources */, ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */, ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */, @@ -1099,10 +1174,12 @@ ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */, EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */, ECA738C92BE60EF700A4542E /* GraphicContext.swift in Sources */, + EC2002E92C4167C5002EBD5F /* ShortcutKey.swift in Sources */, EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */, ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */, EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */, EC9AB09F2C1401A40076AF58 /* EraserObject.swift in Sources */, + EC2002DB2C4162E5002EBD5F /* AppDelegate.swift in Sources */, ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */, ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */, ECA738B32BE60D9E00A4542E /* CanvasView.swift in Sources */, @@ -1120,12 +1197,15 @@ EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */, ECF7B2E42C39174D004D2C57 /* Platform.swift in Sources */, ECA738C12BE60E5300A4542E /* PenStyle.swift in Sources */, + EC2002D72C4160EF002EBD5F /* EditCommands.swift in Sources */, ECF7B2DF2C39169C004D2C57 /* simd_float4x4++.swift in Sources */, ECF7B2D02C39169C004D2C57 /* Array++.swift in Sources */, ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */, ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */, EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */, + EC2002D92C4161ED002EBD5F /* ViewCommands.swift in Sources */, EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */, + EC2002D52C416033002EBD5F /* FileCommands.swift in Sources */, EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */, ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */, ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */, diff --git a/Memola/App/AppDelegate.swift b/Memola/App/AppDelegate.swift new file mode 100644 index 0000000..2950d07 --- /dev/null +++ b/Memola/App/AppDelegate.swift @@ -0,0 +1,19 @@ +// +// AppDelegate.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import SwiftUI + +class AppDelegate: NSObject, ObservableObject { } + +#if os(macOS) +extension AppDelegate: NSApplicationDelegate { + func applicationDidFinishLaunching(_ notification: Notification) { + NSWindow.allowsAutomaticWindowTabbing = false + UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows": false]) + } +} +#endif diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index d262564..2a0a44e 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -9,6 +9,10 @@ import SwiftUI @main struct MemolaApp: App { + #if os(macOS) + @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + #endif + var body: some Scene { WindowGroup { DashboardView() @@ -31,5 +35,11 @@ struct MemolaApp: App { .defaultSize(width: 1200, height: 800) .windowToolbarStyle(.unifiedCompact) #endif + .commands { + AppCommands() + FileCommands() + EditCommands() + ViewCommands() + } } } diff --git a/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift b/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift index 3f1aa47..eed64e8 100644 --- a/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift +++ b/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift @@ -101,7 +101,9 @@ final class PenStroke: Stroke, @unchecked Sendable { fetchRequest.predicate = NSPredicate(format: "ANY strokes == %@", stroke) do { - let erasers = try Persistence.shared.backgroundContext.fetch(fetchRequest) + let erasers = try withPersistenceSync(\.backgroundContext) { context in + try context.fetch(fetchRequest) + } return erasers } catch { NSLog("[Memola] - \(error.localizedDescription)") diff --git a/Memola/Features/Dashboard/Dashboard/MemoManager.swift b/Memola/Features/Dashboard/Dashboard/MemoManager.swift index e2adfdd..4713583 100644 --- a/Memola/Features/Dashboard/Dashboard/MemoManager.swift +++ b/Memola/Features/Dashboard/Dashboard/MemoManager.swift @@ -8,6 +8,7 @@ import SwiftUI import Foundation +#warning("TODO: use environmnet instead of singleton") class MemoManager: ObservableObject { static let shared: MemoManager = .init() diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 0edbd98..96479ff 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -8,6 +8,7 @@ import SwiftUI struct MemosView: View { + @Environment(\.shortcut) var shortcut @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -42,6 +43,7 @@ struct MemosView: View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in memoCard(memoObject, cellWidth) } + .focusedSceneValue(\.activeSceneKey, .memos) .navigationTitle(horizontalSizeClass == .compact ? "Memos" : "") #if os(iOS) .navigationBarTitleDisplayMode(.inline) @@ -150,6 +152,9 @@ struct MemosView: View { .onReceive(timer) { date in currentDate = date } + .onReceive(shortcut.publisher()) { shortcut in + handleShortcut(for: shortcut) + } } func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View { @@ -277,4 +282,15 @@ struct MemosView: View { try context.saveIfNeeded() } } + + func handleShortcut(for shortcut: Shortcuts) { + switch shortcut { + case .newMemo: + if MemoManager.shared.memoObject == nil { + createMemo(title: "Untitled") + } + default: + break + } + } } diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index c484474..534d8d2 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -8,6 +8,7 @@ import SwiftUI struct TrashView: View { + @Environment(\.shortcut) var shortcut @Environment(\.horizontalSizeClass) var horizontalSizeClass @FetchRequest var memoObjects: FetchedResults @@ -43,6 +44,7 @@ struct TrashView: View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in memoCard(memoObject, cellWidth) } + .focusedSceneValue(\.activeSceneKey, .trash) .navigationTitle(horizontalSizeClass == .compact ? "Trash" : "") #if os(iOS) .navigationBarTitleDisplayMode(.inline) diff --git a/Memola/Persistence/Core/Persistence.swift b/Memola/Persistence/Core/Persistence.swift index 15c19f3..3597225 100644 --- a/Memola/Persistence/Core/Persistence.swift +++ b/Memola/Persistence/Core/Persistence.swift @@ -109,3 +109,10 @@ func withPersistenceSync(_ keypath: KeyPath } } } + +func withPersistenceSync(_ keypath: KeyPath, _ task: @escaping (NSManagedObjectContext) throws -> T) throws -> T { + let context = Persistence.shared[keyPath: keypath] + return try context.performAndWait { + return try task(context) + } +} diff --git a/Memola/Persistence/Objects/PenObject.swift b/Memola/Persistence/Objects/PenObject.swift index 054edf4..4c51ad6 100644 --- a/Memola/Persistence/Objects/PenObject.swift +++ b/Memola/Persistence/Objects/PenObject.swift @@ -20,7 +20,7 @@ class PenObject: NSManagedObject { extension PenObject { static func createObject(_ keyPath: KeyPath, penStyle: any PenStyle) -> PenObject { - let object = PenObject(context: Persistence.shared[keyPath: keyPath]) + let object = PenObject(keyPath) object.color = penStyle.color object.style = penStyle.strokeStyle.rawValue object.isSelected = false diff --git a/Memola/Shortcut/Commands/AppCommands.swift b/Memola/Shortcut/Commands/AppCommands.swift new file mode 100644 index 0000000..16c39fd --- /dev/null +++ b/Memola/Shortcut/Commands/AppCommands.swift @@ -0,0 +1,21 @@ +// +// AppCommands.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import SwiftUI + +struct AppCommands: Commands { + var body: some Commands { + CommandGroup(replacing: .appSettings) { + Button { + + } label: { + Text("Services...") + } + .keyboardShortcut(",", modifiers: .command) + } + } +} diff --git a/Memola/Shortcut/Commands/EditCommands.swift b/Memola/Shortcut/Commands/EditCommands.swift new file mode 100644 index 0000000..159dce9 --- /dev/null +++ b/Memola/Shortcut/Commands/EditCommands.swift @@ -0,0 +1,30 @@ +// +// EditCommands.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import SwiftUI + +struct EditCommands: Commands { + var body: some Commands { + CommandGroup(replacing: .undoRedo) { + // memo view + Button { + + } label: { + Text("Undo") + } + .keyboardShortcut("z", modifiers: [.command]) + Button { + + } label: { + Text("Redo") + } + .keyboardShortcut("z", modifiers: [.command, .shift]) + } + CommandGroup(replacing: .pasteboard) { } + } +} + diff --git a/Memola/Shortcut/Commands/FileCommands.swift b/Memola/Shortcut/Commands/FileCommands.swift new file mode 100644 index 0000000..389205d --- /dev/null +++ b/Memola/Shortcut/Commands/FileCommands.swift @@ -0,0 +1,26 @@ +// +// FileCommands.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import SwiftUI + +struct FileCommands: Commands { + @Environment(\.shortcut) var shortcut + @FocusedValue(\.activeSceneKey) var appScene + + var body: some Commands { + CommandGroup(replacing: .newItem) { + if appScene == .memos { + Button { + shortcut.trigger(.newMemo) + } label: { + Text("New Memo") + } + .keyboardShortcut("n", modifiers: [.command]) + } + } + } +} diff --git a/Memola/Shortcut/Commands/ViewCommands.swift b/Memola/Shortcut/Commands/ViewCommands.swift new file mode 100644 index 0000000..487c9f7 --- /dev/null +++ b/Memola/Shortcut/Commands/ViewCommands.swift @@ -0,0 +1,14 @@ +// +// ViewCommands.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import SwiftUI + +struct ViewCommands: Commands { + var body: some Commands { + CommandGroup(replacing: .toolbar) { } + } +} diff --git a/Memola/Shortcut/Core/Shortcut.swift b/Memola/Shortcut/Core/Shortcut.swift new file mode 100644 index 0000000..456e6f0 --- /dev/null +++ b/Memola/Shortcut/Core/Shortcut.swift @@ -0,0 +1,25 @@ +// +// Shortcut.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import Combine +import Foundation + +class Shortcut: ObservableObject { + static let shared: Shortcut = .init() + + private let shortcutPublisher = PassthroughSubject() + + private init() { } + + func trigger(_ shortcut: Shortcuts) { + shortcutPublisher.send(shortcut) + } + + func publisher() -> AnyPublisher { + shortcutPublisher.eraseToAnyPublisher() + } +} diff --git a/Memola/Shortcut/Core/Shortcuts.swift b/Memola/Shortcut/Core/Shortcuts.swift new file mode 100644 index 0000000..438600e --- /dev/null +++ b/Memola/Shortcut/Core/Shortcuts.swift @@ -0,0 +1,14 @@ +// +// Shortcuts.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import Foundation + +enum Shortcuts { + // MARK: - Memos + case newMemo + case findMemo +} diff --git a/Memola/Shortcut/EnvironmentKeys/ShortcutKey.swift b/Memola/Shortcut/EnvironmentKeys/ShortcutKey.swift new file mode 100644 index 0000000..6d9d2c9 --- /dev/null +++ b/Memola/Shortcut/EnvironmentKeys/ShortcutKey.swift @@ -0,0 +1,18 @@ +// +// ShortcutKey.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import SwiftUI + +struct ShortcutKey: EnvironmentKey { + static var defaultValue: Shortcut = .shared +} + +extension EnvironmentValues { + var shortcut: Shortcut { + get { self[ShortcutKey.self] } + } +} diff --git a/Memola/Utilies/AppScene/ActiveSceneKey.swift b/Memola/Utilies/AppScene/ActiveSceneKey.swift new file mode 100644 index 0000000..7e323b5 --- /dev/null +++ b/Memola/Utilies/AppScene/ActiveSceneKey.swift @@ -0,0 +1,19 @@ +// +// ActiveSceneKey.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import SwiftUI + +struct ActiveSceneKey: FocusedValueKey { + typealias Value = AppScene +} + +extension FocusedValues { + var activeSceneKey: AppScene? { + get { self[ActiveSceneKey.self] } + set { self[ActiveSceneKey.self] = newValue } + } +} diff --git a/Memola/Utilies/AppScene/AppScene.swift b/Memola/Utilies/AppScene/AppScene.swift new file mode 100644 index 0000000..0c71200 --- /dev/null +++ b/Memola/Utilies/AppScene/AppScene.swift @@ -0,0 +1,14 @@ +// +// AppScene.swift +// Memola +// +// Created by Dscyre Scotti on 7/12/24. +// + +import Foundation + +enum AppScene { + case memos + case trash + case memo +} From 00d9a8e5f9a13ee06a0db993b904c70197ca42b3 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 14 Jul 2024 15:56:29 +0700 Subject: [PATCH 15/22] bug: fix dead lock on background context thread --- .../Elements/Geometries/Stroke/Strokes/PenStroke.swift | 2 +- Memola/Persistence/Core/Persistence.swift | 6 ++---- 2 files changed, 3 insertions(+), 5 deletions(-) diff --git a/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift b/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift index eed64e8..f633904 100644 --- a/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift +++ b/Memola/Canvas/Elements/Geometries/Stroke/Strokes/PenStroke.swift @@ -101,7 +101,7 @@ final class PenStroke: Stroke, @unchecked Sendable { fetchRequest.predicate = NSPredicate(format: "ANY strokes == %@", stroke) do { - let erasers = try withPersistenceSync(\.backgroundContext) { context in + let erasers = try withPersistenceContext(\.backgroundContext) { context in try context.fetch(fetchRequest) } return erasers diff --git a/Memola/Persistence/Core/Persistence.swift b/Memola/Persistence/Core/Persistence.swift index 3597225..df7c3a2 100644 --- a/Memola/Persistence/Core/Persistence.swift +++ b/Memola/Persistence/Core/Persistence.swift @@ -110,9 +110,7 @@ func withPersistenceSync(_ keypath: KeyPath } } -func withPersistenceSync(_ keypath: KeyPath, _ task: @escaping (NSManagedObjectContext) throws -> T) throws -> T { +func withPersistenceContext(_ keypath: KeyPath, _ task: @escaping (NSManagedObjectContext) throws -> T) throws -> T { let context = Persistence.shared[keyPath: keypath] - return try context.performAndWait { - return try task(context) - } + return try task(context) } From ca93472cd49092859d915c406634b17e15b65308 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 14 Jul 2024 16:03:05 +0700 Subject: [PATCH 16/22] feat: add find keyboard shortcut --- Memola.xcodeproj/project.pbxproj | 16 ++++++--- .../{AppDelegate.swift => Application.swift} | 13 ++++++-- Memola/App/MemolaApp.swift | 6 ++-- .../ContextMenuViewModifier.swift | 2 +- .../OnDismissSearchViewModifier.swift | 29 ++++++++++++++++ .../ViewModifiers/OnDragViewModifier.swift | 2 +- .../Dashboard/Dashboard/DashboardView.swift | 5 +-- .../Dashboard/Details/Memos/MemosView.swift | 7 +++- .../Dashboard/Details/Trash/TrashView.swift | 7 +++- Memola/Shortcut/Commands/ViewCommands.swift | 18 +++++++++- Memola/Shortcut/Core/Shortcut.swift | 2 +- .../NavigationSplitViewVisibility++.swift | 33 +++++++++++++++++++ 12 files changed, 123 insertions(+), 17 deletions(-) rename Memola/App/{AppDelegate.swift => Application.swift} (54%) create mode 100644 Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift create mode 100644 Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 039dc18..f2fd160 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -25,7 +25,6 @@ EC2002D52C416033002EBD5F /* FileCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D42C416033002EBD5F /* FileCommands.swift */; }; EC2002D72C4160EF002EBD5F /* EditCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D62C4160EF002EBD5F /* EditCommands.swift */; }; EC2002D92C4161ED002EBD5F /* ViewCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002D82C4161ED002EBD5F /* ViewCommands.swift */; }; - EC2002DB2C4162E5002EBD5F /* AppDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */; }; EC2002DD2C4163E8002EBD5F /* AppCommands.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002DC2C4163E8002EBD5F /* AppCommands.swift */; }; EC2002E12C416470002EBD5F /* Shortcut.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002E02C416470002EBD5F /* Shortcut.swift */; }; EC2002E52C416551002EBD5F /* Shortcuts.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2002E42C416551002EBD5F /* Shortcuts.swift */; }; @@ -45,6 +44,9 @@ EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */; }; EC5D40812C21CE270067F090 /* PhotoBackgroundRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */; }; EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */; }; + EC6E3BD72C43C6A400DD20F3 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BD62C43C6A400DD20F3 /* Application.swift */; }; + EC6E3BD92C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */; }; + EC6E3BDB2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */; }; EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; }; EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; }; EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; }; @@ -160,7 +162,6 @@ EC2002D42C416033002EBD5F /* FileCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCommands.swift; sourceTree = ""; }; EC2002D62C4160EF002EBD5F /* EditCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCommands.swift; sourceTree = ""; }; EC2002D82C4161ED002EBD5F /* ViewCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCommands.swift; sourceTree = ""; }; - EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppDelegate.swift; sourceTree = ""; }; EC2002DC2C4163E8002EBD5F /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = ""; }; EC2002E02C416470002EBD5F /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = ""; }; EC2002E42C416551002EBD5F /* Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcuts.swift; sourceTree = ""; }; @@ -181,6 +182,9 @@ EC50500E2BF670EA00B4D86E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = ""; }; EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoBackgroundRenderPass.swift; sourceTree = ""; }; EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovingAverage.swift; sourceTree = ""; }; + EC6E3BD62C43C6A400DD20F3 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; + EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnDismissSearchViewModifier.swift; sourceTree = ""; }; + EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NavigationSplitViewVisibility++.swift"; sourceTree = ""; }; EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; }; EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = ""; }; EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -492,6 +496,7 @@ EC50500B2BF6673300B4D86E /* ViewModifiers */ = { isa = PBXGroup; children = ( + EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */, EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */, EC0D14272BF7BF20009BFE5F /* ContextMenuViewModifier.swift */, ); @@ -585,7 +590,7 @@ isa = PBXGroup; children = ( EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */, - EC2002DA2C4162E5002EBD5F /* AppDelegate.swift */, + EC6E3BD62C43C6A400DD20F3 /* Application.swift */, ); path = App; sourceTree = ""; @@ -950,6 +955,7 @@ ECF7B2CF2C39169C004D2C57 /* Extensions */ = { isa = PBXGroup; children = ( + EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */, ECF7B2BD2C39169C004D2C57 /* Array++.swift */, ECF7B2BE2C39169C004D2C57 /* CGAffineTransform++.swift */, ECF7B2BF2C39169C004D2C57 /* CGFloat++.swift */, @@ -1120,6 +1126,7 @@ EC2002E52C416551002EBD5F /* Shortcuts.swift in Sources */, EC2002DD2C4163E8002EBD5F /* AppCommands.swift in Sources */, EC1815082C2D980B00541369 /* Sort.swift in Sources */, + EC6E3BD92C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift in Sources */, ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */, ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */, ECF7B2E12C39169C004D2C57 /* View++.swift in Sources */, @@ -1129,6 +1136,7 @@ ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */, ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */, EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */, + EC6E3BDB2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift in Sources */, ECA738C62BE60E9D00A4542E /* EraserPenStyle.swift in Sources */, ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */, ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */, @@ -1139,6 +1147,7 @@ ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */, ECF7B2DE2C39169C004D2C57 /* NSManagedObjectContext++.swift in Sources */, ECA738882BE5FF4400A4542E /* Renderer.swift in Sources */, + EC6E3BD72C43C6A400DD20F3 /* Application.swift in Sources */, EC8C9DCE2C39882500A8F3C4 /* NSSyncScrollView.swift in Sources */, ECF7B2D72C39169C004D2C57 /* Color++.swift in Sources */, EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */, @@ -1179,7 +1188,6 @@ ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */, EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */, EC9AB09F2C1401A40076AF58 /* EraserObject.swift in Sources */, - EC2002DB2C4162E5002EBD5F /* AppDelegate.swift in Sources */, ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */, ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */, ECA738B32BE60D9E00A4542E /* CanvasView.swift in Sources */, diff --git a/Memola/App/AppDelegate.swift b/Memola/App/Application.swift similarity index 54% rename from Memola/App/AppDelegate.swift rename to Memola/App/Application.swift index 2950d07..fe73936 100644 --- a/Memola/App/AppDelegate.swift +++ b/Memola/App/Application.swift @@ -1,19 +1,26 @@ // -// AppDelegate.swift +// Application.swift // Memola // // Created by Dscyre Scotti on 7/12/24. // +import Combine import SwiftUI -class AppDelegate: NSObject, ObservableObject { } +class Application: NSObject, ObservableObject { + +} #if os(macOS) -extension AppDelegate: NSApplicationDelegate { +extension Application: NSApplicationDelegate { func applicationDidFinishLaunching(_ notification: Notification) { NSWindow.allowsAutomaticWindowTabbing = false UserDefaults.standard.register(defaults: ["NSQuitAlwaysKeepsWindows": false]) } } +#else +extension Application: UIApplicationDelegate { + func applicationDidFinishLaunching(_ application: UIApplication) { } +} #endif diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index 2a0a44e..cf96907 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -10,9 +10,11 @@ import SwiftUI @main struct MemolaApp: App { #if os(macOS) - @NSApplicationDelegateAdaptor(AppDelegate.self) var appDelegate + @NSApplicationDelegateAdaptor(Application.self) var application + #else + @UIApplicationDelegateAdaptor(Application.self) var application #endif - + var body: some Scene { WindowGroup { DashboardView() diff --git a/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift b/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift index ee1a3f2..34f458c 100644 --- a/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift +++ b/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift @@ -8,7 +8,7 @@ import SwiftUI import Foundation -struct ContextMenuViewModifier: ViewModifier { +private struct ContextMenuViewModifier: ViewModifier { let condition: Bool let menuItems: () -> MenuContent let preview: () -> Preview diff --git a/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift b/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift new file mode 100644 index 0000000..10c74f6 --- /dev/null +++ b/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift @@ -0,0 +1,29 @@ +// +// OnDismissSearchViewModifier.swift +// Memola +// +// Created by Dscyre Scotti on 7/14/24. +// + +import SwiftUI + +private struct OnDismissSearchViewModifier: ViewModifier { + @Environment(\.dismissSearch) var dismissSearch + + @Binding var isActive: Bool + + func body(content: Content) -> some View { + content + .onChange(of: isActive) { oldValue, newValue in + if !newValue { + dismissSearch() + } + } + } +} + +extension View { + func onDismissSearch(isActive: Binding) -> some View { + modifier(OnDismissSearchViewModifier(isActive: isActive)) + } +} diff --git a/Memola/Components/ViewModifiers/OnDragViewModifier.swift b/Memola/Components/ViewModifiers/OnDragViewModifier.swift index b60d540..c31c141 100644 --- a/Memola/Components/ViewModifiers/OnDragViewModifier.swift +++ b/Memola/Components/ViewModifiers/OnDragViewModifier.swift @@ -8,7 +8,7 @@ import SwiftUI import Foundation -struct OnDragViewModifier: ViewModifier { +private struct OnDragViewModifier: ViewModifier { let condition: Bool let data: () -> NSItemProvider let preview: () -> Preview diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index 519439b..a319f08 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -13,12 +13,13 @@ struct DashboardView: View { @StateObject var memoManager: MemoManager = .shared @State var sidebarItem: SidebarItem? = .memos + @AppStorage("memola.app.scene.side-bar.column-visibility") var columnVisibility: NavigationSplitViewVisibility = .all @Namespace var namespace var body: some View { #if os(macOS) - NavigationSplitView { + NavigationSplitView(columnVisibility: $columnVisibility) { Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) } detail: { switch sidebarItem { @@ -45,7 +46,7 @@ struct DashboardView: View { } } #else - NavigationSplitView { + NavigationSplitView(columnVisibility: $columnVisibility) { Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) } detail: { switch sidebarItem { diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 96479ff..9e9569a 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -15,6 +15,7 @@ struct MemosView: View { @State var query: String = "" @State var currentDate: Date = .now + @State var isActiveSearch: Bool = false @AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent @AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none @@ -43,12 +44,16 @@ struct MemosView: View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in memoCard(memoObject, cellWidth) } + .onDismissSearch(isActive: $isActiveSearch) .focusedSceneValue(\.activeSceneKey, .memos) .navigationTitle(horizontalSizeClass == .compact ? "Memos" : "") #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif - .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) + .searchable(text: $query, isPresented: $isActiveSearch, placement: .toolbar, prompt: Text("Search")) + .onSubmit(of: .search) { + isActiveSearch = false + } .toolbar { #if os(macOS) ToolbarItem(placement: .navigation) { diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 534d8d2..bda573c 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -16,6 +16,7 @@ struct TrashView: View { @State var query: String = "" @State var restoredMemo: MemoObject? @State var deletedMemo: MemoObject? + @State var isActiveSearch: Bool = false @Binding var sidebarItem: SidebarItem? @@ -44,12 +45,16 @@ struct TrashView: View { MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in memoCard(memoObject, cellWidth) } + .onDismissSearch(isActive: $isActiveSearch) .focusedSceneValue(\.activeSceneKey, .trash) .navigationTitle(horizontalSizeClass == .compact ? "Trash" : "") #if os(iOS) .navigationBarTitleDisplayMode(.inline) #endif - .searchable(text: $query, placement: .toolbar, prompt: Text("Search")) + .searchable(text: $query, isPresented: $isActiveSearch, placement: .toolbar, prompt: Text("Search")) + .onSubmit(of: .search) { + isActiveSearch = false + } .toolbar { #if os(macOS) ToolbarItem(placement: .navigation) { diff --git a/Memola/Shortcut/Commands/ViewCommands.swift b/Memola/Shortcut/Commands/ViewCommands.swift index 487c9f7..aa4240f 100644 --- a/Memola/Shortcut/Commands/ViewCommands.swift +++ b/Memola/Shortcut/Commands/ViewCommands.swift @@ -8,7 +8,23 @@ import SwiftUI struct ViewCommands: Commands { + @FocusedValue(\.activeSceneKey) var appScene + var body: some Commands { - CommandGroup(replacing: .toolbar) { } + CommandGroup(replacing: .toolbar) { + if appScene == .trash || appScene == .memos { + Button { + #if os(macOS) + guard let toolbar = NSApp.keyWindow?.toolbar else { return } + if let search = toolbar.items.first(where: { $0.itemIdentifier.rawValue == "com.apple.SwiftUI.search" }) as? NSSearchToolbarItem { + search.beginSearchInteraction() + } + #endif + } label: { + Text("Find Memo") + } + .keyboardShortcut("f", modifiers: [.command]) + } + } } } diff --git a/Memola/Shortcut/Core/Shortcut.swift b/Memola/Shortcut/Core/Shortcut.swift index 456e6f0..57c4043 100644 --- a/Memola/Shortcut/Core/Shortcut.swift +++ b/Memola/Shortcut/Core/Shortcut.swift @@ -8,7 +8,7 @@ import Combine import Foundation -class Shortcut: ObservableObject { +class Shortcut { static let shared: Shortcut = .init() private let shortcutPublisher = PassthroughSubject() diff --git a/Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift b/Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift new file mode 100644 index 0000000..1599039 --- /dev/null +++ b/Memola/Utilies/Extensions/NavigationSplitViewVisibility++.swift @@ -0,0 +1,33 @@ +// +// NavigationSplitViewVisibility++.swift +// Memola +// +// Created by Dscyre Scotti on 7/14/24. +// + +import SwiftUI + +extension NavigationSplitViewVisibility: RawRepresentable { + public init?(rawValue: Int) { + switch rawValue { + case 0: self = .all + case 1: self = .automatic + case 2: self = .detailOnly + case 3: self = .doubleColumn + default: self = .all + } + } + + public var rawValue: Int { + switch self { + case .all: 0 + case .automatic: 1 + case .detailOnly: 2 + case .doubleColumn: 3 + default: -1 + } + } + + public typealias RawValue = Int +} + From 9b9ccf2a869b2fb8ad73feb60a3220a87749937e Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 14 Jul 2024 16:44:21 +0700 Subject: [PATCH 17/22] chore: refactor --- Memola.xcodeproj/project.pbxproj | 6 +- Memola/App/Application.swift | 15 ++++- Memola/App/MemolaApp.swift | 7 ++- Memola/Canvas/Contexts/LineGridContext.swift | 2 +- Memola/Canvas/Contexts/PointGridContext.swift | 2 +- Memola/Canvas/Contexts/ViewPortContext.swift | 2 +- Memola/Canvas/Core/PipelineStates.swift | 2 +- .../Canvas/Elements/Core/ElementGroup.swift | 2 +- .../Stroke/Algorithms/MovingAverage.swift | 2 +- Memola/Canvas/History/History.swift | 2 +- Memola/Canvas/RTree/Node.swift | 2 +- Memola/Canvas/RTree/RTree.swift | 2 +- .../Canvas/RenderPasses/CacheRenderPass.swift | 2 +- .../RenderPasses/EraserRenderPass.swift | 2 +- .../RenderPasses/GraphicRenderPass.swift | 2 +- .../PhotoBackgroundRenderPass.swift | 2 +- .../Canvas/RenderPasses/PhotoRenderPass.swift | 2 +- .../RenderPasses/StrokeRenderPass.swift | 2 +- .../RenderPasses/ViewPortRenderPass.swift | 2 +- Memola/Canvas/Tool/Core/Tool.swift | 4 +- .../ViewController/CanvasViewController.swift | 60 +++++++++---------- .../Views/AppKit/NSCenterClipView.swift | 2 +- .../Views/AppKit/NSSyncScrollView.swift | 2 +- .../View/Bridge/Views/DrawingView.swift | 8 +-- Memola/Canvas/View/Canvas/CanvasView.swift | 12 +++- .../ContextMenuViewModifier.swift | 12 +++- .../OnDismissSearchViewModifier.swift | 8 ++- .../ViewModifiers/OnDragViewModifier.swift | 12 +++- .../Views/CameraView/CameraView.swift | 9 ++- .../Views/ColorPicker/ColorPicker.swift | 24 ++++---- .../Views/Placeholder/Placeholder.swift | 8 ++- .../Dashboard/Dashboard/DashboardView.swift | 10 ++-- .../Dashboard/Details/Memos/MemosView.swift | 36 +++++------ .../Dashboard/Details/Shared/MemoCard.swift | 10 ++-- .../Dashboard/Details/Shared/MemoGrid.swift | 16 +++-- .../Details/Shared/MemoPreview.swift | 14 +++-- .../Dashboard/Details/Trash/TrashView.swift | 28 ++++----- .../Features/Dashboard/Sidebar/Sidebar.swift | 13 ++-- .../Memo/ElementToolbar/ElementToolbar.swift | 19 +++--- Memola/Features/Memo/Memo/MemoView.swift | 24 ++++---- Memola/Features/Memo/PenDock/PenDock.swift | 53 ++++++++-------- .../Memo/PenDock/PenDropDelegate.swift | 12 +++- .../Features/Memo/PhotoDock/PhotoDock.swift | 27 +++++---- .../Memo/PhotoPreview/PhotoPreview.swift | 11 +++- Memola/Features/Memo/Toolbar/Toolbar.swift | 28 ++++----- Memola/Persistence/Core/Persistence.swift | 8 --- Memola/Persistence/Objects/PenObject.swift | 2 +- Memola/Persistence/Objects/PhotoObject.swift | 2 +- Memola/Persistence/Objects/ToolObject.swift | 2 +- Memola/Shortcut/Commands/FileCommands.swift | 4 +- Memola/Shortcut/Commands/ViewCommands.swift | 14 ++--- Memola/Shortcut/Core/Shortcut.swift | 14 ++--- .../ShortcutKey.swift | 2 +- Memola/Utilies/AppScene/ActiveSceneKey.swift | 2 +- .../SidebarVisibility/SidebarVisibility.swift | 13 ++++ 55 files changed, 341 insertions(+), 244 deletions(-) rename Memola/Shortcut/{EnvironmentKeys => EnvironmentValues}/ShortcutKey.swift (85%) create mode 100644 Memola/Utilies/SidebarVisibility/SidebarVisibility.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index f2fd160..f442273 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -409,7 +409,7 @@ children = ( EC2002D32C416002002EBD5F /* Commands */, EC2002DF2C416466002EBD5F /* Core */, - EC2002E72C4167B1002EBD5F /* EnvironmentKeys */, + EC2002E72C4167B1002EBD5F /* EnvironmentValues */, ); path = Shortcut; sourceTree = ""; @@ -423,12 +423,12 @@ path = Core; sourceTree = ""; }; - EC2002E72C4167B1002EBD5F /* EnvironmentKeys */ = { + EC2002E72C4167B1002EBD5F /* EnvironmentValues */ = { isa = PBXGroup; children = ( EC2002E82C4167C5002EBD5F /* ShortcutKey.swift */, ); - path = EnvironmentKeys; + path = EnvironmentValues; sourceTree = ""; }; EC2002EE2C417BBF002EBD5F /* AppScene */ = { diff --git a/Memola/App/Application.swift b/Memola/App/Application.swift index fe73936..fd9972f 100644 --- a/Memola/App/Application.swift +++ b/Memola/App/Application.swift @@ -8,8 +8,21 @@ import Combine import SwiftUI -class Application: NSObject, ObservableObject { +final class Application: NSObject, ObservableObject { + +} +extension Application { + func activateSearchBar() { + #if os(macOS) + guard let toolbar = NSApp.keyWindow?.toolbar else { return } + if let search = toolbar.items.first(where: { $0.itemIdentifier.rawValue == "com.apple.SwiftUI.search" }) as? NSSearchToolbarItem { + search.beginSearchInteraction() + } + #else + #warning("TODO: implement for ipad") + #endif + } } #if os(macOS) diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index cf96907..5889ecd 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -10,9 +10,9 @@ import SwiftUI @main struct MemolaApp: App { #if os(macOS) - @NSApplicationDelegateAdaptor(Application.self) var application + @NSApplicationDelegateAdaptor(Application.self) private var application #else - @UIApplicationDelegateAdaptor(Application.self) var application + @UIApplicationDelegateAdaptor(Application.self) private var application #endif var body: some Scene { @@ -30,6 +30,7 @@ struct MemolaApp: App { #if os(macOS) .frame(minWidth: 1000, minHeight: 600) #endif + .environmentObject(application) } #if os(macOS) .defaultPosition(.center) @@ -41,7 +42,7 @@ struct MemolaApp: App { AppCommands() FileCommands() EditCommands() - ViewCommands() + ViewCommands(application: application) } } } diff --git a/Memola/Canvas/Contexts/LineGridContext.swift b/Memola/Canvas/Contexts/LineGridContext.swift index 4dc9b09..68ecfd0 100644 --- a/Memola/Canvas/Contexts/LineGridContext.swift +++ b/Memola/Canvas/Contexts/LineGridContext.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class LineGridContext { +final class LineGridContext { var vertices: [LineGridVertex] = [] var vertexCount: Int = 0 var vertexBuffer: MTLBuffer? diff --git a/Memola/Canvas/Contexts/PointGridContext.swift b/Memola/Canvas/Contexts/PointGridContext.swift index 0f94681..2ab9281 100644 --- a/Memola/Canvas/Contexts/PointGridContext.swift +++ b/Memola/Canvas/Contexts/PointGridContext.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class PointGridContext { +final class PointGridContext { var vertices: [PointGridVertex] = [] var vertexCount: Int = 0 var vertexBuffer: MTLBuffer? diff --git a/Memola/Canvas/Contexts/ViewPortContext.swift b/Memola/Canvas/Contexts/ViewPortContext.swift index f74e6b5..c560a68 100644 --- a/Memola/Canvas/Contexts/ViewPortContext.swift +++ b/Memola/Canvas/Contexts/ViewPortContext.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class ViewPortContext { +final class ViewPortContext { var vertices: [ViewPortVertex] = [] let vertexCount: Int = 4 var vertexBuffer: MTLBuffer? diff --git a/Memola/Canvas/Core/PipelineStates.swift b/Memola/Canvas/Core/PipelineStates.swift index b4e02f3..ac22028 100644 --- a/Memola/Canvas/Core/PipelineStates.swift +++ b/Memola/Canvas/Core/PipelineStates.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -struct PipelineStates { +enum PipelineStates { static func createPointGridPipelineState(from renderer: Renderer, pixelFormat: MTLPixelFormat? = nil) -> MTLRenderPipelineState? { let device = renderer.device let library = renderer.library diff --git a/Memola/Canvas/Elements/Core/ElementGroup.swift b/Memola/Canvas/Elements/Core/ElementGroup.swift index 71c8668..d61365b 100644 --- a/Memola/Canvas/Elements/Core/ElementGroup.swift +++ b/Memola/Canvas/Elements/Core/ElementGroup.swift @@ -7,7 +7,7 @@ import Foundation -class ElementGroup { +final class ElementGroup { var elements: [Element] = [] var type: ElementGroupType diff --git a/Memola/Canvas/Elements/Geometries/Stroke/Algorithms/MovingAverage.swift b/Memola/Canvas/Elements/Geometries/Stroke/Algorithms/MovingAverage.swift index ff4d6f1..242194a 100644 --- a/Memola/Canvas/Elements/Geometries/Stroke/Algorithms/MovingAverage.swift +++ b/Memola/Canvas/Elements/Geometries/Stroke/Algorithms/MovingAverage.swift @@ -7,7 +7,7 @@ import Foundation -class MovingAverage { +final class MovingAverage { private var sum: CGPoint private var points: [CGPoint] private var windowSize: Int diff --git a/Memola/Canvas/History/History.swift b/Memola/Canvas/History/History.swift index a23b8eb..9cbf6c8 100644 --- a/Memola/Canvas/History/History.swift +++ b/Memola/Canvas/History/History.swift @@ -8,7 +8,7 @@ import Combine import Foundation -class History: ObservableObject { +final class History: ObservableObject { var memo: MemoObject? init(memo: MemoObject?) { diff --git a/Memola/Canvas/RTree/Node.swift b/Memola/Canvas/RTree/Node.swift index 2321961..f024183 100644 --- a/Memola/Canvas/RTree/Node.swift +++ b/Memola/Canvas/RTree/Node.swift @@ -7,7 +7,7 @@ import Foundation -class Node where T: Equatable & Comparable { +final class Node where T: Equatable & Comparable { var box: Box var value: T? var isLeaf: Bool diff --git a/Memola/Canvas/RTree/RTree.swift b/Memola/Canvas/RTree/RTree.swift index 168520f..3c69ec8 100644 --- a/Memola/Canvas/RTree/RTree.swift +++ b/Memola/Canvas/RTree/RTree.swift @@ -7,7 +7,7 @@ import Foundation -class RTree where T: Equatable & Comparable { +final class RTree where T: Equatable & Comparable { private var root: Node private let maxEntries: Int private let minEntries: Int diff --git a/Memola/Canvas/RenderPasses/CacheRenderPass.swift b/Memola/Canvas/RenderPasses/CacheRenderPass.swift index 75d4e5b..92aaf9f 100644 --- a/Memola/Canvas/RenderPasses/CacheRenderPass.swift +++ b/Memola/Canvas/RenderPasses/CacheRenderPass.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class CacheRenderPass: RenderPass { +final class CacheRenderPass: RenderPass { var label: String = "Cache Render Pass" var descriptor: MTLRenderPassDescriptor? diff --git a/Memola/Canvas/RenderPasses/EraserRenderPass.swift b/Memola/Canvas/RenderPasses/EraserRenderPass.swift index 229a452..f06d781 100644 --- a/Memola/Canvas/RenderPasses/EraserRenderPass.swift +++ b/Memola/Canvas/RenderPasses/EraserRenderPass.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class EraserRenderPass: RenderPass { +final class EraserRenderPass: RenderPass { var label: String = "Eraser Render Pass" var descriptor: MTLRenderPassDescriptor? diff --git a/Memola/Canvas/RenderPasses/GraphicRenderPass.swift b/Memola/Canvas/RenderPasses/GraphicRenderPass.swift index c13d903..697bb0d 100644 --- a/Memola/Canvas/RenderPasses/GraphicRenderPass.swift +++ b/Memola/Canvas/RenderPasses/GraphicRenderPass.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class GraphicRenderPass: RenderPass { +final class GraphicRenderPass: RenderPass { var label: String { "Graphic Render Pass" } var descriptor: MTLRenderPassDescriptor? var graphicTexture: MTLTexture? diff --git a/Memola/Canvas/RenderPasses/PhotoBackgroundRenderPass.swift b/Memola/Canvas/RenderPasses/PhotoBackgroundRenderPass.swift index 86f1eb5..eaf51f3 100644 --- a/Memola/Canvas/RenderPasses/PhotoBackgroundRenderPass.swift +++ b/Memola/Canvas/RenderPasses/PhotoBackgroundRenderPass.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class PhotoBackgroundRenderPass: RenderPass { +final class PhotoBackgroundRenderPass: RenderPass { var label: String = "Photo Background Render Pass" var descriptor: MTLRenderPassDescriptor? diff --git a/Memola/Canvas/RenderPasses/PhotoRenderPass.swift b/Memola/Canvas/RenderPasses/PhotoRenderPass.swift index 6761a0e..3a08df1 100644 --- a/Memola/Canvas/RenderPasses/PhotoRenderPass.swift +++ b/Memola/Canvas/RenderPasses/PhotoRenderPass.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class PhotoRenderPass: RenderPass { +final class PhotoRenderPass: RenderPass { var label: String = "Photo Render Pass" var descriptor: MTLRenderPassDescriptor? diff --git a/Memola/Canvas/RenderPasses/StrokeRenderPass.swift b/Memola/Canvas/RenderPasses/StrokeRenderPass.swift index f319616..cdbb77a 100644 --- a/Memola/Canvas/RenderPasses/StrokeRenderPass.swift +++ b/Memola/Canvas/RenderPasses/StrokeRenderPass.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class StrokeRenderPass: RenderPass { +final class StrokeRenderPass: RenderPass { var label: String = "Stroke Render Pass" var descriptor: MTLRenderPassDescriptor? diff --git a/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift b/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift index d939bb1..cafbb60 100644 --- a/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift +++ b/Memola/Canvas/RenderPasses/ViewPortRenderPass.swift @@ -8,7 +8,7 @@ import MetalKit import Foundation -class ViewPortRenderPass: RenderPass { +final class ViewPortRenderPass: RenderPass { var label: String { "View Port Render Pass"} var descriptor: MTLRenderPassDescriptor? diff --git a/Memola/Canvas/Tool/Core/Tool.swift b/Memola/Canvas/Tool/Core/Tool.swift index 8a413b3..244f3dc 100644 --- a/Memola/Canvas/Tool/Core/Tool.swift +++ b/Memola/Canvas/Tool/Core/Tool.swift @@ -10,8 +10,8 @@ import SwiftUI import CoreData import Foundation -public class Tool: NSObject, ObservableObject { - let object: ToolObject +final class Tool: NSObject, ObservableObject { + private let object: ToolObject @Published var pens: [Pen] = [] diff --git a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift index 819a7c1..5c045d5 100644 --- a/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/ViewController/CanvasViewController.swift @@ -10,21 +10,21 @@ import SwiftUI import MetalKit import Foundation -class CanvasViewController: Platform.ViewController { - let drawingView: DrawingView - let scrollView: Platform.ScrollView = Platform.ScrollView() - var renderView: MTKView { +final class CanvasViewController: Platform.ViewController { + private let drawingView: DrawingView + private let scrollView: Platform.ScrollView = Platform.ScrollView() + private var renderView: MTKView { drawingView.renderView } - var photoInsertGesture: Platform.TapGestureRecognizer? + private var photoInsertGesture: Platform.TapGestureRecognizer? - let tool: Tool - let canvas: Canvas - let history: History - let renderer: Renderer + private let tool: Tool + private let canvas: Canvas + private let history: History + private let renderer: Renderer - var cancellables: Set = [] + private var cancellables: Set = [] init(tool: Tool, canvas: Canvas, history: History) { self.tool = tool @@ -94,7 +94,7 @@ class CanvasViewController: Platform.ViewController { } extension CanvasViewController { - func configureViews() { + private func configureViews() { #if os(macOS) view.wantsLayer = true view.layer?.backgroundColor = NSColor.white.cgColor @@ -153,7 +153,7 @@ extension CanvasViewController { #endif } - func resizeDocumentView(to newSize: CGSize? = nil) { + private func resizeDocumentView(to newSize: CGSize? = nil) { #if os(macOS) scrollView.layoutSubtreeIfNeeded() #else @@ -193,7 +193,7 @@ extension CanvasViewController { } #if os(iOS) - func centerDocumentView(to newSize: CGSize? = nil) { + private func centerDocumentView(to newSize: CGSize? = nil) { let documentViewSize = drawingView.frame.size let scrollViewSize = newSize ?? view.frame.size let verticalPadding = documentViewSize.height < scrollViewSize.height ? (scrollViewSize.height - documentViewSize.height) / 2 : 0 @@ -202,7 +202,7 @@ extension CanvasViewController { } #endif - func updateDocumentBounds() { + private func updateDocumentBounds() { #if os(macOS) let ratio = drawingView.ratio var bounds = scrollView.convert(scrollView.bounds, to: drawingView) @@ -225,7 +225,7 @@ extension CanvasViewController { } extension CanvasViewController { - func configureListeners() { + private func configureListeners() { #if os(macOS) NotificationCenter.default.publisher(for: NSScrollView.didEndLiveMagnifyNotification, object: scrollView) .sink { [weak self] _ in @@ -292,12 +292,12 @@ extension CanvasViewController { } extension CanvasViewController { - func loadMemo() { + private func loadMemo() { tool.load() canvas.load() } - func canvasStateChanged(_ state: Canvas.State) { + private func canvasStateChanged(_ state: Canvas.State) { guard state == .loaded else { return } renderView.delegate = self renderer.resize(on: renderView, to: renderView.drawableSize) @@ -318,7 +318,7 @@ extension CanvasViewController: MTKViewDelegate { } extension CanvasViewController { - func configureGestures() { + private func configureGestures() { let photoInsertGesture = Platform.TapGestureRecognizer(target: self, action: #selector(recognizeTapGesture)) #if os(macOS) photoInsertGesture.numberOfClicksRequired = 1 @@ -329,7 +329,7 @@ extension CanvasViewController { scrollView.addGestureRecognizer(photoInsertGesture) } - @objc func recognizeTapGesture(_ gesture: Platform.TapGestureRecognizer) { + @objc private func recognizeTapGesture(_ gesture: Platform.TapGestureRecognizer) { guard let photoItem = tool.selectedPhotoItem else { return } withAnimation { tool.selectedPhotoItem = nil @@ -406,7 +406,7 @@ extension CanvasViewController: UIScrollViewDelegate { #endif extension CanvasViewController { - func magnificationStarted() { + private func magnificationStarted() { guard !renderer.updatesViewPort else { return } drawingView.touchCancelled() canvas.updateClipBounds(scrollView, on: drawingView) @@ -414,21 +414,21 @@ extension CanvasViewController { renderer.setUpdatesViewPort(true) } - func magnificationEnded() { + private func magnificationEnded() { renderer.setUpdatesViewPort(false) renderer.setRedrawsGraphicRender() renderView.draw() drawingView.enableUserInteraction() } - func draggingStarted() { + private func draggingStarted() { guard !renderer.updatesViewPort else { return } canvas.updateClipBounds(scrollView, on: drawingView) drawingView.disableUserInteraction() renderer.setUpdatesViewPort(true) } - func draggingEnded() { + private func draggingEnded() { renderer.setUpdatesViewPort(false) renderer.setRedrawsGraphicRender() renderView.draw() @@ -437,13 +437,13 @@ extension CanvasViewController { } extension CanvasViewController { - func penChanged(to pen: Pen?) { + private func penChanged(to pen: Pen?) { if let pen, let device = drawingView.renderView.device { pen.style.loadTexture(on: device) } } - func toolSelectionChanged(to selection: ToolSelection) { + private func toolSelectionChanged(to selection: ToolSelection) { let enablesScrolling: Bool let enablesDrawing: Bool let enablesPhotoInsertion: Bool @@ -474,7 +474,7 @@ extension CanvasViewController { } extension CanvasViewController { - func zoomChanged(_ zoomScale: CGFloat) { + private func zoomChanged(_ zoomScale: CGFloat) { #if os(macOS) let rect = scrollView.documentVisibleRect scrollView.setMagnification(zoomScale, centeredAt: CGPoint(x: rect.midX, y: rect.midY)) @@ -483,7 +483,7 @@ extension CanvasViewController { #endif } - func lockModeChanged(_ state: Bool) { + private func lockModeChanged(_ state: Bool) { #if os(macOS) #warning("TODO: implement for macos") #else @@ -491,7 +491,7 @@ extension CanvasViewController { #endif } - func gridModeChanged(_ mode: GridMode) { + private func gridModeChanged(_ mode: GridMode) { drawingView.disableUserInteraction() renderer.setRedrawsGraphicRender() renderView.draw() @@ -500,7 +500,7 @@ extension CanvasViewController { } extension CanvasViewController { - func historyUndid() { + private func historyUndid() { guard let event = history.undo() else { return } drawingView.disableUserInteraction() canvas.graphicContext.undoGraphic(for: event) @@ -509,7 +509,7 @@ extension CanvasViewController { drawingView.enableUserInteraction() } - func historyRedid() { + private func historyRedid() { guard let event = history.redo() else { return } drawingView.disableUserInteraction() canvas.graphicContext.redoGraphic(for: event) diff --git a/Memola/Canvas/View/Bridge/Views/AppKit/NSCenterClipView.swift b/Memola/Canvas/View/Bridge/Views/AppKit/NSCenterClipView.swift index a9c48dd..94d785a 100644 --- a/Memola/Canvas/View/Bridge/Views/AppKit/NSCenterClipView.swift +++ b/Memola/Canvas/View/Bridge/Views/AppKit/NSCenterClipView.swift @@ -8,7 +8,7 @@ #if canImport(AppKit) import AppKit -class NSCenterClipView: NSClipView { +final class NSCenterClipView: NSClipView { override func constrainBoundsRect(_ proposedBounds: NSRect) -> NSRect { var rect = super.constrainBoundsRect(proposedBounds) if let containerView = self.documentView { diff --git a/Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift b/Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift index 280cfc4..4ca1a55 100644 --- a/Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift +++ b/Memola/Canvas/View/Bridge/Views/AppKit/NSSyncScrollView.swift @@ -13,7 +13,7 @@ protocol NSSyncScrollViewDelegate: AnyObject { func scrollViewDidScroll(_ scrollView: NSSyncScrollView) } -class NSSyncScrollView: NSScrollView { +final class NSSyncScrollView: NSScrollView { weak var delegate: NSSyncScrollViewDelegate? override func magnify(with event: NSEvent) { diff --git a/Memola/Canvas/View/Bridge/Views/DrawingView.swift b/Memola/Canvas/View/Bridge/Views/DrawingView.swift index 73e7ff2..71833c2 100644 --- a/Memola/Canvas/View/Bridge/Views/DrawingView.swift +++ b/Memola/Canvas/View/Bridge/Views/DrawingView.swift @@ -9,10 +9,10 @@ import SwiftUI import MetalKit import Foundation -class DrawingView: Platform.View { - let tool: Tool - let canvas: Canvas - let history: History +final class DrawingView: Platform.View { + private let tool: Tool + private let canvas: Canvas + private let history: History let renderView: MTKView var ratio: CGFloat { canvas.size.width / bounds.width } diff --git a/Memola/Canvas/View/Canvas/CanvasView.swift b/Memola/Canvas/View/Canvas/CanvasView.swift index 9c03657..ba1f52e 100644 --- a/Memola/Canvas/View/Canvas/CanvasView.swift +++ b/Memola/Canvas/View/Canvas/CanvasView.swift @@ -8,9 +8,15 @@ import SwiftUI struct CanvasView: Platform.ViewControllerRepresentable { - @ObservedObject var tool: Tool - @ObservedObject var canvas: Canvas - @ObservedObject var history: History + @ObservedObject private var tool: Tool + @ObservedObject private var canvas: Canvas + @ObservedObject private var history: History + + init(tool: Tool, canvas: Canvas, history: History) { + self.tool = tool + self.canvas = canvas + self.history = history + } #if os(macOS) func makeNSViewController(context: Context) -> CanvasViewController { diff --git a/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift b/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift index 34f458c..3b0e4d0 100644 --- a/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift +++ b/Memola/Components/ViewModifiers/ContextMenuViewModifier.swift @@ -9,9 +9,15 @@ import SwiftUI import Foundation private struct ContextMenuViewModifier: ViewModifier { - let condition: Bool - let menuItems: () -> MenuContent - let preview: () -> Preview + private let condition: Bool + private let menuItems: () -> MenuContent + private let preview: () -> Preview + + init(condition: Bool, @ViewBuilder menuItems: @escaping () -> MenuContent, @ViewBuilder preview: @escaping () -> Preview) { + self.condition = condition + self.menuItems = menuItems + self.preview = preview + } @ViewBuilder func body(content: Content) -> some View { diff --git a/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift b/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift index 10c74f6..6f5f973 100644 --- a/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift +++ b/Memola/Components/ViewModifiers/OnDismissSearchViewModifier.swift @@ -8,9 +8,13 @@ import SwiftUI private struct OnDismissSearchViewModifier: ViewModifier { - @Environment(\.dismissSearch) var dismissSearch + @Environment(\.dismissSearch) private var dismissSearch - @Binding var isActive: Bool + @Binding private var isActive: Bool + + init(isActive: Binding) { + self._isActive = isActive + } func body(content: Content) -> some View { content diff --git a/Memola/Components/ViewModifiers/OnDragViewModifier.swift b/Memola/Components/ViewModifiers/OnDragViewModifier.swift index c31c141..1305779 100644 --- a/Memola/Components/ViewModifiers/OnDragViewModifier.swift +++ b/Memola/Components/ViewModifiers/OnDragViewModifier.swift @@ -9,9 +9,15 @@ import SwiftUI import Foundation private struct OnDragViewModifier: ViewModifier { - let condition: Bool - let data: () -> NSItemProvider - let preview: () -> Preview + private let condition: Bool + private let data: () -> NSItemProvider + private let preview: () -> Preview + + init(condition: Bool, data: @escaping () -> NSItemProvider, @ViewBuilder preview: @escaping () -> Preview) { + self.condition = condition + self.data = data + self.preview = preview + } @ViewBuilder func body(content: Content) -> some View { diff --git a/Memola/Components/Views/CameraView/CameraView.swift b/Memola/Components/Views/CameraView/CameraView.swift index 523a4ea..25abccd 100644 --- a/Memola/Components/Views/CameraView/CameraView.swift +++ b/Memola/Components/Views/CameraView/CameraView.swift @@ -9,12 +9,17 @@ import SwiftUI #if os(iOS) struct CameraView: UIViewControllerRepresentable { - @Binding var image: UIImage? + @Binding private var image: UIImage? - @ObservedObject var canvas: Canvas + @ObservedObject private var canvas: Canvas @Environment(\.dismiss) private var dismiss + init(image: Binding, canvas: Canvas) { + self._image = image + self.canvas = canvas + } + func makeUIViewController(context: Context) -> UIImagePickerController { let picker = UIImagePickerController() picker.sourceType = .camera diff --git a/Memola/Components/Views/ColorPicker/ColorPicker.swift b/Memola/Components/Views/ColorPicker/ColorPicker.swift index fe29a9e..5346e61 100644 --- a/Memola/Components/Views/ColorPicker/ColorPicker.swift +++ b/Memola/Components/Views/ColorPicker/ColorPicker.swift @@ -9,14 +9,18 @@ import SwiftUI import Foundation struct ColorPicker: View { - @State var hue: Double = 1 - @State var saturation: Double = 0 - @State var brightness: Double = 1 - @State var alpha: Double = 1 + @State private var hue: Double = 1 + @State private var saturation: Double = 0 + @State private var brightness: Double = 1 + @State private var alpha: Double = 1 - @Binding var color: Color + @Binding private var color: Color - let size: CGFloat = 20 + private let size: CGFloat = 20 + + init(color: Binding) { + self._color = color + } var body: some View { VStack(spacing: 10) { @@ -43,7 +47,7 @@ struct ColorPicker: View { } @ViewBuilder - var colorPicker: some View { + private var colorPicker: some View { GeometryReader { proxy in ZStack { Color(hue: hue, saturation: 1, brightness: 1) @@ -92,7 +96,7 @@ struct ColorPicker: View { } @ViewBuilder - var hueSlider: some View { + private var hueSlider: some View { GeometryReader { proxy in ZStack(alignment: .leading) { LinearGradient( @@ -138,7 +142,7 @@ struct ColorPicker: View { } @ViewBuilder - var alphaSlider: some View { + private var alphaSlider: some View { GeometryReader { proxy in let color = Color(hue: hue, saturation: saturation, brightness: brightness) ZStack(alignment: .leading) { @@ -190,7 +194,7 @@ struct ColorPicker: View { .frame(height: size) } - func updateColor() { + private func updateColor() { color = Color(hue: hue, saturation: saturation, brightness: brightness).opacity(0.7 * alpha + 0.3) } } diff --git a/Memola/Components/Views/Placeholder/Placeholder.swift b/Memola/Components/Views/Placeholder/Placeholder.swift index c2d2828..e74cb23 100644 --- a/Memola/Components/Views/Placeholder/Placeholder.swift +++ b/Memola/Components/Views/Placeholder/Placeholder.swift @@ -8,9 +8,13 @@ import SwiftUI struct Placeholder: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - let info: Info + private let info: Info + + init(info: Info) { + self.info = info + } var body: some View { VStack(spacing: 15) { diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index a319f08..bfccddf 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -8,14 +8,14 @@ import SwiftUI struct DashboardView: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @StateObject var memoManager: MemoManager = .shared + @StateObject private var memoManager: MemoManager = .shared - @State var sidebarItem: SidebarItem? = .memos - @AppStorage("memola.app.scene.side-bar.column-visibility") var columnVisibility: NavigationSplitViewVisibility = .all + @State private var sidebarItem: SidebarItem? = .memos + @AppStorage("memola.app.scene.side-bar.column-visibility") private var columnVisibility: NavigationSplitViewVisibility = .all - @Namespace var namespace + @Namespace private var namespace var body: some View { #if os(macOS) diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 9e9569a..78c597b 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -8,21 +8,21 @@ import SwiftUI struct MemosView: View { - @Environment(\.shortcut) var shortcut - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.shortcut) private var shortcut + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @FetchRequest var memoObjects: FetchedResults + @FetchRequest private var memoObjects: FetchedResults - @State var query: String = "" - @State var currentDate: Date = .now - @State var isActiveSearch: Bool = false + @State private var query: String = "" + @State private var currentDate: Date = .now + @State private var isActiveSearch: Bool = false - @AppStorage("memola.memo-objects.memos.sort") var sort: Sort = .recent - @AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none + @AppStorage("memola.memo-objects.memos.sort") private var sort: Sort = .recent + @AppStorage("memola.memo-objects.memos.filter") private var filter: Filter = .none - let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() + private let timer = Timer.publish(every: 60, on: .main, in: .common).autoconnect() - var placeholder: Placeholder.Info { + private var placeholder: Placeholder.Info { query.isEmpty ? .memoEmpty : .memoNotFound } @@ -157,12 +157,12 @@ struct MemosView: View { .onReceive(timer) { date in currentDate = date } - .onReceive(shortcut.publisher()) { shortcut in + .onReceive(shortcut.publisher) { shortcut in handleShortcut(for: shortcut) } } - func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View { + private func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View { MemoCard(memoObject: memoObject, cellWidth: cellWidth) { card in card .contextMenu { @@ -208,7 +208,7 @@ struct MemosView: View { } } - func createMemo(title: String) { + private func createMemo(title: String) { let memoObject = MemoObject(\.viewContext) memoObject.title = title memoObject.createdAt = .now @@ -258,11 +258,11 @@ struct MemosView: View { } } - func openMemo(for memo: MemoObject) { + private func openMemo(for memo: MemoObject) { MemoManager.shared.openMemo(memo) } - func updatePredicate() { + private func updatePredicate() { var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")] if !query.isEmpty { predicates.append(NSPredicate(format: "title contains[c] %@", query)) @@ -273,14 +273,14 @@ struct MemosView: View { memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates) } - func toggleFavorite(for memo: MemoObject) { + private func toggleFavorite(for memo: MemoObject) { memo.isFavorite.toggle() withPersistence(\.viewContext) { context in try context.saveIfNeeded() } } - func markAsTrash(for memo: MemoObject) { + private func markAsTrash(for memo: MemoObject) { memo.isTrash = true memo.deletedAt = .now withPersistence(\.viewContext) { context in @@ -288,7 +288,7 @@ struct MemosView: View { } } - func handleShortcut(for shortcut: Shortcuts) { + private func handleShortcut(for shortcut: Shortcuts) { switch shortcut { case .newMemo: if MemoManager.shared.memoObject == nil { diff --git a/Memola/Features/Dashboard/Details/Shared/MemoCard.swift b/Memola/Features/Dashboard/Details/Shared/MemoCard.swift index 9f75ee5..dfde35f 100644 --- a/Memola/Features/Dashboard/Details/Shared/MemoCard.swift +++ b/Memola/Features/Dashboard/Details/Shared/MemoCard.swift @@ -8,12 +8,12 @@ import SwiftUI struct MemoCard: View { - let memoObject: MemoObject - let cellWidth: CGFloat - let modifyPreview: ((MemoPreview) -> Preview)? - let details: () -> Detail + private let memoObject: MemoObject + private let cellWidth: CGFloat + private let modifyPreview: ((MemoPreview) -> Preview)? + private let details: () -> Detail - init(memoObject: MemoObject, cellWidth: CGFloat, @ViewBuilder modifyPreview: @escaping (MemoPreview) -> Preview, @ViewBuilder details: @escaping () -> Detail) { + init(memoObject: MemoObject, cellWidth: CGFloat, modifyPreview: ((MemoPreview) -> Preview)?, @ViewBuilder details: @escaping () -> Detail) { self.memoObject = memoObject self.cellWidth = cellWidth self.modifyPreview = modifyPreview diff --git a/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift b/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift index 2b4fa69..e3b5315 100644 --- a/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift +++ b/Memola/Features/Dashboard/Details/Shared/MemoGrid.swift @@ -8,12 +8,18 @@ import SwiftUI struct MemoGrid: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass - let memoObjects: FetchedResults - let placeholder: Placeholder.Info - @ViewBuilder let card: (MemoObject, CGFloat) -> Card + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + private let memoObjects: FetchedResults + private let placeholder: Placeholder.Info + @ViewBuilder private let card: (MemoObject, CGFloat) -> Card - var maxCellWidth: CGFloat { + init(memoObjects: FetchedResults, placeholder: Placeholder.Info, @ViewBuilder card: @escaping (MemoObject, CGFloat) -> Card) { + self.memoObjects = memoObjects + self.placeholder = placeholder + self.card = card + } + + private var maxCellWidth: CGFloat { if horizontalSizeClass == .compact { return 180 } diff --git a/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift b/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift index b57f80f..6609f59 100644 --- a/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift +++ b/Memola/Features/Dashboard/Details/Shared/MemoPreview.swift @@ -8,11 +8,17 @@ import SwiftUI struct MemoPreview: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - let preview: Data? - let cellWidth: CGFloat - var cellHeight: CGFloat { + private let preview: Data? + private let cellWidth: CGFloat + + init(preview: Data?, cellWidth: CGFloat) { + self.preview = preview + self.cellWidth = cellWidth + } + + private var cellHeight: CGFloat { if horizontalSizeClass == .compact { return 120 } diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index bda573c..3336c90 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -8,19 +8,19 @@ import SwiftUI struct TrashView: View { - @Environment(\.shortcut) var shortcut - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.shortcut) private var shortcut + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @FetchRequest var memoObjects: FetchedResults + @FetchRequest private var memoObjects: FetchedResults - @State var query: String = "" - @State var restoredMemo: MemoObject? - @State var deletedMemo: MemoObject? - @State var isActiveSearch: Bool = false + @State private var query: String = "" + @State private var restoredMemo: MemoObject? + @State private var deletedMemo: MemoObject? + @State private var isActiveSearch: Bool = false - @Binding var sidebarItem: SidebarItem? + @Binding private var sidebarItem: SidebarItem? - var placeholder: Placeholder.Info { + private var placeholder: Placeholder.Info { query.isEmpty ? .trashEmpty : .trashNotFound } @@ -102,7 +102,7 @@ struct TrashView: View { } } - func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View { + private func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View { MemoCard(memoObject: memoObject, cellWidth: cellWidth) { card in card .contextMenu { @@ -131,7 +131,7 @@ struct TrashView: View { } } - func updatePredicate() { + private func updatePredicate() { var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = YES")] if !query.isEmpty { predicates.append(NSPredicate(format: "title contains[c] %@", query)) @@ -139,7 +139,7 @@ struct TrashView: View { memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates) } - func restoreMemo(for memo: MemoObject?) { + private func restoreMemo(for memo: MemoObject?) { guard let memo else { return } memo.isTrash = false memo.deletedAt = nil @@ -148,7 +148,7 @@ struct TrashView: View { } } - func restoreAndOpenMemo(for memo: MemoObject?) { + private func restoreAndOpenMemo(for memo: MemoObject?) { restoreMemo(for: memo) self.sidebarItem = .memos if let memo { @@ -156,7 +156,7 @@ struct TrashView: View { } } - func deleteMemo(for memo: MemoObject?) { + private func deleteMemo(for memo: MemoObject?) { guard let memo else { return } withPersistenceSync(\.viewContext) { context in context.delete(memo) diff --git a/Memola/Features/Dashboard/Sidebar/Sidebar.swift b/Memola/Features/Dashboard/Sidebar/Sidebar.swift index 0314677..3d1794f 100644 --- a/Memola/Features/Dashboard/Sidebar/Sidebar.swift +++ b/Memola/Features/Dashboard/Sidebar/Sidebar.swift @@ -8,10 +8,15 @@ import SwiftUI struct Sidebar: View { - let sidebarItems: [SidebarItem] = [.memos, .trash] - @Binding var sidebarItem: SidebarItem? + private let sidebarItems: [SidebarItem] = [.memos, .trash] + @Binding private var sidebarItem: SidebarItem? - let horizontalSizeClass: UserInterfaceSizeClass? + private let horizontalSizeClass: UserInterfaceSizeClass? + + init(sidebarItem: Binding, horizontalSizeClass: UserInterfaceSizeClass?) { + self._sidebarItem = sidebarItem + self.horizontalSizeClass = horizontalSizeClass + } var body: some View { List(selection: $sidebarItem) { @@ -51,7 +56,7 @@ struct Sidebar: View { } extension Sidebar { - struct SidebarItemButtonStyle: ButtonStyle { + fileprivate struct SidebarItemButtonStyle: ButtonStyle { let state: State func makeBody(configuration: Configuration) -> some View { diff --git a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift index 9c352b6..b8f3387 100644 --- a/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift +++ b/Memola/Features/Memo/ElementToolbar/ElementToolbar.swift @@ -10,12 +10,17 @@ import PhotosUI import AVFoundation struct ElementToolbar: View { - @Environment(\.colorScheme) var colorScheme - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.colorScheme) private var colorScheme + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - let size: CGFloat = 40 - @ObservedObject var tool: Tool - @ObservedObject var canvas: Canvas + private let size: CGFloat = 40 + @ObservedObject private var tool: Tool + @ObservedObject private var canvas: Canvas + + init(tool: Tool, canvas: Canvas) { + self.tool = tool + self.canvas = canvas + } var body: some View { Group { @@ -41,7 +46,7 @@ struct ElementToolbar: View { .foregroundStyle(Color.accentColor) } - var regularToolbar: some View { + private var regularToolbar: some View { HStack(spacing: 0) { Button { withAnimation { @@ -124,7 +129,7 @@ struct ElementToolbar: View { .transition(.move(edge: .top).combined(with: .blurReplace)) } - var compactToolbar: some View { + private var compactToolbar: some View { HStack(spacing: 0) { Button { withAnimation { diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 1891047..3d28666 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -9,17 +9,17 @@ import SwiftUI import CoreData struct MemoView: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @StateObject var tool: Tool - @StateObject var canvas: Canvas - @StateObject var history: History + @StateObject private var tool: Tool + @StateObject private var canvas: Canvas + @StateObject private var history: History - @State var title: String - @FocusState var textFieldState: Bool + @State private var title: String + @FocusState private var textFieldState: Bool - let memo: MemoObject - let size: CGFloat = 40 + private let memo: MemoObject + private let size: CGFloat = 40 init(memo: MemoObject) { self.memo = memo @@ -63,7 +63,7 @@ struct MemoView: View { } } - var canvasView: some View { + private var canvasView: some View { CanvasView(tool: tool, canvas: canvas, history: history) .ignoresSafeArea() .overlay(alignment: .trailing) { @@ -88,7 +88,7 @@ struct MemoView: View { } } - var compactCanvasView: some View { + private var compactCanvasView: some View { CanvasView(tool: tool, canvas: canvas, history: history) .ignoresSafeArea() .overlay(alignment: .bottom) { @@ -115,7 +115,7 @@ struct MemoView: View { } @ViewBuilder - var zoomControl: some View { + private var zoomControl: some View { let upperBound: CGFloat = 400 let lowerBound: CGFloat = 10 let zoomScale: CGFloat = (((canvas.zoomScale - canvas.minimumZoomScale) * (upperBound - lowerBound) / (canvas.maximumZoomScale - canvas.minimumZoomScale)) + lowerBound).rounded() @@ -187,7 +187,7 @@ struct MemoView: View { #endif } - func loadingIndicator(_ title: String) -> some View { + private func loadingIndicator(_ title: String) -> some View { ProgressView { Text(title) } diff --git a/Memola/Features/Memo/PenDock/PenDock.swift b/Memola/Features/Memo/PenDock/PenDock.swift index 0f2d2e0..f11f4d1 100644 --- a/Memola/Features/Memo/PenDock/PenDock.swift +++ b/Memola/Features/Memo/PenDock/PenDock.swift @@ -8,25 +8,30 @@ import SwiftUI struct PenDock: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass - @ObservedObject var tool: Tool - @ObservedObject var canvas: Canvas + @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @ObservedObject private var tool: Tool + @ObservedObject private var canvas: Canvas - let size: CGFloat = 40 - let penPropertySize: CGFloat = 32 - var width: CGFloat { + private let size: CGFloat = 40 + private let penPropertySize: CGFloat = 32 + private var width: CGFloat { horizontalSizeClass == .compact ? size / 2 : size } - var height: CGFloat { + private var height: CGFloat { horizontalSizeClass == .compact ? size : size / 2 } - @State var refreshScrollId: UUID = UUID() - @State var opensColorPicker: Bool = false + @State private var refreshScrollId: UUID = UUID() + @State private var opensColorPicker: Bool = false #if os(macOS) - @State var showsThinknessPicker: Bool = false + @State private var showsThinknessPicker: Bool = false #endif + init(tool: Tool, canvas: Canvas) { + self.tool = tool + self.canvas = canvas + } + var body: some View { #if os(macOS) GeometryReader { proxy in @@ -101,7 +106,7 @@ struct PenDock: View { } @ViewBuilder - var penItemList: some View { + private var penItemList: some View { VStack(alignment: .trailing, spacing: 0) { ScrollViewReader { proxy in ScrollView(.vertical, showsIndicators: false) { @@ -139,7 +144,7 @@ struct PenDock: View { } @ViewBuilder - var compactPenItemList: some View { + private var compactPenItemList: some View { ScrollViewReader { proxy in ScrollView(.horizontal, showsIndicators: false) { LazyHStack(spacing: 0) { @@ -165,7 +170,7 @@ struct PenDock: View { } } - func penItem(_ pen: Pen) -> some View { + private func penItem(_ pen: Pen) -> some View { ZStack { penShadow(pen) if let tip = pen.style.icon.tip { @@ -242,7 +247,7 @@ struct PenDock: View { } } - func compactPenItem(_ pen: Pen) -> some View { + private func compactPenItem(_ pen: Pen) -> some View { ZStack { compactPenShadow(pen) if let tip = pen.style.compactIcon.tip { @@ -319,7 +324,7 @@ struct PenDock: View { } @ViewBuilder - var penPropertyTool: some View { + private var penPropertyTool: some View { if let pen = tool.selectedPen { VStack(spacing: 5) { if pen.strokeStyle == .marker { @@ -340,7 +345,7 @@ struct PenDock: View { } @ViewBuilder - var compactPenPropertyTool: some View { + private var compactPenPropertyTool: some View { if let pen = tool.selectedPen { HStack(spacing: 8) { penThicknessPicker(pen) @@ -356,7 +361,7 @@ struct PenDock: View { } } - func penColorPicker(_ pen: Pen) -> some View { + private func penColorPicker(_ pen: Pen) -> some View { Button { opensColorPicker = true } label: { @@ -409,7 +414,7 @@ struct PenDock: View { } @ViewBuilder - func penThicknessPicker(_ pen: Pen) -> some View { + private func penThicknessPicker(_ pen: Pen) -> some View { let minimum: CGFloat = pen.style.thickness.min let maximum: CGFloat = pen.style.thickness.max let start: CGFloat = 4 @@ -453,7 +458,7 @@ struct PenDock: View { } } - var newPenButton: some View { + private var newPenButton: some View { Button { createNewPen() } label: { @@ -474,7 +479,7 @@ struct PenDock: View { #endif } - func penPreview(_ pen: Pen) -> some View { + private func penPreview(_ pen: Pen) -> some View { ZStack { if let tip = pen.style.icon.tip { Image(tip) @@ -490,7 +495,7 @@ struct PenDock: View { .padding(.leading, 10) } - func compactPenPreview(_ pen: Pen) -> some View { + private func compactPenPreview(_ pen: Pen) -> some View { ZStack { if let tip = pen.style.compactIcon.tip { Image(tip) @@ -506,7 +511,7 @@ struct PenDock: View { .padding(.horizontal, 5) } - func penShadow(_ pen: Pen) -> some View { + private func penShadow(_ pen: Pen) -> some View { ZStack { Group { if let tip = pen.style.icon.tip { @@ -530,7 +535,7 @@ struct PenDock: View { } } - func compactPenShadow(_ pen: Pen) -> some View { + private func compactPenShadow(_ pen: Pen) -> some View { ZStack { Group { if let tip = pen.style.compactIcon.tip { @@ -554,7 +559,7 @@ struct PenDock: View { } } - func createNewPen() { + private func createNewPen() { let pen = PenObject.createObject(\.viewContext, penStyle: .marker) var selectedPen = tool.selectedPen selectedPen = (selectedPen?.strokeStyle == .marker ? (selectedPen ?? tool.pens.last) : tool.pens.last) diff --git a/Memola/Features/Memo/PenDock/PenDropDelegate.swift b/Memola/Features/Memo/PenDock/PenDropDelegate.swift index 8557f34..e5dfc31 100644 --- a/Memola/Features/Memo/PenDock/PenDropDelegate.swift +++ b/Memola/Features/Memo/PenDock/PenDropDelegate.swift @@ -9,9 +9,15 @@ import SwiftUI import Foundation struct PenDropDelegate: DropDelegate { - let id: String - @ObservedObject var tool: Tool - let action: () -> Void + private let id: String + @ObservedObject private var tool: Tool + private let action: () -> Void + + init(id: String, tool: Tool, action: @escaping () -> Void) { + self.id = id + self.tool = tool + self.action = action + } func performDrop(info: DropInfo) -> Bool { tool.draggedPen = nil diff --git a/Memola/Features/Memo/PhotoDock/PhotoDock.swift b/Memola/Features/Memo/PhotoDock/PhotoDock.swift index bdc0983..66f4116 100644 --- a/Memola/Features/Memo/PhotoDock/PhotoDock.swift +++ b/Memola/Features/Memo/PhotoDock/PhotoDock.swift @@ -9,16 +9,21 @@ import SwiftUI import PhotosUI struct PhotoDock: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass - - let size: CGFloat = 40 + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @ObservedObject var tool: Tool - @ObservedObject var canvas: Canvas + private let size: CGFloat = 40 - @State var opensCamera: Bool = false - @State var isCameraAccessDenied: Bool = false - @State var photosPickerItem: PhotosPickerItem? + @ObservedObject private var tool: Tool + @ObservedObject private var canvas: Canvas + + @State private var opensCamera: Bool = false + @State private var isCameraAccessDenied: Bool = false + @State private var photosPickerItem: PhotosPickerItem? + + init(tool: Tool, canvas: Canvas) { + self.tool = tool + self.canvas = canvas + } var body: some View { Group { @@ -71,7 +76,7 @@ struct PhotoDock: View { } } - var photoOption: some View { + private var photoOption: some View { VStack(spacing: 0) { #if os(iOS) Button { @@ -126,7 +131,7 @@ struct PhotoDock: View { .transition(.move(edge: .trailing).combined(with: .blurReplace)) } - var compactPhotoOption: some View { + private var compactPhotoOption: some View { HStack(spacing: 0) { #if os(iOS) Button { @@ -181,7 +186,7 @@ struct PhotoDock: View { .transition(.move(edge: .bottom).combined(with: .blurReplace)) } - func openCamera() { + private func openCamera() { let status = AVCaptureDevice.authorizationStatus(for: .video) switch status { case .notDetermined: diff --git a/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift index 440d2f1..35e7a17 100644 --- a/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift +++ b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift @@ -8,10 +8,15 @@ import SwiftUI struct PhotoPreview: View { - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - let photoItem: PhotoItem - @ObservedObject var tool: Tool + private let photoItem: PhotoItem + @ObservedObject private var tool: Tool + + init(photoItem: PhotoItem, tool: Tool) { + self.photoItem = photoItem + self.tool = tool + } var body: some View { Image(image: photoItem.previewImage) diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index b6f0d59..fb4be13 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -9,19 +9,19 @@ import SwiftUI import Foundation struct Toolbar: View { - @Environment(\.dismiss) var dismiss - @Environment(\.horizontalSizeClass) var horizontalSizeClass + @Environment(\.dismiss) private var dismiss + @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @ObservedObject var tool: Tool - @ObservedObject var canvas: Canvas - @ObservedObject var history: History + @ObservedObject private var tool: Tool + @ObservedObject private var canvas: Canvas + @ObservedObject private var history: History - @State var title: String + @State private var title: String - @FocusState var textFieldState: Bool + @FocusState private var textFieldState: Bool - let size: CGFloat = 40 - let memo: MemoObject + private let size: CGFloat = 40 + private let memo: MemoObject init(memo: MemoObject, tool: Tool, canvas: Canvas, history: History) { self.memo = memo @@ -57,7 +57,7 @@ struct Toolbar: View { .foregroundStyle(Color.accentColor) } - var closeButton: some View { + private var closeButton: some View { Button { closeMemo() } label: { @@ -76,7 +76,7 @@ struct Toolbar: View { .transition(.move(edge: .top).combined(with: .blurReplace)) } - var titleField: some View { + private var titleField: some View { TextField("", text: $title) .focused($textFieldState) .textFieldStyle(.plain) @@ -100,7 +100,7 @@ struct Toolbar: View { .transition(.move(edge: .top).combined(with: .blurReplace)) } - var historyControl: some View { + private var historyControl: some View { HStack(spacing: 0) { Button { history.historyPublisher.send(.undo) @@ -135,7 +135,7 @@ struct Toolbar: View { .transition(.move(edge: .top).combined(with: .blurReplace)) } - var gridModeControl: some View { + private var gridModeControl: some View { #if os(macOS) Button { switch canvas.gridMode { @@ -183,7 +183,7 @@ struct Toolbar: View { #endif } - func closeMemo() { + private func closeMemo() { canvas.save(for: memo) { #if os(macOS) MemoManager.shared.closeMemo() diff --git a/Memola/Persistence/Core/Persistence.swift b/Memola/Persistence/Core/Persistence.swift index df7c3a2..e34a888 100644 --- a/Memola/Persistence/Core/Persistence.swift +++ b/Memola/Persistence/Core/Persistence.swift @@ -77,14 +77,6 @@ final class Persistence { fatalError("[Memola]: \(error.localizedDescription)") } }() - - static func loadMemo(of url: URL) -> MemoObject? { - let viewContext = shared.viewContext - guard let objectID = viewContext.persistentStoreCoordinator?.managedObjectID(forURIRepresentation: url) else { - return nil - } - return viewContext.object(with: objectID) as? MemoObject - } } // MARK: - Global Method diff --git a/Memola/Persistence/Objects/PenObject.swift b/Memola/Persistence/Objects/PenObject.swift index 4c51ad6..9be5f86 100644 --- a/Memola/Persistence/Objects/PenObject.swift +++ b/Memola/Persistence/Objects/PenObject.swift @@ -9,7 +9,7 @@ import CoreData import Foundation @objc(PenObject) -class PenObject: NSManagedObject { +final class PenObject: NSManagedObject { @NSManaged var color: [CGFloat] @NSManaged var style: Int16 @NSManaged var thickness: CGFloat diff --git a/Memola/Persistence/Objects/PhotoObject.swift b/Memola/Persistence/Objects/PhotoObject.swift index 10283e8..ac697c8 100644 --- a/Memola/Persistence/Objects/PhotoObject.swift +++ b/Memola/Persistence/Objects/PhotoObject.swift @@ -9,7 +9,7 @@ import CoreData import Foundation @objc(PhotoObject) -class PhotoObject: NSManagedObject { +final class PhotoObject: NSManagedObject { @NSManaged var width: CGFloat @NSManaged var originY: CGFloat @NSManaged var originX: CGFloat diff --git a/Memola/Persistence/Objects/ToolObject.swift b/Memola/Persistence/Objects/ToolObject.swift index 757d4c2..c5468d4 100644 --- a/Memola/Persistence/Objects/ToolObject.swift +++ b/Memola/Persistence/Objects/ToolObject.swift @@ -9,7 +9,7 @@ import CoreData import Foundation @objc(ToolObject) -class ToolObject: NSManagedObject { +final class ToolObject: NSManagedObject { @NSManaged var selection: Int16 @NSManaged var pens: NSMutableSet @NSManaged var memo: MemoObject? diff --git a/Memola/Shortcut/Commands/FileCommands.swift b/Memola/Shortcut/Commands/FileCommands.swift index 389205d..3a602ba 100644 --- a/Memola/Shortcut/Commands/FileCommands.swift +++ b/Memola/Shortcut/Commands/FileCommands.swift @@ -8,8 +8,8 @@ import SwiftUI struct FileCommands: Commands { - @Environment(\.shortcut) var shortcut - @FocusedValue(\.activeSceneKey) var appScene + @Environment(\.shortcut) private var shortcut + @FocusedValue(\.activeSceneKey) private var appScene var body: some Commands { CommandGroup(replacing: .newItem) { diff --git a/Memola/Shortcut/Commands/ViewCommands.swift b/Memola/Shortcut/Commands/ViewCommands.swift index aa4240f..2a0fecc 100644 --- a/Memola/Shortcut/Commands/ViewCommands.swift +++ b/Memola/Shortcut/Commands/ViewCommands.swift @@ -8,18 +8,18 @@ import SwiftUI struct ViewCommands: Commands { - @FocusedValue(\.activeSceneKey) var appScene + @ObservedObject private var application: Application + @FocusedValue(\.activeSceneKey) private var appScene + + init(application: Application) { + self.application = application + } var body: some Commands { CommandGroup(replacing: .toolbar) { if appScene == .trash || appScene == .memos { Button { - #if os(macOS) - guard let toolbar = NSApp.keyWindow?.toolbar else { return } - if let search = toolbar.items.first(where: { $0.itemIdentifier.rawValue == "com.apple.SwiftUI.search" }) as? NSSearchToolbarItem { - search.beginSearchInteraction() - } - #endif + application.activateSearchBar() } label: { Text("Find Memo") } diff --git a/Memola/Shortcut/Core/Shortcut.swift b/Memola/Shortcut/Core/Shortcut.swift index 57c4043..24c00ef 100644 --- a/Memola/Shortcut/Core/Shortcut.swift +++ b/Memola/Shortcut/Core/Shortcut.swift @@ -8,18 +8,18 @@ import Combine import Foundation -class Shortcut { +final class Shortcut { static let shared: Shortcut = .init() - private let shortcutPublisher = PassthroughSubject() + private let _publisher = PassthroughSubject() + + lazy var publisher: AnyPublisher = { + _publisher.eraseToAnyPublisher() + }() private init() { } func trigger(_ shortcut: Shortcuts) { - shortcutPublisher.send(shortcut) - } - - func publisher() -> AnyPublisher { - shortcutPublisher.eraseToAnyPublisher() + _publisher.send(shortcut) } } diff --git a/Memola/Shortcut/EnvironmentKeys/ShortcutKey.swift b/Memola/Shortcut/EnvironmentValues/ShortcutKey.swift similarity index 85% rename from Memola/Shortcut/EnvironmentKeys/ShortcutKey.swift rename to Memola/Shortcut/EnvironmentValues/ShortcutKey.swift index 6d9d2c9..4d1f1ab 100644 --- a/Memola/Shortcut/EnvironmentKeys/ShortcutKey.swift +++ b/Memola/Shortcut/EnvironmentValues/ShortcutKey.swift @@ -7,7 +7,7 @@ import SwiftUI -struct ShortcutKey: EnvironmentKey { +private struct ShortcutKey: EnvironmentKey { static var defaultValue: Shortcut = .shared } diff --git a/Memola/Utilies/AppScene/ActiveSceneKey.swift b/Memola/Utilies/AppScene/ActiveSceneKey.swift index 7e323b5..b27d6d7 100644 --- a/Memola/Utilies/AppScene/ActiveSceneKey.swift +++ b/Memola/Utilies/AppScene/ActiveSceneKey.swift @@ -7,7 +7,7 @@ import SwiftUI -struct ActiveSceneKey: FocusedValueKey { +private struct ActiveSceneKey: FocusedValueKey { typealias Value = AppScene } diff --git a/Memola/Utilies/SidebarVisibility/SidebarVisibility.swift b/Memola/Utilies/SidebarVisibility/SidebarVisibility.swift new file mode 100644 index 0000000..7125d01 --- /dev/null +++ b/Memola/Utilies/SidebarVisibility/SidebarVisibility.swift @@ -0,0 +1,13 @@ +// +// SidebarVisibility.swift +// Memola +// +// Created by Dscyre Scotti on 7/14/24. +// + +import Foundation + +enum SidebarVisibility { + case shown + case hidden +} From a286f7d8532de8f1b413190d83a3f68b9f1e0992 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 14 Jul 2024 16:53:16 +0700 Subject: [PATCH 18/22] feat: add toggle sidebar command --- Memola.xcodeproj/project.pbxproj | 12 ++++++++++++ Memola/App/Application.swift | 14 +++++++++++++- Memola/Canvas/Tool/Core/Tool.swift | 2 +- .../Dashboard/Dashboard/DashboardView.swift | 4 ++++ Memola/Shortcut/Commands/ViewCommands.swift | 15 ++++++++++++++- 5 files changed, 44 insertions(+), 3 deletions(-) diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index f442273..7f19d8e 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -47,6 +47,7 @@ EC6E3BD72C43C6A400DD20F3 /* Application.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BD62C43C6A400DD20F3 /* Application.swift */; }; EC6E3BD92C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */; }; EC6E3BDB2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */; }; + EC6E3BDE2C43D5A500DD20F3 /* SidebarVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BDC2C43D5A500DD20F3 /* SidebarVisibility.swift */; }; EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; }; EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; }; EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */; }; @@ -185,6 +186,7 @@ EC6E3BD62C43C6A400DD20F3 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = ""; }; EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnDismissSearchViewModifier.swift; sourceTree = ""; }; EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NavigationSplitViewVisibility++.swift"; sourceTree = ""; }; + EC6E3BDC2C43D5A500DD20F3 /* SidebarVisibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarVisibility.swift; sourceTree = ""; }; EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; }; EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = ""; }; EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = ""; }; @@ -519,6 +521,14 @@ path = Algorithms; sourceTree = ""; }; + EC6E3BDD2C43D5A500DD20F3 /* SidebarVisibility */ = { + isa = PBXGroup; + children = ( + EC6E3BDC2C43D5A500DD20F3 /* SidebarVisibility.swift */, + ); + path = SidebarVisibility; + sourceTree = ""; + }; EC7F6BDF2BE5E6E300A34A7B = { isa = PBXGroup; children = ( @@ -989,6 +999,7 @@ ECF7B2E52C391DFA004D2C57 /* Utilies */ = { isa = PBXGroup; children = ( + EC6E3BDD2C43D5A500DD20F3 /* SidebarVisibility */, EC2002EE2C417BBF002EBD5F /* AppScene */, ECF7B2CF2C39169C004D2C57 /* Extensions */, ECF7B2E22C39172D004D2C57 /* Platform */, @@ -1133,6 +1144,7 @@ ECA738DA2BE60FF100A4542E /* CacheRenderPass.swift in Sources */, ECF7B2DA2C39169C004D2C57 /* Float++.swift in Sources */, ECA738CD2BE60F2F00A4542E /* PointGridContext.swift in Sources */, + EC6E3BDE2C43D5A500DD20F3 /* SidebarVisibility.swift in Sources */, ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */, ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */, EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */, diff --git a/Memola/App/Application.swift b/Memola/App/Application.swift index fd9972f..acbbaf5 100644 --- a/Memola/App/Application.swift +++ b/Memola/App/Application.swift @@ -9,7 +9,7 @@ import Combine import SwiftUI final class Application: NSObject, ObservableObject { - + @Published private(set) var sidebarVisibility: SidebarVisibility = .shown } extension Application { @@ -23,6 +23,18 @@ extension Application { #warning("TODO: implement for ipad") #endif } + + func toggleSidebar() { + #if os(macOS) + NSApp.sendAction(#selector(NSSplitViewController.toggleSidebar(_:)), to: nil, from: nil) + #else + #warning("TODO: implement for ipad") + #endif + } + + func changeSidebarVisibility(_ visibility: SidebarVisibility) { + self.sidebarVisibility = visibility + } } #if os(macOS) diff --git a/Memola/Canvas/Tool/Core/Tool.swift b/Memola/Canvas/Tool/Core/Tool.swift index 244f3dc..f7911c2 100644 --- a/Memola/Canvas/Tool/Core/Tool.swift +++ b/Memola/Canvas/Tool/Core/Tool.swift @@ -11,7 +11,7 @@ import CoreData import Foundation final class Tool: NSObject, ObservableObject { - private let object: ToolObject + let object: ToolObject @Published var pens: [Pen] = [] diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index bfccddf..05a0ece 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -11,6 +11,7 @@ struct DashboardView: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass @StateObject private var memoManager: MemoManager = .shared + @EnvironmentObject private var application: Application @State private var sidebarItem: SidebarItem? = .memos @AppStorage("memola.app.scene.side-bar.column-visibility") private var columnVisibility: NavigationSplitViewVisibility = .all @@ -45,6 +46,9 @@ struct DashboardView: View { .transition(.move(edge: .bottom)) } } + .onChange(of: columnVisibility) { oldValue, newValue in + application.changeSidebarVisibility(newValue == .all ? .shown : .hidden) + } #else NavigationSplitView(columnVisibility: $columnVisibility) { Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) diff --git a/Memola/Shortcut/Commands/ViewCommands.swift b/Memola/Shortcut/Commands/ViewCommands.swift index 2a0fecc..beb2bc0 100644 --- a/Memola/Shortcut/Commands/ViewCommands.swift +++ b/Memola/Shortcut/Commands/ViewCommands.swift @@ -17,7 +17,7 @@ struct ViewCommands: Commands { var body: some Commands { CommandGroup(replacing: .toolbar) { - if appScene == .trash || appScene == .memos { + if appScene == .memos || appScene == .trash { Button { application.activateSearchBar() } label: { @@ -25,6 +25,19 @@ struct ViewCommands: Commands { } .keyboardShortcut("f", modifiers: [.command]) } + if appScene == .memos || appScene == .trash { + Button { + application.toggleSidebar() + } label: { + switch application.sidebarVisibility { + case .shown: + Text("Hide Sidebar") + case .hidden: + Text("Show Sidebar") + } + } + .keyboardShortcut("o", modifiers: [.command]) + } } } } From c1e2fa227ed05487806b5aef4931f77ffb7099cb Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 14 Jul 2024 17:03:27 +0700 Subject: [PATCH 19/22] refactor: remove memo manager --- Memola.xcodeproj/project.pbxproj | 4 -- Memola/App/Application.swift | 15 ++++++++ .../Dashboard/Dashboard/DashboardView.swift | 9 ++--- .../Dashboard/Dashboard/MemoManager.swift | 38 ------------------- .../Dashboard/Details/Memos/MemosView.swift | 6 ++- .../Dashboard/Details/Trash/TrashView.swift | 4 +- Memola/Features/Memo/Toolbar/Toolbar.swift | 6 ++- 7 files changed, 31 insertions(+), 51 deletions(-) delete mode 100644 Memola/Features/Dashboard/Dashboard/MemoManager.swift diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 7f19d8e..9b61856 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -37,7 +37,6 @@ EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF72C0F601A005DB0AF /* Node.swift */; }; EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */ = {isa = PBXBuildFile; fileRef = EC3565592BF060D900A4E0BF /* Quad.metal */; }; EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB112C1B2DD90008D976 /* ToolSelection.swift */; }; - EC3D67CC2C3AAD5E00359400 /* MemoManager.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC3D67CB2C3AAD5E00359400 /* MemoManager.swift */; }; EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC42F7842C25267000E86E96 /* ElementGroup.swift */; }; EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; }; EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */; }; @@ -175,7 +174,6 @@ EC2BEBF72C0F601A005DB0AF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = ""; }; EC3565592BF060D900A4E0BF /* Quad.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Quad.metal; sourceTree = ""; }; EC37FB112C1B2DD90008D976 /* ToolSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSelection.swift; sourceTree = ""; }; - EC3D67CB2C3AAD5E00359400 /* MemoManager.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = MemoManager.swift; sourceTree = ""; }; EC42F7842C25267000E86E96 /* ElementGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementGroup.swift; sourceTree = ""; }; EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = ""; }; EC5050062BF65CED00B4D86E /* PenDropDelegate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDropDelegate.swift; sourceTree = ""; }; @@ -309,7 +307,6 @@ EC01511C2C305C99008A115E /* Dashboard */ = { isa = PBXGroup; children = ( - EC3D67CB2C3AAD5E00359400 /* MemoManager.swift */, EC01511D2C305CA9008A115E /* DashboardView.swift */, ); path = Dashboard; @@ -1123,7 +1120,6 @@ EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */, EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */, EC2002F02C417BF1002EBD5F /* ActiveSceneKey.swift in Sources */, - EC3D67CC2C3AAD5E00359400 /* MemoManager.swift in Sources */, EC2002E12C416470002EBD5F /* Shortcut.swift in Sources */, EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */, ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */, diff --git a/Memola/App/Application.swift b/Memola/App/Application.swift index acbbaf5..84a91f9 100644 --- a/Memola/App/Application.swift +++ b/Memola/App/Application.swift @@ -9,9 +9,24 @@ import Combine import SwiftUI final class Application: NSObject, ObservableObject { + @Published var memoObject: MemoObject? @Published private(set) var sidebarVisibility: SidebarVisibility = .shown } +extension Application { + func openMemo(_ memoObject: MemoObject?) { + withAnimation(.easeOut) { + self.memoObject = memoObject + } + } + + func closeMemo() { + withAnimation(.easeOut) { + self.memoObject = nil + } + } +} + extension Application { func activateSearchBar() { #if os(macOS) diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index 05a0ece..a007ab7 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -10,7 +10,6 @@ import SwiftUI struct DashboardView: View { @Environment(\.horizontalSizeClass) private var horizontalSizeClass - @StateObject private var memoManager: MemoManager = .shared @EnvironmentObject private var application: Application @State private var sidebarItem: SidebarItem? = .memos @@ -32,10 +31,10 @@ struct DashboardView: View { MemosView() } } - .toolbar(memoManager.memoObject == nil ? .visible : .hidden, for: .windowToolbar) - .toolbarBackground(memoManager.memoObject == nil ? .clear : Color(nsColor: .windowBackgroundColor), for: .windowToolbar) + .toolbar(application.memoObject == nil ? .visible : .hidden, for: .windowToolbar) + .toolbarBackground(application.memoObject == nil ? .clear : Color(nsColor: .windowBackgroundColor), for: .windowToolbar) .overlay { - if let memo = memoManager.memoObject { + if let memo = application.memoObject { MemoView(memo: memo) .onDisappear { withPersistence(\.viewContext) { context in @@ -62,7 +61,7 @@ struct DashboardView: View { MemosView() } } - .fullScreenCover(item: $memoManager.memoObject) { memo in + .fullScreenCover(item: $application.memoObject) { memo in MemoView(memo: memo) .onDisappear { withPersistence(\.viewContext) { context in diff --git a/Memola/Features/Dashboard/Dashboard/MemoManager.swift b/Memola/Features/Dashboard/Dashboard/MemoManager.swift deleted file mode 100644 index 4713583..0000000 --- a/Memola/Features/Dashboard/Dashboard/MemoManager.swift +++ /dev/null @@ -1,38 +0,0 @@ -// -// MemoManager.swift -// Memola -// -// Created by Dscyre Scotti on 7/7/24. -// - -import SwiftUI -import Foundation - -#warning("TODO: use environmnet instead of singleton") -class MemoManager: ObservableObject { - static let shared: MemoManager = .init() - - @Published var memoObject: MemoObject? - - private init() { } - - func openMemo(_ memoObject: MemoObject?) { - #if os(macOS) - withAnimation(.easeOut) { - self.memoObject = memoObject - } - #else - self.memoObject = memoObject - #endif - } - - func closeMemo() { - #if os(macOS) - withAnimation(.easeOut) { - self.memoObject = nil - } - #else - self.memoObject = nil - #endif - } -} diff --git a/Memola/Features/Dashboard/Details/Memos/MemosView.swift b/Memola/Features/Dashboard/Details/Memos/MemosView.swift index 78c597b..d40aa0e 100644 --- a/Memola/Features/Dashboard/Details/Memos/MemosView.swift +++ b/Memola/Features/Dashboard/Details/Memos/MemosView.swift @@ -11,6 +11,8 @@ struct MemosView: View { @Environment(\.shortcut) private var shortcut @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @EnvironmentObject private var application: Application + @FetchRequest private var memoObjects: FetchedResults @State private var query: String = "" @@ -259,7 +261,7 @@ struct MemosView: View { } private func openMemo(for memo: MemoObject) { - MemoManager.shared.openMemo(memo) + application.openMemo(memo) } private func updatePredicate() { @@ -291,7 +293,7 @@ struct MemosView: View { private func handleShortcut(for shortcut: Shortcuts) { switch shortcut { case .newMemo: - if MemoManager.shared.memoObject == nil { + if application.memoObject == nil { createMemo(title: "Untitled") } default: diff --git a/Memola/Features/Dashboard/Details/Trash/TrashView.swift b/Memola/Features/Dashboard/Details/Trash/TrashView.swift index 3336c90..a3f9f8d 100644 --- a/Memola/Features/Dashboard/Details/Trash/TrashView.swift +++ b/Memola/Features/Dashboard/Details/Trash/TrashView.swift @@ -11,6 +11,8 @@ struct TrashView: View { @Environment(\.shortcut) private var shortcut @Environment(\.horizontalSizeClass) private var horizontalSizeClass + @EnvironmentObject private var application: Application + @FetchRequest private var memoObjects: FetchedResults @State private var query: String = "" @@ -152,7 +154,7 @@ struct TrashView: View { restoreMemo(for: memo) self.sidebarItem = .memos if let memo { - MemoManager.shared.openMemo(memo) + application.openMemo(memo) } } diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index fb4be13..52cf9c9 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -12,6 +12,10 @@ struct Toolbar: View { @Environment(\.dismiss) private var dismiss @Environment(\.horizontalSizeClass) private var horizontalSizeClass + #if os(macOS) + @EnvironmentObject private var application: Application + #endif + @ObservedObject private var tool: Tool @ObservedObject private var canvas: Canvas @ObservedObject private var history: History @@ -186,7 +190,7 @@ struct Toolbar: View { private func closeMemo() { canvas.save(for: memo) { #if os(macOS) - MemoManager.shared.closeMemo() + application.closeMemo() #else dismiss() #endif From bd5701cf8861197db7a05de4420503a402749cac Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sun, 14 Jul 2024 18:16:13 +0700 Subject: [PATCH 20/22] feat: add redo and undo commands --- Memola/App/Application.swift | 8 +-- .../Dashboard/Dashboard/DashboardView.swift | 52 +++++++++++-------- Memola/Features/Memo/Memo/MemoView.swift | 2 + Memola/Shortcut/Commands/EditCommands.swift | 31 ++++++----- 4 files changed, 53 insertions(+), 40 deletions(-) diff --git a/Memola/App/Application.swift b/Memola/App/Application.swift index 84a91f9..8f473e9 100644 --- a/Memola/App/Application.swift +++ b/Memola/App/Application.swift @@ -15,15 +15,11 @@ final class Application: NSObject, ObservableObject { extension Application { func openMemo(_ memoObject: MemoObject?) { - withAnimation(.easeOut) { - self.memoObject = memoObject - } + self.memoObject = memoObject } func closeMemo() { - withAnimation(.easeOut) { - self.memoObject = nil - } + self.memoObject = nil } } diff --git a/Memola/Features/Dashboard/Dashboard/DashboardView.swift b/Memola/Features/Dashboard/Dashboard/DashboardView.swift index a007ab7..48cc22c 100644 --- a/Memola/Features/Dashboard/Dashboard/DashboardView.swift +++ b/Memola/Features/Dashboard/Dashboard/DashboardView.swift @@ -19,32 +19,40 @@ struct DashboardView: View { var body: some View { #if os(macOS) - NavigationSplitView(columnVisibility: $columnVisibility) { - Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) - } detail: { - switch sidebarItem { - case .memos: - MemosView() - case .trash: - TrashView(sidebarItem: $sidebarItem) - default: - MemosView() + ZStack { + if let memo = application.memoObject { + NavigationStack { + MemoView(memo: memo) + .onDisappear { + withPersistence(\.viewContext) { context in + try context.saveIfNeeded() + context.refreshAllObjects() + } + } + .navigationTitle("") + } + .transition(.opacity) + .matchedGeometryEffect(id: "pop-up", in: namespace) + } else { + NavigationSplitView(columnVisibility: $columnVisibility) { + Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass) + } detail: { + switch sidebarItem { + case .memos: + MemosView() + case .trash: + TrashView(sidebarItem: $sidebarItem) + default: + MemosView() + } + } + .transition(.opacity) + .matchedGeometryEffect(id: "pop-up", in: namespace) } } + .animation(.easeIn, value: application.memoObject) .toolbar(application.memoObject == nil ? .visible : .hidden, for: .windowToolbar) .toolbarBackground(application.memoObject == nil ? .clear : Color(nsColor: .windowBackgroundColor), for: .windowToolbar) - .overlay { - if let memo = application.memoObject { - MemoView(memo: memo) - .onDisappear { - withPersistence(\.viewContext) { context in - try context.saveIfNeeded() - context.refreshAllObjects() - } - } - .transition(.move(edge: .bottom)) - } - } .onChange(of: columnVisibility) { oldValue, newValue in application.changeSidebarVisibility(newValue == .all ? .shown : .hidden) } diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 3d28666..2f6385d 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -61,6 +61,8 @@ struct MemoView: View { loadingIndicator("Loading photo...") } } + .focusedSceneObject(history) + .focusedSceneValue(\.activeSceneKey, .memo) } private var canvasView: some View { diff --git a/Memola/Shortcut/Commands/EditCommands.swift b/Memola/Shortcut/Commands/EditCommands.swift index 159dce9..12d00d1 100644 --- a/Memola/Shortcut/Commands/EditCommands.swift +++ b/Memola/Shortcut/Commands/EditCommands.swift @@ -8,21 +8,28 @@ import SwiftUI struct EditCommands: Commands { + @FocusedValue(\.activeSceneKey) private var appScene + + @FocusedObject var history: History? + var body: some Commands { CommandGroup(replacing: .undoRedo) { - // memo view - Button { - - } label: { - Text("Undo") + if appScene == .memo, let history { + Button { + history.historyPublisher.send(.undo) + } label: { + Text("Undo") + } + .keyboardShortcut("z", modifiers: [.command]) + .disabled(history.undoDisabled) + Button { + history.historyPublisher.send(.redo) + } label: { + Text("Redo") + } + .keyboardShortcut("z", modifiers: [.command, .shift]) + .disabled(history.redoDisabled) } - .keyboardShortcut("z", modifiers: [.command]) - Button { - - } label: { - Text("Redo") - } - .keyboardShortcut("z", modifiers: [.command, .shift]) } CommandGroup(replacing: .pasteboard) { } } From 2e64ebc5705a13c92ea05d000a2043f06d5e3cc1 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Mon, 15 Jul 2024 22:09:32 +0700 Subject: [PATCH 21/22] feat: add tool and grid commands --- Memola/Canvas/Core/Canvas.swift | 13 ++++++ Memola/Canvas/Tool/Core/Tool.swift | 1 + Memola/Features/Memo/Memo/MemoView.swift | 2 + Memola/Features/Memo/Toolbar/Toolbar.swift | 9 +--- Memola/Shortcut/Commands/EditCommands.swift | 50 +++++++++++++++------ Memola/Shortcut/Commands/ViewCommands.swift | 17 +++++-- 6 files changed, 68 insertions(+), 24 deletions(-) diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index 589be72..ddb9f3b 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -181,6 +181,19 @@ extension Canvas { try context.saveIfNeeded() } } + + func toggleGridMode() { + let _gridMode: GridMode + switch gridMode { + case .none: + _gridMode = .point + case .point: + _gridMode = .line + case .line: + _gridMode = .none + } + setGridMode(_gridMode) + } } // MARK: - Stroke diff --git a/Memola/Canvas/Tool/Core/Tool.swift b/Memola/Canvas/Tool/Core/Tool.swift index f7911c2..12b1e2d 100644 --- a/Memola/Canvas/Tool/Core/Tool.swift +++ b/Memola/Canvas/Tool/Core/Tool.swift @@ -35,6 +35,7 @@ final class Tool: NSObject, ObservableObject { } func selectTool(_ selection: ToolSelection) { + guard self.selection != selection else { return } self.selection = selection withPersistence(\.viewContext) { [weak object] context in object?.selection = selection.rawValue diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 2f6385d..2030334 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -61,6 +61,8 @@ struct MemoView: View { loadingIndicator("Loading photo...") } } + .focusedSceneObject(tool) + .focusedSceneObject(canvas) .focusedSceneObject(history) .focusedSceneValue(\.activeSceneKey, .memo) } diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 52cf9c9..64d214d 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -142,14 +142,7 @@ struct Toolbar: View { private var gridModeControl: some View { #if os(macOS) Button { - switch canvas.gridMode { - case .none: - canvas.gridMode = .point - case .point: - canvas.gridMode = .line - case .line: - canvas.gridMode = .none - } + canvas.toggleGridMode() } label: { Image(systemName: canvas.gridMode.icon) .frame(width: size, height: size) diff --git a/Memola/Shortcut/Commands/EditCommands.swift b/Memola/Shortcut/Commands/EditCommands.swift index 12d00d1..a11c1ba 100644 --- a/Memola/Shortcut/Commands/EditCommands.swift +++ b/Memola/Shortcut/Commands/EditCommands.swift @@ -10,25 +10,49 @@ import SwiftUI struct EditCommands: Commands { @FocusedValue(\.activeSceneKey) private var appScene + @FocusedObject var tool: Tool? @FocusedObject var history: History? var body: some Commands { CommandGroup(replacing: .undoRedo) { - if appScene == .memo, let history { - Button { - history.historyPublisher.send(.undo) - } label: { - Text("Undo") + if appScene == .memo { + if let history { + Button { + history.historyPublisher.send(.undo) + } label: { + Text("Undo") + } + .keyboardShortcut("z", modifiers: [.command]) + .disabled(history.undoDisabled) + Button { + history.historyPublisher.send(.redo) + } label: { + Text("Redo") + } + .keyboardShortcut("z", modifiers: [.command, .shift]) + .disabled(history.redoDisabled) } - .keyboardShortcut("z", modifiers: [.command]) - .disabled(history.undoDisabled) - Button { - history.historyPublisher.send(.redo) - } label: { - Text("Redo") + Divider() + if let tool { + Button { + tool.selectTool(.hand) + } label: { + Text("Hand Tool") + } + .keyboardShortcut("h", modifiers: [.option]) + Button { + tool.selectTool(.pen) + } label: { + Text("Pen Tool") + } + .keyboardShortcut("p", modifiers: [.option]) + Button { + tool.selectTool(.photo) + } label: { + Text("Photo Tool") + } + .keyboardShortcut("p", modifiers: [.option, .shift]) } - .keyboardShortcut("z", modifiers: [.command, .shift]) - .disabled(history.redoDisabled) } } CommandGroup(replacing: .pasteboard) { } diff --git a/Memola/Shortcut/Commands/ViewCommands.swift b/Memola/Shortcut/Commands/ViewCommands.swift index beb2bc0..12290e0 100644 --- a/Memola/Shortcut/Commands/ViewCommands.swift +++ b/Memola/Shortcut/Commands/ViewCommands.swift @@ -9,23 +9,25 @@ import SwiftUI struct ViewCommands: Commands { @ObservedObject private var application: Application + @FocusedValue(\.activeSceneKey) private var appScene + @FocusedObject var canvas: Canvas? + init(application: Application) { self.application = application } var body: some Commands { CommandGroup(replacing: .toolbar) { - if appScene == .memos || appScene == .trash { + switch appScene { + case .memos, .trash: Button { application.activateSearchBar() } label: { Text("Find Memo") } .keyboardShortcut("f", modifiers: [.command]) - } - if appScene == .memos || appScene == .trash { Button { application.toggleSidebar() } label: { @@ -37,6 +39,15 @@ struct ViewCommands: Commands { } } .keyboardShortcut("o", modifiers: [.command]) + case .memo: + Button { + canvas?.toggleGridMode() + } label: { + Text("Change Grid Layout") + } + .keyboardShortcut("g", modifiers: [.option]) + default: + EmptyView() } } } From e45eba52fe718a25ebb927cfc4fb214203a31125 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Mon, 15 Jul 2024 22:15:39 +0700 Subject: [PATCH 22/22] feat: add onsubmit for textfield --- Memola/Features/Memo/Toolbar/Toolbar.swift | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index 64d214d..89e98ce 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -71,12 +71,12 @@ struct Toolbar: View { .clipShape(.rect(cornerRadius: 8)) .contentShape(.rect(cornerRadius: 8)) } + .disabled(title.isEmpty) #if os(iOS) .hoverEffect(.lift) #else .buttonStyle(.plain) #endif - .disabled(textFieldState) .transition(.move(edge: .top).combined(with: .blurReplace)) } @@ -102,6 +102,9 @@ struct Toolbar: View { } } .transition(.move(edge: .top).combined(with: .blurReplace)) + .onSubmit(of: .text) { + textFieldState = false + } } private var historyControl: some View { @@ -135,7 +138,6 @@ struct Toolbar: View { } .background(.regularMaterial) .clipShape(.rect(cornerRadius: 8)) - .disabled(textFieldState) .transition(.move(edge: .top).combined(with: .blurReplace)) }