mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-07-12 15:52:41 +02:00
Merge pull request #62 from dscyrescotti/feature/macos
Add macOS runtime target
This commit is contained in:
@@ -21,17 +21,21 @@
|
|||||||
EC1815082C2D980B00541369 /* Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1815072C2D980B00541369 /* Sort.swift */; };
|
EC1815082C2D980B00541369 /* Sort.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1815072C2D980B00541369 /* Sort.swift */; };
|
||||||
EC18150A2C2DA09E00541369 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1815092C2DA09E00541369 /* Filter.swift */; };
|
EC18150A2C2DA09E00541369 /* Filter.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC1815092C2DA09E00541369 /* Filter.swift */; };
|
||||||
EC18150D2C2DAC3700541369 /* Placeholder.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC18150C2C2DAC3700541369 /* Placeholder.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 */; };
|
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 */; };
|
||||||
|
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 */; };
|
EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */; };
|
||||||
EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */; };
|
EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */; };
|
||||||
EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF52C0F600D005DB0AF /* Box.swift */; };
|
EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF52C0F600D005DB0AF /* Box.swift */; };
|
||||||
EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC2BEBF72C0F601A005DB0AF /* Node.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 */; };
|
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 */; };
|
EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC37FB112C1B2DD90008D976 /* ToolSelection.swift */; };
|
||||||
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC42F7842C25267000E86E96 /* ElementGroup.swift */; };
|
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC42F7842C25267000E86E96 /* ElementGroup.swift */; };
|
||||||
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
|
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC4538882BEBCAE000A86FEC /* Quad.swift */; };
|
||||||
@@ -39,9 +43,15 @@
|
|||||||
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */; };
|
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */; };
|
||||||
EC5D40812C21CE270067F090 /* PhotoBackgroundRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */; };
|
EC5D40812C21CE270067F090 /* PhotoBackgroundRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */; };
|
||||||
EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC5E838F2BFDB69C00261D9C /* MovingAverage.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 */; };
|
||||||
|
EC6E3BDE2C43D5A500DD20F3 /* SidebarVisibility.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC6E3BDC2C43D5A500DD20F3 /* SidebarVisibility.swift */; };
|
||||||
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
|
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */; };
|
||||||
EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; };
|
EC7F6BF02BE5E6E400A34A7B /* Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */; };
|
||||||
EC7F6BF32BE5E6E400A34A7B /* Preview Assets.xcassets in Resources */ = {isa = PBXBuildFile; fileRef = EC7F6BF22BE5E6E400A34A7B /* Preview 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 */; };
|
EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */; };
|
||||||
EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */; };
|
EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */; };
|
||||||
EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */; };
|
EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */ = {isa = PBXBuildFile; fileRef = EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */; };
|
||||||
@@ -61,7 +71,6 @@
|
|||||||
ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */; };
|
ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */; };
|
||||||
ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389D2BE601CB00A4542E /* QuadVertex.swift */; };
|
ECA7389E2BE601CB00A4542E /* QuadVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389D2BE601CB00A4542E /* QuadVertex.swift */; };
|
||||||
ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA7389F2BE601E400A4542E /* ViewPortVertex.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 */; };
|
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A52BE6023F00A4542E /* GridUniforms.swift */; };
|
||||||
ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A72BE6025900A4542E /* GraphicUniforms.swift */; };
|
ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A72BE6025900A4542E /* GraphicUniforms.swift */; };
|
||||||
ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A92BE6026D00A4542E /* Uniforms.swift */; };
|
ECA738AA2BE6026D00A4542E /* Uniforms.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738A92BE6026D00A4542E /* Uniforms.swift */; };
|
||||||
@@ -88,22 +97,11 @@
|
|||||||
ECA738E02BE610B900A4542E /* EraserRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */; };
|
ECA738E02BE610B900A4542E /* EraserRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */; };
|
||||||
ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */; };
|
ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */; };
|
||||||
ECA738E42BE6110800A4542E /* Drawable.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738E32BE6110800A4542E /* Drawable.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 */; };
|
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; };
|
||||||
ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; };
|
ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; };
|
||||||
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; };
|
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; };
|
||||||
ECBE529C2C1D94A4006BDB3D /* CameraView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE529A2C1D94A4006BDB3D /* CameraView.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 */; };
|
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 */; };
|
ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; };
|
||||||
ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; };
|
ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; };
|
||||||
ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */; };
|
ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */; };
|
||||||
@@ -113,11 +111,30 @@
|
|||||||
ECD12A952C1B1FA200B96E12 /* PhotoVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A942C1B1FA200B96E12 /* PhotoVertex.swift */; };
|
ECD12A952C1B1FA200B96E12 /* PhotoVertex.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A942C1B1FA200B96E12 /* PhotoVertex.swift */; };
|
||||||
ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDAC07A2C318DBC0000ED77 /* ElementToolbar.swift */; };
|
ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDAC07A2C318DBC0000ED77 /* ElementToolbar.swift */; };
|
||||||
ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECDDD40C2C366B3B00DF9D5E /* PreviewRenderPass.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 */; };
|
ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BC2C00AA170045C53D /* EraserStroke.swift */; };
|
||||||
ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BE2C00AB440045C53D /* Stroke.swift */; };
|
ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883BE2C00AB440045C53D /* Stroke.swift */; };
|
||||||
ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */; };
|
ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */; };
|
||||||
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECEC01A72BEE11BA006DA24C /* QuadShape.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 */; };
|
||||||
|
ECF7B2E72C39544E004D2C57 /* NSCenterClipView.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECF7B2E62C39544E004D2C57 /* NSCenterClipView.swift */; };
|
||||||
ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA151F2BEF21EF00455818 /* MemoObject.swift */; };
|
ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA151F2BEF21EF00455818 /* MemoObject.swift */; };
|
||||||
ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA15212BEF21F500455818 /* CanvasObject.swift */; };
|
ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA15212BEF21F500455818 /* CanvasObject.swift */; };
|
||||||
ECFA15242BEF223300455818 /* GraphicContextObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA15232BEF223300455818 /* GraphicContextObject.swift */; };
|
ECFA15242BEF223300455818 /* GraphicContextObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECFA15232BEF223300455818 /* GraphicContextObject.swift */; };
|
||||||
@@ -141,17 +158,21 @@
|
|||||||
EC1815072C2D980B00541369 /* Sort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sort.swift; sourceTree = "<group>"; };
|
EC1815072C2D980B00541369 /* Sort.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Sort.swift; sourceTree = "<group>"; };
|
||||||
EC1815092C2DA09E00541369 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = "<group>"; };
|
EC1815092C2DA09E00541369 /* Filter.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Filter.swift; sourceTree = "<group>"; };
|
||||||
EC18150C2C2DAC3700541369 /* Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = "<group>"; };
|
EC18150C2C2DAC3700541369 /* Placeholder.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Placeholder.swift; sourceTree = "<group>"; };
|
||||||
EC18150E2C2DB13200541369 /* Date++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Date++.swift"; sourceTree = "<group>"; };
|
|
||||||
EC1B783C2BFA0AC9005A34E2 /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = "<group>"; };
|
EC1B783C2BFA0AC9005A34E2 /* Toolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Toolbar.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002D42C416033002EBD5F /* FileCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FileCommands.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002D62C4160EF002EBD5F /* EditCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditCommands.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002D82C4161ED002EBD5F /* ViewCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewCommands.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002DC2C4163E8002EBD5F /* AppCommands.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppCommands.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002E02C416470002EBD5F /* Shortcut.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcut.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002E42C416551002EBD5F /* Shortcuts.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Shortcuts.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002E82C4167C5002EBD5F /* ShortcutKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ShortcutKey.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002EC2C417B68002EBD5F /* AppScene.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AppScene.swift; sourceTree = "<group>"; };
|
||||||
|
EC2002EF2C417BF1002EBD5F /* ActiveSceneKey.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveSceneKey.swift; sourceTree = "<group>"; };
|
||||||
EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyStroke.swift; sourceTree = "<group>"; };
|
EC2106AC2C10C2A700FBE27C /* AnyStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AnyStroke.swift; sourceTree = "<group>"; };
|
||||||
EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = "<group>"; };
|
EC2BEBF32C0F5FF7005DB0AF /* RTree.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = RTree.swift; sourceTree = "<group>"; };
|
||||||
EC2BEBF52C0F600D005DB0AF /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
|
EC2BEBF52C0F600D005DB0AF /* Box.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Box.swift; sourceTree = "<group>"; };
|
||||||
EC2BEBF72C0F601A005DB0AF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = "<group>"; };
|
EC2BEBF72C0F601A005DB0AF /* Node.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Node.swift; sourceTree = "<group>"; };
|
||||||
EC3565512BEFC65F00A4E0BF /* NSManagedObjectContext++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext++.swift"; sourceTree = "<group>"; };
|
|
||||||
EC3565532BEFC6AD00A4E0BF /* View++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "View++.swift"; sourceTree = "<group>"; };
|
|
||||||
EC3565552BEFC7B300A4E0BF /* NSManagedObject++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "NSManagedObject++.swift"; sourceTree = "<group>"; };
|
|
||||||
EC3565592BF060D900A4E0BF /* Quad.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Quad.metal; sourceTree = "<group>"; };
|
EC3565592BF060D900A4E0BF /* Quad.metal */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.metal; path = Quad.metal; sourceTree = "<group>"; };
|
||||||
EC35655B2BF0712A00A4E0BF /* Float++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Float++.swift"; sourceTree = "<group>"; };
|
|
||||||
EC37FB112C1B2DD90008D976 /* ToolSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSelection.swift; sourceTree = "<group>"; };
|
EC37FB112C1B2DD90008D976 /* ToolSelection.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ToolSelection.swift; sourceTree = "<group>"; };
|
||||||
EC42F7842C25267000E86E96 /* ElementGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementGroup.swift; sourceTree = "<group>"; };
|
EC42F7842C25267000E86E96 /* ElementGroup.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementGroup.swift; sourceTree = "<group>"; };
|
||||||
EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = "<group>"; };
|
EC4538882BEBCAE000A86FEC /* Quad.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Quad.swift; sourceTree = "<group>"; };
|
||||||
@@ -160,10 +181,16 @@
|
|||||||
EC50500E2BF670EA00B4D86E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
EC50500E2BF670EA00B4D86E /* Info.plist */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.plist.xml; path = Info.plist; sourceTree = "<group>"; };
|
||||||
EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoBackgroundRenderPass.swift; sourceTree = "<group>"; };
|
EC5D40802C21CE270067F090 /* PhotoBackgroundRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoBackgroundRenderPass.swift; sourceTree = "<group>"; };
|
||||||
EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovingAverage.swift; sourceTree = "<group>"; };
|
EC5E838F2BFDB69C00261D9C /* MovingAverage.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MovingAverage.swift; sourceTree = "<group>"; };
|
||||||
|
EC6E3BD62C43C6A400DD20F3 /* Application.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = Application.swift; sourceTree = "<group>"; };
|
||||||
|
EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = OnDismissSearchViewModifier.swift; sourceTree = "<group>"; };
|
||||||
|
EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NavigationSplitViewVisibility++.swift"; sourceTree = "<group>"; };
|
||||||
|
EC6E3BDC2C43D5A500DD20F3 /* SidebarVisibility.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = SidebarVisibility.swift; sourceTree = "<group>"; };
|
||||||
EC7F6BE82BE5E6E300A34A7B /* Memola.app */ = {isa = PBXFileReference; explicitFileType = wrapper.application; includeInIndex = 0; path = Memola.app; sourceTree = BUILT_PRODUCTS_DIR; };
|
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 = "<group>"; };
|
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemolaApp.swift; sourceTree = "<group>"; };
|
||||||
EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
EC7F6BEF2BE5E6E400A34A7B /* Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = Assets.xcassets; sourceTree = "<group>"; };
|
||||||
EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
EC7F6BF22BE5E6E400A34A7B /* Preview Assets.xcassets */ = {isa = PBXFileReference; lastKnownFileType = folder.assetcatalog; path = "Preview Assets.xcassets"; sourceTree = "<group>"; };
|
||||||
|
EC86C5812C4010CC00C07D21 /* PhotoDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoDock.swift; sourceTree = "<group>"; };
|
||||||
|
EC8C9DCD2C39882500A8F3C4 /* NSSyncScrollView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSSyncScrollView.swift; sourceTree = "<group>"; };
|
||||||
EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridMode.swift; sourceTree = "<group>"; };
|
EC8F54AB2C2ACDA8001C7C74 /* GridMode.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridMode.swift; sourceTree = "<group>"; };
|
||||||
EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridVertex.swift; sourceTree = "<group>"; };
|
EC8F54AD2C2AF5A4001C7C74 /* LineGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridVertex.swift; sourceTree = "<group>"; };
|
||||||
EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridContext.swift; sourceTree = "<group>"; };
|
EC8F54AF2C2AF5E9001C7C74 /* LineGridContext.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = LineGridContext.swift; sourceTree = "<group>"; };
|
||||||
@@ -183,7 +210,6 @@
|
|||||||
ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointGridVertex.swift; sourceTree = "<group>"; };
|
ECA7389B2BE601AF00A4542E /* PointGridVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PointGridVertex.swift; sourceTree = "<group>"; };
|
||||||
ECA7389D2BE601CB00A4542E /* QuadVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadVertex.swift; sourceTree = "<group>"; };
|
ECA7389D2BE601CB00A4542E /* QuadVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadVertex.swift; sourceTree = "<group>"; };
|
||||||
ECA7389F2BE601E400A4542E /* ViewPortVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPortVertex.swift; sourceTree = "<group>"; };
|
ECA7389F2BE601E400A4542E /* ViewPortVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ViewPortVertex.swift; sourceTree = "<group>"; };
|
||||||
ECA738A22BE6020A00A4542E /* CGFloat++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGFloat++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738A52BE6023F00A4542E /* GridUniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridUniforms.swift; sourceTree = "<group>"; };
|
ECA738A52BE6023F00A4542E /* GridUniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GridUniforms.swift; sourceTree = "<group>"; };
|
||||||
ECA738A72BE6025900A4542E /* GraphicUniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicUniforms.swift; sourceTree = "<group>"; };
|
ECA738A72BE6025900A4542E /* GraphicUniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicUniforms.swift; sourceTree = "<group>"; };
|
||||||
ECA738A92BE6026D00A4542E /* Uniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uniforms.swift; sourceTree = "<group>"; };
|
ECA738A92BE6026D00A4542E /* Uniforms.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Uniforms.swift; sourceTree = "<group>"; };
|
||||||
@@ -210,22 +236,11 @@
|
|||||||
ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserRenderPass.swift; sourceTree = "<group>"; };
|
ECA738DF2BE610B900A4542E /* EraserRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserRenderPass.swift; sourceTree = "<group>"; };
|
||||||
ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicRenderPass.swift; sourceTree = "<group>"; };
|
ECA738E12BE610D000A4542E /* GraphicRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicRenderPass.swift; sourceTree = "<group>"; };
|
||||||
ECA738E32BE6110800A4542E /* Drawable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Drawable.swift; sourceTree = "<group>"; };
|
ECA738E32BE6110800A4542E /* Drawable.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Drawable.swift; sourceTree = "<group>"; };
|
||||||
ECA738E52BE611FD00A4542E /* CGRect++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGRect++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738E72BE6120F00A4542E /* Color++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Color++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738E92BE6122E00A4542E /* CGPoint++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGPoint++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738EB2BE6124E00A4542E /* CGAffineTransform++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGAffineTransform++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738ED2BE6125D00A4542E /* simd_float4x4++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "simd_float4x4++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738EF2BE6127700A4542E /* CGSize++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "CGSize++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738F12BE6128F00A4542E /* Collection++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Collection++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738F32BE612A000A4542E /* Array++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Array++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = "<group>"; };
|
||||||
ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = "<group>"; };
|
ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = "<group>"; };
|
||||||
ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = "<group>"; };
|
ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = "<group>"; };
|
||||||
ECBE529A2C1D94A4006BDB3D /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
|
ECBE529A2C1D94A4006BDB3D /* CameraView.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = CameraView.swift; sourceTree = "<group>"; };
|
||||||
ECBE529D2C1DAB21006BDB3D /* UIImage++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "UIImage++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECC995A22C1E8F2800B2699A /* PhotoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoItem.swift; sourceTree = "<group>"; };
|
ECC995A22C1E8F2800B2699A /* PhotoItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoItem.swift; sourceTree = "<group>"; };
|
||||||
ECC995A42C1EB4CC00B2699A /* Data++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "Data++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = "<group>"; };
|
ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = "<group>"; };
|
||||||
ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
|
ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = "<group>"; };
|
||||||
ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoObject.swift; sourceTree = "<group>"; };
|
ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoObject.swift; sourceTree = "<group>"; };
|
||||||
@@ -235,11 +250,31 @@
|
|||||||
ECD12A942C1B1FA200B96E12 /* PhotoVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoVertex.swift; sourceTree = "<group>"; };
|
ECD12A942C1B1FA200B96E12 /* PhotoVertex.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoVertex.swift; sourceTree = "<group>"; };
|
||||||
ECDAC07A2C318DBC0000ED77 /* ElementToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementToolbar.swift; sourceTree = "<group>"; };
|
ECDAC07A2C318DBC0000ED77 /* ElementToolbar.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementToolbar.swift; sourceTree = "<group>"; };
|
||||||
ECDDD40C2C366B3B00DF9D5E /* PreviewRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewRenderPass.swift; sourceTree = "<group>"; };
|
ECDDD40C2C366B3B00DF9D5E /* PreviewRenderPass.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PreviewRenderPass.swift; sourceTree = "<group>"; };
|
||||||
ECDDD40E2C368B2700DF9D5E /* MTLTexture++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLTexture++.swift"; sourceTree = "<group>"; };
|
|
||||||
ECE883BC2C00AA170045C53D /* EraserStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserStroke.swift; sourceTree = "<group>"; };
|
ECE883BC2C00AA170045C53D /* EraserStroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EraserStroke.swift; sourceTree = "<group>"; };
|
||||||
ECE883BE2C00AB440045C53D /* Stroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stroke.swift; sourceTree = "<group>"; };
|
ECE883BE2C00AB440045C53D /* Stroke.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Stroke.swift; sourceTree = "<group>"; };
|
||||||
ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeStyle.swift; sourceTree = "<group>"; };
|
ECE883C02C00C9CB0045C53D /* StrokeStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StrokeStyle.swift; sourceTree = "<group>"; };
|
||||||
ECEC01A72BEE11BA006DA24C /* QuadShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadShape.swift; sourceTree = "<group>"; };
|
ECEC01A72BEE11BA006DA24C /* QuadShape.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = QuadShape.swift; sourceTree = "<group>"; };
|
||||||
|
ECF7B2BD2C39169C004D2C57 /* Array++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Array++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2BE2C39169C004D2C57 /* CGAffineTransform++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGAffineTransform++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2BF2C39169C004D2C57 /* CGFloat++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGFloat++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C02C39169C004D2C57 /* CGPoint++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGPoint++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C12C39169C004D2C57 /* CGRect++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGRect++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C22C39169C004D2C57 /* CGSize++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "CGSize++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C32C39169C004D2C57 /* Collection++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Collection++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C42C39169C004D2C57 /* Color++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Color++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C52C39169C004D2C57 /* Data++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Data++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C62C39169C004D2C57 /* Date++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Date++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C72C39169C004D2C57 /* Float++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Float++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C82C39169C004D2C57 /* MTLDevice++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2C92C39169C004D2C57 /* MTLTexture++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "MTLTexture++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2CA2C39169C004D2C57 /* NSManagedObject++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObject++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2CB2C39169C004D2C57 /* NSManagedObjectContext++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "NSManagedObjectContext++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2CC2C39169C004D2C57 /* simd_float4x4++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "simd_float4x4++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2CD2C39169C004D2C57 /* Image++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "Image++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2CE2C39169C004D2C57 /* View++.swift */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = sourcecode.swift; path = "View++.swift"; sourceTree = "<group>"; };
|
||||||
|
ECF7B2E32C39174D004D2C57 /* Platform.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Platform.swift; sourceTree = "<group>"; };
|
||||||
|
ECF7B2E62C39544E004D2C57 /* NSCenterClipView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = NSCenterClipView.swift; sourceTree = "<group>"; };
|
||||||
|
ECF7B2E82C395A8E004D2C57 /* Memola.entitlements */ = {isa = PBXFileReference; lastKnownFileType = text.plist.entitlements; path = Memola.entitlements; sourceTree = "<group>"; };
|
||||||
ECFA151F2BEF21EF00455818 /* MemoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoObject.swift; sourceTree = "<group>"; };
|
ECFA151F2BEF21EF00455818 /* MemoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = MemoObject.swift; sourceTree = "<group>"; };
|
||||||
ECFA15212BEF21F500455818 /* CanvasObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanvasObject.swift; sourceTree = "<group>"; };
|
ECFA15212BEF21F500455818 /* CanvasObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CanvasObject.swift; sourceTree = "<group>"; };
|
||||||
ECFA15232BEF223300455818 /* GraphicContextObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicContextObject.swift; sourceTree = "<group>"; };
|
ECFA15232BEF223300455818 /* GraphicContextObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = GraphicContextObject.swift; sourceTree = "<group>"; };
|
||||||
@@ -317,6 +352,7 @@
|
|||||||
EC1437B42BE748E60022C903 /* Views */ = {
|
EC1437B42BE748E60022C903 /* Views */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
EC8C9DCC2C3987FD00A8F3C4 /* AppKit */,
|
||||||
ECA738AC2BE60CC600A4542E /* DrawingView.swift */,
|
ECA738AC2BE60CC600A4542E /* DrawingView.swift */,
|
||||||
);
|
);
|
||||||
path = Views;
|
path = Views;
|
||||||
@@ -356,6 +392,53 @@
|
|||||||
path = Toolbar;
|
path = Toolbar;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
EC2002D32C416002002EBD5F /* Commands */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC2002DC2C4163E8002EBD5F /* AppCommands.swift */,
|
||||||
|
EC2002D62C4160EF002EBD5F /* EditCommands.swift */,
|
||||||
|
EC2002D42C416033002EBD5F /* FileCommands.swift */,
|
||||||
|
EC2002D82C4161ED002EBD5F /* ViewCommands.swift */,
|
||||||
|
);
|
||||||
|
path = Commands;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC2002DE2C41645A002EBD5F /* Shortcut */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC2002D32C416002002EBD5F /* Commands */,
|
||||||
|
EC2002DF2C416466002EBD5F /* Core */,
|
||||||
|
EC2002E72C4167B1002EBD5F /* EnvironmentValues */,
|
||||||
|
);
|
||||||
|
path = Shortcut;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC2002DF2C416466002EBD5F /* Core */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC2002E02C416470002EBD5F /* Shortcut.swift */,
|
||||||
|
EC2002E42C416551002EBD5F /* Shortcuts.swift */,
|
||||||
|
);
|
||||||
|
path = Core;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC2002E72C4167B1002EBD5F /* EnvironmentValues */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC2002E82C4167C5002EBD5F /* ShortcutKey.swift */,
|
||||||
|
);
|
||||||
|
path = EnvironmentValues;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC2002EE2C417BBF002EBD5F /* AppScene */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC2002EC2C417B68002EBD5F /* AppScene.swift */,
|
||||||
|
EC2002EF2C417BF1002EBD5F /* ActiveSceneKey.swift */,
|
||||||
|
);
|
||||||
|
path = AppScene;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
EC2BEBF22C0F5FE1005DB0AF /* RTree */ = {
|
EC2BEBF22C0F5FE1005DB0AF /* RTree */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -412,6 +495,7 @@
|
|||||||
EC50500B2BF6673300B4D86E /* ViewModifiers */ = {
|
EC50500B2BF6673300B4D86E /* ViewModifiers */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
EC6E3BD82C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift */,
|
||||||
EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */,
|
EC50500C2BF6674400B4D86E /* OnDragViewModifier.swift */,
|
||||||
EC0D14272BF7BF20009BFE5F /* ContextMenuViewModifier.swift */,
|
EC0D14272BF7BF20009BFE5F /* ContextMenuViewModifier.swift */,
|
||||||
);
|
);
|
||||||
@@ -434,6 +518,14 @@
|
|||||||
path = Algorithms;
|
path = Algorithms;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
EC6E3BDD2C43D5A500DD20F3 /* SidebarVisibility */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC6E3BDC2C43D5A500DD20F3 /* SidebarVisibility.swift */,
|
||||||
|
);
|
||||||
|
path = SidebarVisibility;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
EC7F6BDF2BE5E6E300A34A7B = {
|
EC7F6BDF2BE5E6E300A34A7B = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -454,14 +546,16 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
ECA738762BE5EE4E00A4542E /* App */,
|
ECA738762BE5EE4E00A4542E /* App */,
|
||||||
|
EC2002DE2C41645A002EBD5F /* Shortcut */,
|
||||||
ECA7387E2BE5FE4200A4542E /* Canvas */,
|
ECA7387E2BE5FE4200A4542E /* Canvas */,
|
||||||
EC5050102BF670EE00B4D86E /* Config */,
|
|
||||||
EC50500A2BF6672000B4D86E /* Components */,
|
EC50500A2BF6672000B4D86E /* Components */,
|
||||||
ECA738A12BE601F700A4542E /* Extensions */,
|
EC5050102BF670EE00B4D86E /* Config */,
|
||||||
ECA738772BE5EEE800A4542E /* Features */,
|
ECA738772BE5EEE800A4542E /* Features */,
|
||||||
|
ECF7B2E82C395A8E004D2C57 /* Memola.entitlements */,
|
||||||
ECA738FA2BE61B1700A4542E /* Persistence */,
|
ECA738FA2BE61B1700A4542E /* Persistence */,
|
||||||
EC7F6BF12BE5E6E400A34A7B /* Preview Content */,
|
EC7F6BF12BE5E6E400A34A7B /* Preview Content */,
|
||||||
ECA738802BE5FE6000A4542E /* Resources */,
|
ECA738802BE5FE6000A4542E /* Resources */,
|
||||||
|
ECF7B2E52C391DFA004D2C57 /* Utilies */,
|
||||||
);
|
);
|
||||||
path = Memola;
|
path = Memola;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -474,6 +568,23 @@
|
|||||||
path = "Preview Content";
|
path = "Preview Content";
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
EC86C5802C4010BE00C07D21 /* PhotoDock */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC86C5812C4010CC00C07D21 /* PhotoDock.swift */,
|
||||||
|
);
|
||||||
|
path = PhotoDock;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
EC8C9DCC2C3987FD00A8F3C4 /* AppKit */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
ECF7B2E62C39544E004D2C57 /* NSCenterClipView.swift */,
|
||||||
|
EC8C9DCD2C39882500A8F3C4 /* NSSyncScrollView.swift */,
|
||||||
|
);
|
||||||
|
path = AppKit;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
EC8F54AA2C2ACD9D001C7C74 /* Grid */ = {
|
EC8F54AA2C2ACD9D001C7C74 /* Grid */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -486,6 +597,7 @@
|
|||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */,
|
EC7F6BEB2BE5E6E300A34A7B /* MemolaApp.swift */,
|
||||||
|
EC6E3BD62C43C6A400DD20F3 /* Application.swift */,
|
||||||
);
|
);
|
||||||
path = App;
|
path = App;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
@@ -512,6 +624,7 @@
|
|||||||
ECA7387B2BE5EF3500A4542E /* Memo */ = {
|
ECA7387B2BE5EF3500A4542E /* Memo */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
EC86C5802C4010BE00C07D21 /* PhotoDock */,
|
||||||
ECDAC0792C318DAF0000ED77 /* ElementToolbar */,
|
ECDAC0792C318DAF0000ED77 /* ElementToolbar */,
|
||||||
ECBE52942C1D58F5006BDB3D /* PhotoPreview */,
|
ECBE52942C1D58F5006BDB3D /* PhotoPreview */,
|
||||||
EC1B783B2BFA0AAC005A34E2 /* Toolbar */,
|
EC1B783B2BFA0AAC005A34E2 /* Toolbar */,
|
||||||
@@ -613,31 +726,6 @@
|
|||||||
path = Vertices;
|
path = Vertices;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
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 = "<group>";
|
|
||||||
};
|
|
||||||
ECA738A42BE6022F00A4542E /* Uniforms */ = {
|
ECA738A42BE6022F00A4542E /* Uniforms */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -871,6 +959,51 @@
|
|||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
ECF7B2CF2C39169C004D2C57 /* Extensions */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC6E3BDA2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift */,
|
||||||
|
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 = "<group>";
|
||||||
|
};
|
||||||
|
ECF7B2E22C39172D004D2C57 /* Platform */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
ECF7B2E32C39174D004D2C57 /* Platform.swift */,
|
||||||
|
);
|
||||||
|
path = Platform;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
|
ECF7B2E52C391DFA004D2C57 /* Utilies */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
EC6E3BDD2C43D5A500DD20F3 /* SidebarVisibility */,
|
||||||
|
EC2002EE2C417BBF002EBD5F /* AppScene */,
|
||||||
|
ECF7B2CF2C39169C004D2C57 /* Extensions */,
|
||||||
|
ECF7B2E22C39172D004D2C57 /* Platform */,
|
||||||
|
);
|
||||||
|
path = Utilies;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
ECFA151E2BEF21BE00455818 /* Objects */ = {
|
ECFA151E2BEF21BE00455818 /* Objects */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -981,73 +1114,84 @@
|
|||||||
EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */,
|
EC8F54B02C2AF5E9001C7C74 /* LineGridContext.swift in Sources */,
|
||||||
EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */,
|
EC35655A2BF060D900A4E0BF /* Quad.metal in Sources */,
|
||||||
ECA738912BE600F500A4542E /* Cache.metal in Sources */,
|
ECA738912BE600F500A4542E /* Cache.metal in Sources */,
|
||||||
|
ECF7B2D82C39169C004D2C57 /* Data++.swift in Sources */,
|
||||||
ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */,
|
ECA7389C2BE601AF00A4542E /* PointGridVertex.swift in Sources */,
|
||||||
ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */,
|
ECA738A82BE6025900A4542E /* GraphicUniforms.swift in Sources */,
|
||||||
EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */,
|
EC01511E2C305CA9008A115E /* DashboardView.swift in Sources */,
|
||||||
EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */,
|
EC8F54AE2C2AF5A4001C7C74 /* LineGridVertex.swift in Sources */,
|
||||||
ECA738E62BE611FD00A4542E /* CGRect++.swift in Sources */,
|
EC2002F02C417BF1002EBD5F /* ActiveSceneKey.swift in Sources */,
|
||||||
|
EC2002E12C416470002EBD5F /* Shortcut.swift in Sources */,
|
||||||
EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */,
|
EC5E83902BFDB69C00261D9C /* MovingAverage.swift in Sources */,
|
||||||
ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */,
|
ECFA15262BEF224900455818 /* StrokeObject.swift in Sources */,
|
||||||
ECA738E82BE6120F00A4542E /* Color++.swift in Sources */,
|
|
||||||
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */,
|
ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */,
|
||||||
ECA7387A2BE5EF0400A4542E /* MemosView.swift in Sources */,
|
ECA7387A2BE5EF0400A4542E /* MemosView.swift in Sources */,
|
||||||
|
EC86C5822C4010CC00C07D21 /* PhotoDock.swift in Sources */,
|
||||||
ECA738BA2BE60DEF00A4542E /* HistoryAction.swift in Sources */,
|
ECA738BA2BE60DEF00A4542E /* HistoryAction.swift in Sources */,
|
||||||
EC3565522BEFC65F00A4E0BF /* NSManagedObjectContext++.swift in Sources */,
|
|
||||||
ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */,
|
ECFA15222BEF21F500455818 /* CanvasObject.swift in Sources */,
|
||||||
ECA738AA2BE6026D00A4542E /* Uniforms.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 */,
|
EC1815082C2D980B00541369 /* Sort.swift in Sources */,
|
||||||
|
EC6E3BD92C43C6C000DD20F3 /* OnDismissSearchViewModifier.swift in Sources */,
|
||||||
ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */,
|
ECA7387D2BE5EF4B00A4542E /* MemoView.swift in Sources */,
|
||||||
ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */,
|
ECDDD40D2C366B3B00DF9D5E /* PreviewRenderPass.swift in Sources */,
|
||||||
|
ECF7B2E12C39169C004D2C57 /* View++.swift in Sources */,
|
||||||
ECA738DA2BE60FF100A4542E /* CacheRenderPass.swift in Sources */,
|
ECA738DA2BE60FF100A4542E /* CacheRenderPass.swift in Sources */,
|
||||||
|
ECF7B2DA2C39169C004D2C57 /* Float++.swift in Sources */,
|
||||||
ECA738CD2BE60F2F00A4542E /* PointGridContext.swift in Sources */,
|
ECA738CD2BE60F2F00A4542E /* PointGridContext.swift in Sources */,
|
||||||
|
EC6E3BDE2C43D5A500DD20F3 /* SidebarVisibility.swift in Sources */,
|
||||||
ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */,
|
ECFA15202BEF21EF00455818 /* MemoObject.swift in Sources */,
|
||||||
ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */,
|
ECE883C12C00C9CB0045C53D /* StrokeStyle.swift in Sources */,
|
||||||
EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */,
|
EC37FB122C1B2DD90008D976 /* ToolSelection.swift in Sources */,
|
||||||
|
EC6E3BDB2C43C78700DD20F3 /* NavigationSplitViewVisibility++.swift in Sources */,
|
||||||
ECA738C62BE60E9D00A4542E /* EraserPenStyle.swift in Sources */,
|
ECA738C62BE60E9D00A4542E /* EraserPenStyle.swift in Sources */,
|
||||||
ECA738EA2BE6122E00A4542E /* CGPoint++.swift in Sources */,
|
|
||||||
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
ECA738A62BE6023F00A4542E /* GridUniforms.swift in Sources */,
|
||||||
ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */,
|
ECA738D72BE60FC100A4542E /* SolidPointStrokeGenerator.swift in Sources */,
|
||||||
EC3565542BEFC6AD00A4E0BF /* View++.swift in Sources */,
|
|
||||||
ECBE529E2C1DAB21006BDB3D /* UIImage++.swift in Sources */,
|
|
||||||
EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */,
|
EC2BEBF62C0F600D005DB0AF /* Box.swift in Sources */,
|
||||||
ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */,
|
ECA738832BE5FEFE00A4542E /* RenderPass.swift in Sources */,
|
||||||
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
ECEC01A82BEE11BA006DA24C /* QuadShape.swift in Sources */,
|
||||||
|
ECF7B2DC2C39169C004D2C57 /* MTLTexture++.swift in Sources */,
|
||||||
ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */,
|
ECA738862BE5FF2500A4542E /* Canvas.swift in Sources */,
|
||||||
|
ECF7B2DE2C39169C004D2C57 /* NSManagedObjectContext++.swift in Sources */,
|
||||||
ECA738882BE5FF4400A4542E /* Renderer.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 */,
|
EC01512C2C306BEF008A115E /* MemoCard.swift in Sources */,
|
||||||
ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */,
|
ECA738D42BE60F9100A4542E /* StrokeGenerator.swift in Sources */,
|
||||||
|
ECF7B2D52C39169C004D2C57 /* CGSize++.swift in Sources */,
|
||||||
ECA739082BE623F300A4542E /* PenDock.swift in Sources */,
|
ECA739082BE623F300A4542E /* PenDock.swift in Sources */,
|
||||||
ECA738CB2BE60F1900A4542E /* ViewPortContext.swift in Sources */,
|
ECA738CB2BE60F1900A4542E /* ViewPortContext.swift in Sources */,
|
||||||
ECA738EE2BE6125D00A4542E /* simd_float4x4++.swift in Sources */,
|
|
||||||
ECA7388C2BE6009600A4542E /* Textures.swift in Sources */,
|
ECA7388C2BE6009600A4542E /* Textures.swift in Sources */,
|
||||||
ECFC51272BF8885700D0D051 /* ColorPicker.swift in Sources */,
|
ECFC51272BF8885700D0D051 /* ColorPicker.swift in Sources */,
|
||||||
ECA738B82BE60DDC00A4542E /* HistoryEvent.swift in Sources */,
|
ECA738B82BE60DDC00A4542E /* HistoryEvent.swift in Sources */,
|
||||||
EC0D14262BF7A8C9009BFE5F /* PenObject.swift in Sources */,
|
EC0D14262BF7A8C9009BFE5F /* PenObject.swift in Sources */,
|
||||||
ECA738952BE6012D00A4542E /* ViewPort.metal in Sources */,
|
ECA738952BE6012D00A4542E /* ViewPort.metal in Sources */,
|
||||||
ECD12A952C1B1FA200B96E12 /* PhotoVertex.swift in Sources */,
|
ECD12A952C1B1FA200B96E12 /* PhotoVertex.swift in Sources */,
|
||||||
|
ECF7B2DD2C39169C004D2C57 /* NSManagedObject++.swift in Sources */,
|
||||||
ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */,
|
ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */,
|
||||||
ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */,
|
ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */,
|
||||||
EC0D14242BF79C98009BFE5F /* MemolaModel.xcdatamodeld in Sources */,
|
EC0D14242BF79C98009BFE5F /* MemolaModel.xcdatamodeld in Sources */,
|
||||||
ECA738F02BE6127700A4542E /* CGSize++.swift in Sources */,
|
EC2002ED2C417B68002EBD5F /* AppScene.swift in Sources */,
|
||||||
ECFA15242BEF223300455818 /* GraphicContextObject.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 */,
|
ECA738E22BE610D000A4542E /* GraphicRenderPass.swift in Sources */,
|
||||||
ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */,
|
ECE883BF2C00AB440045C53D /* Stroke.swift in Sources */,
|
||||||
ECA738DC2BE6108D00A4542E /* StrokeRenderPass.swift in Sources */,
|
ECA738DC2BE6108D00A4542E /* StrokeRenderPass.swift in Sources */,
|
||||||
ECA738F42BE612A000A4542E /* Array++.swift in Sources */,
|
ECF7B2E72C39544E004D2C57 /* NSCenterClipView.swift in Sources */,
|
||||||
|
ECF7B2D22C39169C004D2C57 /* CGFloat++.swift in Sources */,
|
||||||
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */,
|
EC4538892BEBCAE000A86FEC /* Quad.swift in Sources */,
|
||||||
ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */,
|
ECE883BD2C00AA170045C53D /* EraserStroke.swift in Sources */,
|
||||||
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */,
|
EC42F7852C25267000E86E96 /* ElementGroup.swift in Sources */,
|
||||||
EC18150F2C2DB13200541369 /* Date++.swift in Sources */,
|
|
||||||
ECD12A8F2C1AEBA400B96E12 /* Photo.swift in Sources */,
|
ECD12A8F2C1AEBA400B96E12 /* Photo.swift in Sources */,
|
||||||
|
ECF7B2D42C39169C004D2C57 /* CGRect++.swift in Sources */,
|
||||||
|
ECF7B2D62C39169C004D2C57 /* Collection++.swift in Sources */,
|
||||||
EC0151232C306089008A115E /* Sidebar.swift in Sources */,
|
EC0151232C306089008A115E /* Sidebar.swift in Sources */,
|
||||||
ECD12A932C1B062000B96E12 /* Photo.metal in Sources */,
|
ECD12A932C1B062000B96E12 /* Photo.metal in Sources */,
|
||||||
ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */,
|
ECA7388F2BE600DA00A4542E /* Grid.metal in Sources */,
|
||||||
EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */,
|
EC2BEBF42C0F5FF7005DB0AF /* RTree.swift in Sources */,
|
||||||
ECA738C92BE60EF700A4542E /* GraphicContext.swift in Sources */,
|
ECA738C92BE60EF700A4542E /* GraphicContext.swift in Sources */,
|
||||||
ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */,
|
EC2002E92C4167C5002EBD5F /* ShortcutKey.swift in Sources */,
|
||||||
EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */,
|
EC0D14212BF79C73009BFE5F /* ToolObject.swift in Sources */,
|
||||||
ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */,
|
ECDAC07B2C318DBC0000ED77 /* ElementToolbar.swift in Sources */,
|
||||||
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */,
|
EC50500D2BF6674400B4D86E /* OnDragViewModifier.swift in Sources */,
|
||||||
@@ -1059,30 +1203,36 @@
|
|||||||
EC01512A2C306935008A115E /* MemoGrid.swift in Sources */,
|
EC01512A2C306935008A115E /* MemoGrid.swift in Sources */,
|
||||||
ECA738C42BE60E8800A4542E /* MarkerPenStyle.swift in Sources */,
|
ECA738C42BE60E8800A4542E /* MarkerPenStyle.swift in Sources */,
|
||||||
EC0151262C3067B9008A115E /* TrashView.swift in Sources */,
|
EC0151262C3067B9008A115E /* TrashView.swift in Sources */,
|
||||||
|
ECF7B2D92C39169C004D2C57 /* Date++.swift in Sources */,
|
||||||
ECA738BF2BE60E3400A4542E /* Pen.swift in Sources */,
|
ECA738BF2BE60E3400A4542E /* Pen.swift in Sources */,
|
||||||
ECFA15282BEF225000455818 /* QuadObject.swift in Sources */,
|
ECFA15282BEF225000455818 /* QuadObject.swift in Sources */,
|
||||||
ECA738932BE6011100A4542E /* Stroke.metal in Sources */,
|
ECA738932BE6011100A4542E /* Stroke.metal in Sources */,
|
||||||
ECA738B62BE60DCD00A4542E /* History.swift in Sources */,
|
ECA738B62BE60DCD00A4542E /* History.swift in Sources */,
|
||||||
ECA738D22BE60F7B00A4542E /* PenStroke.swift in Sources */,
|
ECA738D22BE60F7B00A4542E /* PenStroke.swift in Sources */,
|
||||||
EC01512E2C30727F008A115E /* MemoPreview.swift in Sources */,
|
EC01512E2C30727F008A115E /* MemoPreview.swift in Sources */,
|
||||||
ECA738F22BE6128F00A4542E /* Collection++.swift in Sources */,
|
|
||||||
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */,
|
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */,
|
||||||
ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */,
|
ECF7B2E42C39174D004D2C57 /* Platform.swift in Sources */,
|
||||||
ECA738C12BE60E5300A4542E /* PenStyle.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 */,
|
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */,
|
||||||
ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */,
|
ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */,
|
||||||
EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */,
|
EC8F54AC2C2ACDA8001C7C74 /* GridMode.swift in Sources */,
|
||||||
|
EC2002D92C4161ED002EBD5F /* ViewCommands.swift in Sources */,
|
||||||
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */,
|
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */,
|
||||||
|
EC2002D52C416033002EBD5F /* FileCommands.swift in Sources */,
|
||||||
EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */,
|
EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */,
|
||||||
ECDDD40F2C368B2700DF9D5E /* MTLTexture++.swift in Sources */,
|
|
||||||
ECC995A52C1EB4CC00B2699A /* Data++.swift in Sources */,
|
|
||||||
ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */,
|
ECA738A02BE601E400A4542E /* ViewPortVertex.swift in Sources */,
|
||||||
ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */,
|
ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */,
|
||||||
EC0D14282BF7BF20009BFE5F /* ContextMenuViewModifier.swift in Sources */,
|
EC0D14282BF7BF20009BFE5F /* ContextMenuViewModifier.swift in Sources */,
|
||||||
EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */,
|
EC2106AD2C10C2A700FBE27C /* AnyStroke.swift in Sources */,
|
||||||
|
ECF7B2DB2C39169C004D2C57 /* MTLDevice++.swift in Sources */,
|
||||||
ECC995A32C1E8F2800B2699A /* PhotoItem.swift in Sources */,
|
ECC995A32C1E8F2800B2699A /* PhotoItem.swift in Sources */,
|
||||||
ECA738BC2BE60E0300A4542E /* Tool.swift in Sources */,
|
ECA738BC2BE60E0300A4542E /* Tool.swift in Sources */,
|
||||||
|
ECF7B2E02C39169C004D2C57 /* Image++.swift in Sources */,
|
||||||
ECA738972BE6014200A4542E /* Graphic.metal in Sources */,
|
ECA738972BE6014200A4542E /* Graphic.metal in Sources */,
|
||||||
|
ECF7B2D32C39169C004D2C57 /* CGPoint++.swift in Sources */,
|
||||||
ECA7388A2BE6006A00A4542E /* PipelineStates.swift in Sources */,
|
ECA7388A2BE6006A00A4542E /* PipelineStates.swift in Sources */,
|
||||||
EC18150D2C2DAC3700541369 /* Placeholder.swift in Sources */,
|
EC18150D2C2DAC3700541369 /* Placeholder.swift in Sources */,
|
||||||
);
|
);
|
||||||
@@ -1215,6 +1365,8 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Memola/Memola.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\"";
|
||||||
@@ -1231,12 +1383,13 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTLLINKER_FLAGS = "";
|
MTLLINKER_FLAGS = "";
|
||||||
MTL_COMPILER_FLAGS = "";
|
MTL_COMPILER_FLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.Memola;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.Memola;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
@@ -1250,6 +1403,8 @@
|
|||||||
buildSettings = {
|
buildSettings = {
|
||||||
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
ASSETCATALOG_COMPILER_APPICON_NAME = AppIcon;
|
||||||
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
ASSETCATALOG_COMPILER_GLOBAL_ACCENT_COLOR_NAME = AccentColor;
|
||||||
|
CODE_SIGN_ENTITLEMENTS = Memola/Memola.entitlements;
|
||||||
|
"CODE_SIGN_IDENTITY[sdk=macosx*]" = "Apple Development";
|
||||||
CODE_SIGN_STYLE = Automatic;
|
CODE_SIGN_STYLE = Automatic;
|
||||||
CURRENT_PROJECT_VERSION = 1;
|
CURRENT_PROJECT_VERSION = 1;
|
||||||
DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\"";
|
DEVELOPMENT_ASSET_PATHS = "\"Memola/Preview Content\"";
|
||||||
@@ -1266,12 +1421,13 @@
|
|||||||
"$(inherited)",
|
"$(inherited)",
|
||||||
"@executable_path/Frameworks",
|
"@executable_path/Frameworks",
|
||||||
);
|
);
|
||||||
|
MACOSX_DEPLOYMENT_TARGET = 14.0;
|
||||||
MARKETING_VERSION = 1.0;
|
MARKETING_VERSION = 1.0;
|
||||||
MTLLINKER_FLAGS = "";
|
MTLLINKER_FLAGS = "";
|
||||||
MTL_COMPILER_FLAGS = "";
|
MTL_COMPILER_FLAGS = "";
|
||||||
PRODUCT_BUNDLE_IDENTIFIER = com.example.Memola;
|
PRODUCT_BUNDLE_IDENTIFIER = com.example.Memola;
|
||||||
PRODUCT_NAME = "$(TARGET_NAME)";
|
PRODUCT_NAME = "$(TARGET_NAME)";
|
||||||
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator";
|
SUPPORTED_PLATFORMS = "iphoneos iphonesimulator macosx";
|
||||||
SUPPORTS_MACCATALYST = NO;
|
SUPPORTS_MACCATALYST = NO;
|
||||||
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
SUPPORTS_MAC_DESIGNED_FOR_IPHONE_IPAD = NO;
|
||||||
SWIFT_EMIT_LOC_STRINGS = YES;
|
SWIFT_EMIT_LOC_STRINGS = YES;
|
||||||
|
|||||||
@@ -0,0 +1,62 @@
|
|||||||
|
//
|
||||||
|
// Application.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/12/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?) {
|
||||||
|
self.memoObject = memoObject
|
||||||
|
}
|
||||||
|
|
||||||
|
func closeMemo() {
|
||||||
|
self.memoObject = nil
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
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
|
||||||
|
}
|
||||||
|
|
||||||
|
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)
|
||||||
|
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
|
||||||
@@ -9,11 +9,17 @@ import SwiftUI
|
|||||||
|
|
||||||
@main
|
@main
|
||||||
struct MemolaApp: App {
|
struct MemolaApp: App {
|
||||||
|
#if os(macOS)
|
||||||
|
@NSApplicationDelegateAdaptor(Application.self) private var application
|
||||||
|
#else
|
||||||
|
@UIApplicationDelegateAdaptor(Application.self) private var application
|
||||||
|
#endif
|
||||||
|
|
||||||
var body: some Scene {
|
var body: some Scene {
|
||||||
WindowGroup {
|
WindowGroup {
|
||||||
DashboardView()
|
DashboardView()
|
||||||
.persistence(\.viewContext)
|
.persistence(\.viewContext)
|
||||||
.onReceive(NotificationCenter.default.publisher(for: UIApplication.willTerminateNotification)) { _ in
|
.onReceive(NotificationCenter.default.publisher(for: Platform.Application.willTerminateNotification)) { _ in
|
||||||
withPersistenceSync(\.viewContext) { context in
|
withPersistenceSync(\.viewContext) { context in
|
||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
}
|
}
|
||||||
@@ -21,6 +27,22 @@ struct MemolaApp: App {
|
|||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.frame(minWidth: 1000, minHeight: 600)
|
||||||
|
#endif
|
||||||
|
.environmentObject(application)
|
||||||
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
.defaultPosition(.center)
|
||||||
|
.windowResizability(.contentSize)
|
||||||
|
.defaultSize(width: 1200, height: 800)
|
||||||
|
.windowToolbarStyle(.unifiedCompact)
|
||||||
|
#endif
|
||||||
|
.commands {
|
||||||
|
AppCommands()
|
||||||
|
FileCommands()
|
||||||
|
EditCommands()
|
||||||
|
ViewCommands(application: application)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class LineGridContext {
|
final class LineGridContext {
|
||||||
var vertices: [LineGridVertex] = []
|
var vertices: [LineGridVertex] = []
|
||||||
var vertexCount: Int = 0
|
var vertexCount: Int = 0
|
||||||
var vertexBuffer: MTLBuffer?
|
var vertexBuffer: MTLBuffer?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class PointGridContext {
|
final class PointGridContext {
|
||||||
var vertices: [PointGridVertex] = []
|
var vertices: [PointGridVertex] = []
|
||||||
var vertexCount: Int = 0
|
var vertexCount: Int = 0
|
||||||
var vertexBuffer: MTLBuffer?
|
var vertexBuffer: MTLBuffer?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ViewPortContext {
|
final class ViewPortContext {
|
||||||
var vertices: [ViewPortVertex] = []
|
var vertices: [ViewPortVertex] = []
|
||||||
let vertexCount: Int = 4
|
let vertexCount: Int = 4
|
||||||
var vertexBuffer: MTLBuffer?
|
var vertexBuffer: MTLBuffer?
|
||||||
|
|||||||
@@ -32,7 +32,6 @@ final class Canvas: ObservableObject, Identifiable, @unchecked Sendable {
|
|||||||
|
|
||||||
@Published var state: State = .initial
|
@Published var state: State = .initial
|
||||||
@Published var zoomScale: CGFloat = .zero
|
@Published var zoomScale: CGFloat = .zero
|
||||||
@Published var locksCanvas: Bool = false
|
|
||||||
|
|
||||||
@Published var gridMode: GridMode = .point
|
@Published var gridMode: GridMode = .point
|
||||||
|
|
||||||
@@ -85,7 +84,11 @@ extension Canvas {
|
|||||||
func save(for memoObject: MemoObject, completion: @escaping () -> Void) {
|
func save(for memoObject: MemoObject, completion: @escaping () -> Void) {
|
||||||
state = .closing
|
state = .closing
|
||||||
let previewImage = renderer?.drawPreview(on: self)
|
let previewImage = renderer?.drawPreview(on: self)
|
||||||
|
#if os(macOS)
|
||||||
|
memoObject.preview = previewImage?.tiffRepresentation
|
||||||
|
#else
|
||||||
memoObject.preview = previewImage?.jpegData(compressionQuality: 0.8)
|
memoObject.preview = previewImage?.jpegData(compressionQuality: 0.8)
|
||||||
|
#endif
|
||||||
withPersistenceSync(\.viewContext) { context in
|
withPersistenceSync(\.viewContext) { context in
|
||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
}
|
}
|
||||||
@@ -105,7 +108,13 @@ extension Canvas {
|
|||||||
func updateTransform(on drawingView: DrawingView) {
|
func updateTransform(on drawingView: DrawingView) {
|
||||||
let bounds = CGRect(origin: .zero, size: size)
|
let bounds = CGRect(origin: .zero, size: size)
|
||||||
let renderView = drawingView.renderView
|
let renderView = drawingView.renderView
|
||||||
|
#if os(macOS)
|
||||||
|
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)
|
let targetRect = drawingView.convert(drawingView.bounds, to: renderView)
|
||||||
|
#endif
|
||||||
let transform1 = bounds.transform(to: targetRect)
|
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 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)
|
let transform3 = CGAffineTransform.identity.translatedBy(x: 0, y: 1).scaledBy(x: 1, y: -1).translatedBy(x: 0, y: 1)
|
||||||
@@ -135,20 +144,30 @@ extension Canvas {
|
|||||||
self.previewTransform = simd_float4x4(transform)
|
self.previewTransform = simd_float4x4(transform)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateClipBounds(_ scrollView: UIScrollView, on drawingView: DrawingView) {
|
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 ratio = drawingView.ratio
|
||||||
let bounds = scrollView.convert(scrollView.bounds, to: drawingView)
|
let bounds = scrollView.convert(scrollView.bounds, to: drawingView)
|
||||||
clipBounds = CGRect(origin: bounds.origin.muliply(by: ratio), size: bounds.size.multiply(by: ratio))
|
clipBounds = CGRect(origin: bounds.origin.muliply(by: ratio), size: bounds.size.multiply(by: ratio))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Zoom Scale
|
// MARK: - Zoom Scale
|
||||||
extension Canvas {
|
extension Canvas {
|
||||||
func setZoomScale(_ zoomScale: CGFloat) {
|
func setZoomScale(_ zoomScale: CGFloat) {
|
||||||
DispatchQueue.main.async { [weak self] in
|
#if os(macOS)
|
||||||
guard let self else { return }
|
self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale)
|
||||||
|
#else
|
||||||
|
DispatchQueue.main.async { [unowned self] in
|
||||||
self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale)
|
self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -162,6 +181,19 @@ extension Canvas {
|
|||||||
try context.saveIfNeeded()
|
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
|
// MARK: - Stroke
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct PipelineStates {
|
enum PipelineStates {
|
||||||
static func createPointGridPipelineState(from renderer: Renderer, pixelFormat: MTLPixelFormat? = nil) -> MTLRenderPipelineState? {
|
static func createPointGridPipelineState(from renderer: Renderer, pixelFormat: MTLPixelFormat? = nil) -> MTLRenderPipelineState? {
|
||||||
let device = renderer.device
|
let device = renderer.device
|
||||||
let library = renderer.library
|
let library = renderer.library
|
||||||
|
|||||||
@@ -110,9 +110,9 @@ final class Renderer {
|
|||||||
viewPortRenderPass.draw(into: commandBuffer, on: canvas, with: self)
|
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 {
|
guard let commandBuffer = commandQueue.makeCommandBuffer() else {
|
||||||
NSLog("[Memola] - Unable to create command buffer")
|
NSLog("[Memola] - Unable to create command buffer for preview")
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
strokeRenderPass.eraserRenderPass = eraserRenderPass
|
strokeRenderPass.eraserRenderPass = eraserRenderPass
|
||||||
@@ -124,6 +124,10 @@ final class Renderer {
|
|||||||
guard let cgImage = previewRenderPass.previewTexture?.getImage() else {
|
guard let cgImage = previewRenderPass.previewTexture?.getImage() else {
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
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)
|
return UIImage(cgImage: cgImage, scale: 1.0, orientation: .downMirrored)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ElementGroup {
|
final class ElementGroup {
|
||||||
var elements: [Element] = []
|
var elements: [Element] = []
|
||||||
var type: ElementGroupType
|
var type: ElementGroupType
|
||||||
|
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class MovingAverage {
|
final class MovingAverage {
|
||||||
private var sum: CGPoint
|
private var sum: CGPoint
|
||||||
private var points: [CGPoint]
|
private var points: [CGPoint]
|
||||||
private var windowSize: Int
|
private var windowSize: Int
|
||||||
|
|||||||
@@ -101,7 +101,9 @@ final class PenStroke: Stroke, @unchecked Sendable {
|
|||||||
fetchRequest.predicate = NSPredicate(format: "ANY strokes == %@", stroke)
|
fetchRequest.predicate = NSPredicate(format: "ANY strokes == %@", stroke)
|
||||||
|
|
||||||
do {
|
do {
|
||||||
let erasers = try Persistence.shared.backgroundContext.fetch(fetchRequest)
|
let erasers = try withPersistenceContext(\.backgroundContext) { context in
|
||||||
|
try context.fetch(fetchRequest)
|
||||||
|
}
|
||||||
return erasers
|
return erasers
|
||||||
} catch {
|
} catch {
|
||||||
NSLog("[Memola] - \(error.localizedDescription)")
|
NSLog("[Memola] - \(error.localizedDescription)")
|
||||||
|
|||||||
@@ -12,7 +12,7 @@ final class Photo: @unchecked Sendable, Equatable {
|
|||||||
var id: UUID = UUID()
|
var id: UUID = UUID()
|
||||||
var size: CGSize
|
var size: CGSize
|
||||||
var origin: CGPoint
|
var origin: CGPoint
|
||||||
var image: UIImage?
|
var image: Platform.Image?
|
||||||
var url: URL?
|
var url: URL?
|
||||||
var bounds: [CGFloat]
|
var bounds: [CGFloat]
|
||||||
var createdAt: Date
|
var createdAt: Date
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import Combine
|
import Combine
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class History: ObservableObject {
|
final class History: ObservableObject {
|
||||||
var memo: MemoObject?
|
var memo: MemoObject?
|
||||||
|
|
||||||
init(memo: MemoObject?) {
|
init(memo: MemoObject?) {
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class Node<T> where T: Equatable & Comparable {
|
final class Node<T> where T: Equatable & Comparable {
|
||||||
var box: Box
|
var box: Box
|
||||||
var value: T?
|
var value: T?
|
||||||
var isLeaf: Bool
|
var isLeaf: Bool
|
||||||
|
|||||||
@@ -7,7 +7,7 @@
|
|||||||
|
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class RTree<T> where T: Equatable & Comparable {
|
final class RTree<T> where T: Equatable & Comparable {
|
||||||
private var root: Node<T>
|
private var root: Node<T>
|
||||||
private let maxEntries: Int
|
private let maxEntries: Int
|
||||||
private let minEntries: Int
|
private let minEntries: Int
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class CacheRenderPass: RenderPass {
|
final class CacheRenderPass: RenderPass {
|
||||||
var label: String = "Cache Render Pass"
|
var label: String = "Cache Render Pass"
|
||||||
|
|
||||||
var descriptor: MTLRenderPassDescriptor?
|
var descriptor: MTLRenderPassDescriptor?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class EraserRenderPass: RenderPass {
|
final class EraserRenderPass: RenderPass {
|
||||||
var label: String = "Eraser Render Pass"
|
var label: String = "Eraser Render Pass"
|
||||||
|
|
||||||
var descriptor: MTLRenderPassDescriptor?
|
var descriptor: MTLRenderPassDescriptor?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class GraphicRenderPass: RenderPass {
|
final class GraphicRenderPass: RenderPass {
|
||||||
var label: String { "Graphic Render Pass" }
|
var label: String { "Graphic Render Pass" }
|
||||||
var descriptor: MTLRenderPassDescriptor?
|
var descriptor: MTLRenderPassDescriptor?
|
||||||
var graphicTexture: MTLTexture?
|
var graphicTexture: MTLTexture?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class PhotoBackgroundRenderPass: RenderPass {
|
final class PhotoBackgroundRenderPass: RenderPass {
|
||||||
var label: String = "Photo Background Render Pass"
|
var label: String = "Photo Background Render Pass"
|
||||||
|
|
||||||
var descriptor: MTLRenderPassDescriptor?
|
var descriptor: MTLRenderPassDescriptor?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class PhotoRenderPass: RenderPass {
|
final class PhotoRenderPass: RenderPass {
|
||||||
var label: String = "Photo Render Pass"
|
var label: String = "Photo Render Pass"
|
||||||
|
|
||||||
var descriptor: MTLRenderPassDescriptor?
|
var descriptor: MTLRenderPassDescriptor?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class StrokeRenderPass: RenderPass {
|
final class StrokeRenderPass: RenderPass {
|
||||||
var label: String = "Stroke Render Pass"
|
var label: String = "Stroke Render Pass"
|
||||||
|
|
||||||
var descriptor: MTLRenderPassDescriptor?
|
var descriptor: MTLRenderPassDescriptor?
|
||||||
|
|||||||
@@ -8,7 +8,7 @@
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class ViewPortRenderPass: RenderPass {
|
final class ViewPortRenderPass: RenderPass {
|
||||||
var label: String { "View Port Render Pass"}
|
var label: String { "View Port Render Pass"}
|
||||||
var descriptor: MTLRenderPassDescriptor?
|
var descriptor: MTLRenderPassDescriptor?
|
||||||
|
|
||||||
|
|||||||
@@ -10,7 +10,7 @@ import SwiftUI
|
|||||||
import CoreData
|
import CoreData
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
public class Tool: NSObject, ObservableObject {
|
final class Tool: NSObject, ObservableObject {
|
||||||
let object: ToolObject
|
let object: ToolObject
|
||||||
|
|
||||||
@Published var pens: [Pen] = []
|
@Published var pens: [Pen] = []
|
||||||
@@ -35,6 +35,7 @@ public class Tool: NSObject, ObservableObject {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func selectTool(_ selection: ToolSelection) {
|
func selectTool(_ selection: ToolSelection) {
|
||||||
|
guard self.selection != selection else { return }
|
||||||
self.selection = selection
|
self.selection = selection
|
||||||
withPersistence(\.viewContext) { [weak object] context in
|
withPersistence(\.viewContext) { [weak object] context in
|
||||||
object?.selection = selection.rawValue
|
object?.selection = selection.rawValue
|
||||||
@@ -127,7 +128,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 }
|
guard let (resizedImage, dimension) = resizePhoto(of: image) else { return }
|
||||||
let photoItem = bookmarkPhoto(of: resizedImage, and: image, in: dimension, with: canvasID)
|
let photoItem = bookmarkPhoto(of: resizedImage, and: image, in: dimension, with: canvasID)
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -136,7 +137,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 targetSize = CGSize(width: 512, height: 512)
|
||||||
let size = image.size
|
let size = image.size
|
||||||
let widthRatio = targetSize.width / size.width
|
let widthRatio = targetSize.width / size.width
|
||||||
@@ -147,6 +148,14 @@ public class Tool: NSObject, ObservableObject {
|
|||||||
)
|
)
|
||||||
let rect = CGRect(origin: .zero, size: targetSize)
|
let rect = CGRect(origin: .zero, size: targetSize)
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
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)
|
UIGraphicsBeginImageContextWithOptions(targetSize, true, 1.0)
|
||||||
image.draw(in: rect)
|
image.draw(in: rect)
|
||||||
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
let newImage = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
@@ -155,10 +164,15 @@ public class Tool: NSObject, ObservableObject {
|
|||||||
guard let newImage else { return nil }
|
guard let newImage else { return nil }
|
||||||
|
|
||||||
return (newImage, dimension)
|
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)
|
||||||
|
guard let data = image.tiffRepresentation else { return nil }
|
||||||
|
#else
|
||||||
guard let data = image.jpegData(compressionQuality: 1) else { return nil }
|
guard let data = image.jpegData(compressionQuality: 1) else { return nil }
|
||||||
|
#endif
|
||||||
let fileManager = FileManager.default
|
let fileManager = FileManager.default
|
||||||
guard let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
guard let directory = fileManager.urls(for: .documentDirectory, in: .userDomainMask).first else {
|
||||||
return nil
|
return nil
|
||||||
|
|||||||
@@ -10,21 +10,21 @@ import SwiftUI
|
|||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class CanvasViewController: UIViewController {
|
final class CanvasViewController: Platform.ViewController {
|
||||||
let drawingView: DrawingView
|
private let drawingView: DrawingView
|
||||||
let scrollView: UIScrollView = UIScrollView()
|
private let scrollView: Platform.ScrollView = Platform.ScrollView()
|
||||||
var renderView: MTKView {
|
private var renderView: MTKView {
|
||||||
drawingView.renderView
|
drawingView.renderView
|
||||||
}
|
}
|
||||||
|
|
||||||
var photoInsertGesture: UITapGestureRecognizer?
|
private var photoInsertGesture: Platform.TapGestureRecognizer?
|
||||||
|
|
||||||
let tool: Tool
|
private let tool: Tool
|
||||||
let canvas: Canvas
|
private let canvas: Canvas
|
||||||
let history: History
|
private let history: History
|
||||||
let renderer: Renderer
|
private let renderer: Renderer
|
||||||
|
|
||||||
var cancellables: Set<AnyCancellable> = []
|
private var cancellables: Set<AnyCancellable> = []
|
||||||
|
|
||||||
init(tool: Tool, canvas: Canvas, history: History) {
|
init(tool: Tool, canvas: Canvas, history: History) {
|
||||||
self.tool = tool
|
self.tool = tool
|
||||||
@@ -47,6 +47,29 @@ class CanvasViewController: UIViewController {
|
|||||||
configureListeners()
|
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) {
|
override func viewWillAppear(_ animated: Bool) {
|
||||||
super.viewWillAppear(animated)
|
super.viewWillAppear(animated)
|
||||||
resizeDocumentView()
|
resizeDocumentView()
|
||||||
@@ -67,11 +90,18 @@ class CanvasViewController: UIViewController {
|
|||||||
super.viewDidDisappear(animated)
|
super.viewDidDisappear(animated)
|
||||||
history.resetRedo()
|
history.resetRedo()
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func configureViews() {
|
private func configureViews() {
|
||||||
|
#if os(macOS)
|
||||||
|
view.wantsLayer = true
|
||||||
|
view.layer?.backgroundColor = NSColor.white.cgColor
|
||||||
|
#else
|
||||||
view.backgroundColor = .white
|
view.backgroundColor = .white
|
||||||
|
#endif
|
||||||
|
|
||||||
renderView.autoResizeDrawable = false
|
renderView.autoResizeDrawable = false
|
||||||
renderView.enableSetNeedsDisplay = true
|
renderView.enableSetNeedsDisplay = true
|
||||||
renderView.translatesAutoresizingMaskIntoConstraints = false
|
renderView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
@@ -84,14 +114,24 @@ extension CanvasViewController {
|
|||||||
renderView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
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.maximumZoomScale = canvas.maximumZoomScale
|
||||||
scrollView.minimumZoomScale = canvas.minimumZoomScale
|
scrollView.minimumZoomScale = canvas.minimumZoomScale
|
||||||
scrollView.contentInsetAdjustmentBehavior = .never
|
scrollView.contentInsetAdjustmentBehavior = .never
|
||||||
scrollView.isScrollEnabled = true
|
scrollView.isScrollEnabled = true
|
||||||
scrollView.showsVerticalScrollIndicator = true
|
scrollView.showsVerticalScrollIndicator = true
|
||||||
scrollView.showsHorizontalScrollIndicator = true
|
scrollView.showsHorizontalScrollIndicator = true
|
||||||
scrollView.delegate = self
|
|
||||||
scrollView.backgroundColor = .clear
|
scrollView.backgroundColor = .clear
|
||||||
|
#endif
|
||||||
|
scrollView.delegate = self
|
||||||
|
|
||||||
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||||
view.addSubview(scrollView)
|
view.addSubview(scrollView)
|
||||||
@@ -102,14 +142,23 @@ extension CanvasViewController {
|
|||||||
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
scrollView.trailingAnchor.constraint(equalTo: view.trailingAnchor)
|
||||||
])
|
])
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
scrollView.contentView = NSCenterClipView()
|
||||||
|
scrollView.contentView.drawsBackground = false
|
||||||
|
scrollView.documentView = drawingView
|
||||||
|
#else
|
||||||
scrollView.addSubview(drawingView)
|
scrollView.addSubview(drawingView)
|
||||||
drawingView.backgroundColor = .clear
|
drawingView.backgroundColor = .clear
|
||||||
drawingView.isUserInteractionEnabled = false
|
drawingView.isUserInteractionEnabled = false
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func resizeDocumentView(to newSize: CGSize? = nil) {
|
private func resizeDocumentView(to newSize: CGSize? = nil) {
|
||||||
|
#if os(macOS)
|
||||||
|
scrollView.layoutSubtreeIfNeeded()
|
||||||
|
#else
|
||||||
scrollView.layoutIfNeeded()
|
scrollView.layoutIfNeeded()
|
||||||
|
#endif
|
||||||
let size = canvas.size
|
let size = canvas.size
|
||||||
let widthScale = (newSize?.width ?? view.frame.width) / size.width
|
let widthScale = (newSize?.width ?? view.frame.width) / size.width
|
||||||
let heightScale = (newSize?.height ?? view.frame.height) / size.height
|
let heightScale = (newSize?.height ?? view.frame.height) / size.height
|
||||||
@@ -120,28 +169,48 @@ extension CanvasViewController {
|
|||||||
let newFrame = CGRect(x: 0, y: 0, width: width, height: height)
|
let newFrame = CGRect(x: 0, y: 0, width: width, height: height)
|
||||||
drawingView.frame = newFrame
|
drawingView.frame = newFrame
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
DispatchQueue.main.async { [unowned canvas] in
|
||||||
|
canvas.setZoomScale(canvas.defaultZoomScale)
|
||||||
|
}
|
||||||
|
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)
|
scrollView.setZoomScale(canvas.defaultZoomScale, animated: true)
|
||||||
centerDocumentView(to: newSize)
|
centerDocumentView(to: newSize)
|
||||||
|
#endif
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
let offsetX = (newFrame.width * canvas.defaultZoomScale - view.frame.width) / 2
|
let offsetX = (newFrame.width * canvas.defaultZoomScale - view.frame.width) / 2
|
||||||
let offsetY = (newFrame.height * canvas.defaultZoomScale - view.frame.height) / 2
|
let offsetY = (newFrame.height * canvas.defaultZoomScale - view.frame.height) / 2
|
||||||
|
|
||||||
let point = CGPoint(x: offsetX, y: offsetY)
|
let point = CGPoint(x: offsetX, y: offsetY)
|
||||||
scrollView.setContentOffset(point, animated: true)
|
scrollView.setContentOffset(point, animated: true)
|
||||||
|
#endif
|
||||||
drawingView.updateDrawableSize(with: view.frame.size)
|
drawingView.updateDrawableSize(with: view.frame.size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func centerDocumentView(to newSize: CGSize? = nil) {
|
#if os(iOS)
|
||||||
|
private func centerDocumentView(to newSize: CGSize? = nil) {
|
||||||
let documentViewSize = drawingView.frame.size
|
let documentViewSize = drawingView.frame.size
|
||||||
let scrollViewSize = newSize ?? view.frame.size
|
let scrollViewSize = newSize ?? view.frame.size
|
||||||
let verticalPadding = documentViewSize.height < scrollViewSize.height ? (scrollViewSize.height - documentViewSize.height) / 2 : 0
|
let verticalPadding = documentViewSize.height < scrollViewSize.height ? (scrollViewSize.height - documentViewSize.height) / 2 : 0
|
||||||
let horizontalPadding = documentViewSize.width < scrollViewSize.width ? (scrollViewSize.width - documentViewSize.width) / 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)
|
self.scrollView.contentInset = UIEdgeInsets(top: verticalPadding, left: horizontalPadding, bottom: verticalPadding, right: horizontalPadding)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
func updateDocumentBounds() {
|
private func updateDocumentBounds() {
|
||||||
|
#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)
|
||||||
|
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)
|
var bounds = scrollView.bounds.muliply(by: drawingView.ratio / scrollView.zoomScale)
|
||||||
|
#endif
|
||||||
let xDelta = bounds.minX * 0.0
|
let xDelta = bounds.minX * 0.0
|
||||||
let yDelta = bounds.minY * 0.0
|
let yDelta = bounds.minY * 0.0
|
||||||
bounds.origin.x -= xDelta
|
bounds.origin.x -= xDelta
|
||||||
@@ -156,7 +225,31 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func configureListeners() {
|
private func configureListeners() {
|
||||||
|
#if os(macOS)
|
||||||
|
NotificationCenter.default.publisher(for: NSScrollView.didEndLiveMagnifyNotification, object: scrollView)
|
||||||
|
.sink { [weak self] _ in
|
||||||
|
self?.updateDocumentBounds()
|
||||||
|
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?.updateDocumentBounds()
|
||||||
|
self?.draggingEnded()
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
#endif
|
||||||
canvas.$state
|
canvas.$state
|
||||||
.sink { [weak self] state in
|
.sink { [weak self] state in
|
||||||
self?.canvasStateChanged(state)
|
self?.canvasStateChanged(state)
|
||||||
@@ -167,11 +260,6 @@ extension CanvasViewController {
|
|||||||
self?.zoomChanged(zoomScale)
|
self?.zoomChanged(zoomScale)
|
||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
canvas.$locksCanvas
|
|
||||||
.sink { [weak self] state in
|
|
||||||
self?.lockModeChanged(state)
|
|
||||||
}
|
|
||||||
.store(in: &cancellables)
|
|
||||||
canvas.$gridMode
|
canvas.$gridMode
|
||||||
.delay(for: .milliseconds(100), scheduler: DispatchQueue.main)
|
.delay(for: .milliseconds(100), scheduler: DispatchQueue.main)
|
||||||
.sink { [weak self] mode in
|
.sink { [weak self] mode in
|
||||||
@@ -204,12 +292,12 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func loadMemo() {
|
private func loadMemo() {
|
||||||
tool.load()
|
tool.load()
|
||||||
canvas.load()
|
canvas.load()
|
||||||
}
|
}
|
||||||
|
|
||||||
func canvasStateChanged(_ state: Canvas.State) {
|
private func canvasStateChanged(_ state: Canvas.State) {
|
||||||
guard state == .loaded else { return }
|
guard state == .loaded else { return }
|
||||||
renderView.delegate = self
|
renderView.delegate = self
|
||||||
renderer.resize(on: renderView, to: renderView.drawableSize)
|
renderer.resize(on: renderView, to: renderView.drawableSize)
|
||||||
@@ -230,25 +318,46 @@ extension CanvasViewController: MTKViewDelegate {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func configureGestures() {
|
private 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
|
photoInsertGesture.numberOfTapsRequired = 1
|
||||||
|
#endif
|
||||||
self.photoInsertGesture = photoInsertGesture
|
self.photoInsertGesture = photoInsertGesture
|
||||||
scrollView.addGestureRecognizer(photoInsertGesture)
|
scrollView.addGestureRecognizer(photoInsertGesture)
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc func recognizeTapGesture(_ gesture: UITapGestureRecognizer) {
|
@objc private func recognizeTapGesture(_ gesture: Platform.TapGestureRecognizer) {
|
||||||
guard let photoItem = tool.selectedPhotoItem else { return }
|
guard let photoItem = tool.selectedPhotoItem else { return }
|
||||||
withAnimation {
|
withAnimation {
|
||||||
tool.selectedPhotoItem = nil
|
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)
|
let point = gesture.location(in: drawingView)
|
||||||
|
#endif
|
||||||
let photo = canvas.insertPhoto(at: point.muliply(by: drawingView.ratio), photoItem: photoItem)
|
let photo = canvas.insertPhoto(at: point.muliply(by: drawingView.ratio), photoItem: photoItem)
|
||||||
history.addUndo(.photo(photo))
|
history.addUndo(.photo(photo))
|
||||||
drawingView.draw()
|
drawingView.draw()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#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 {
|
extension CanvasViewController: UIScrollViewDelegate {
|
||||||
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
func viewForZooming(in scrollView: UIScrollView) -> UIView? {
|
||||||
drawingView
|
drawingView
|
||||||
@@ -294,9 +403,10 @@ extension CanvasViewController: UIScrollViewDelegate {
|
|||||||
draggingEnded()
|
draggingEnded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func magnificationStarted() {
|
private func magnificationStarted() {
|
||||||
guard !renderer.updatesViewPort else { return }
|
guard !renderer.updatesViewPort else { return }
|
||||||
drawingView.touchCancelled()
|
drawingView.touchCancelled()
|
||||||
canvas.updateClipBounds(scrollView, on: drawingView)
|
canvas.updateClipBounds(scrollView, on: drawingView)
|
||||||
@@ -304,21 +414,21 @@ extension CanvasViewController {
|
|||||||
renderer.setUpdatesViewPort(true)
|
renderer.setUpdatesViewPort(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func magnificationEnded() {
|
private func magnificationEnded() {
|
||||||
renderer.setUpdatesViewPort(false)
|
renderer.setUpdatesViewPort(false)
|
||||||
renderer.setRedrawsGraphicRender()
|
renderer.setRedrawsGraphicRender()
|
||||||
renderView.draw()
|
renderView.draw()
|
||||||
drawingView.enableUserInteraction()
|
drawingView.enableUserInteraction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func draggingStarted() {
|
private func draggingStarted() {
|
||||||
guard !renderer.updatesViewPort else { return }
|
guard !renderer.updatesViewPort else { return }
|
||||||
canvas.updateClipBounds(scrollView, on: drawingView)
|
canvas.updateClipBounds(scrollView, on: drawingView)
|
||||||
drawingView.disableUserInteraction()
|
drawingView.disableUserInteraction()
|
||||||
renderer.setUpdatesViewPort(true)
|
renderer.setUpdatesViewPort(true)
|
||||||
}
|
}
|
||||||
|
|
||||||
func draggingEnded() {
|
private func draggingEnded() {
|
||||||
renderer.setUpdatesViewPort(false)
|
renderer.setUpdatesViewPort(false)
|
||||||
renderer.setRedrawsGraphicRender()
|
renderer.setRedrawsGraphicRender()
|
||||||
renderView.draw()
|
renderView.draw()
|
||||||
@@ -327,13 +437,13 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func penChanged(to pen: Pen?) {
|
private func penChanged(to pen: Pen?) {
|
||||||
if let pen, let device = drawingView.renderView.device {
|
if let pen, let device = drawingView.renderView.device {
|
||||||
pen.style.loadTexture(on: device)
|
pen.style.loadTexture(on: device)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func toolSelectionChanged(to selection: ToolSelection) {
|
private func toolSelectionChanged(to selection: ToolSelection) {
|
||||||
let enablesScrolling: Bool
|
let enablesScrolling: Bool
|
||||||
let enablesDrawing: Bool
|
let enablesDrawing: Bool
|
||||||
let enablesPhotoInsertion: Bool
|
let enablesPhotoInsertion: Bool
|
||||||
@@ -352,23 +462,36 @@ extension CanvasViewController {
|
|||||||
enablesDrawing = false
|
enablesDrawing = false
|
||||||
enablesPhotoInsertion = true
|
enablesPhotoInsertion = true
|
||||||
}
|
}
|
||||||
|
#if os(macOS)
|
||||||
|
#warning("TODO: implement for macos")
|
||||||
|
#else
|
||||||
scrollView.isScrollEnabled = enablesScrolling
|
scrollView.isScrollEnabled = enablesScrolling
|
||||||
drawingView.isUserInteractionEnabled = enablesDrawing
|
drawingView.isUserInteractionEnabled = enablesDrawing
|
||||||
photoInsertGesture?.isEnabled = enablesPhotoInsertion
|
photoInsertGesture?.isEnabled = enablesPhotoInsertion
|
||||||
enablesDrawing ? drawingView.enableUserInteraction() : drawingView.disableUserInteraction()
|
enablesDrawing ? drawingView.enableUserInteraction() : drawingView.disableUserInteraction()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
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))
|
||||||
|
#else
|
||||||
scrollView.setZoomScale(zoomScale, animated: true)
|
scrollView.setZoomScale(zoomScale, animated: true)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func lockModeChanged(_ state: Bool) {
|
private func lockModeChanged(_ state: Bool) {
|
||||||
|
#if os(macOS)
|
||||||
|
#warning("TODO: implement for macos")
|
||||||
|
#else
|
||||||
scrollView.pinchGestureRecognizer?.isEnabled = !state
|
scrollView.pinchGestureRecognizer?.isEnabled = !state
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func gridModeChanged(_ mode: GridMode) {
|
private func gridModeChanged(_ mode: GridMode) {
|
||||||
drawingView.disableUserInteraction()
|
drawingView.disableUserInteraction()
|
||||||
renderer.setRedrawsGraphicRender()
|
renderer.setRedrawsGraphicRender()
|
||||||
renderView.draw()
|
renderView.draw()
|
||||||
@@ -377,7 +500,7 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func historyUndid() {
|
private func historyUndid() {
|
||||||
guard let event = history.undo() else { return }
|
guard let event = history.undo() else { return }
|
||||||
drawingView.disableUserInteraction()
|
drawingView.disableUserInteraction()
|
||||||
canvas.graphicContext.undoGraphic(for: event)
|
canvas.graphicContext.undoGraphic(for: event)
|
||||||
@@ -386,7 +509,7 @@ extension CanvasViewController {
|
|||||||
drawingView.enableUserInteraction()
|
drawingView.enableUserInteraction()
|
||||||
}
|
}
|
||||||
|
|
||||||
func historyRedid() {
|
private func historyRedid() {
|
||||||
guard let event = history.redo() else { return }
|
guard let event = history.redo() else { return }
|
||||||
drawingView.disableUserInteraction()
|
drawingView.disableUserInteraction()
|
||||||
canvas.graphicContext.redoGraphic(for: event)
|
canvas.graphicContext.redoGraphic(for: event)
|
||||||
|
|||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// NSCenterClipView.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/6/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
#if canImport(AppKit)
|
||||||
|
import AppKit
|
||||||
|
|
||||||
|
final class NSCenterClipView: 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
|
||||||
@@ -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)
|
||||||
|
}
|
||||||
|
|
||||||
|
final 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
|
||||||
@@ -5,14 +5,14 @@
|
|||||||
// Created by Dscyre Scotti on 5/4/24.
|
// Created by Dscyre Scotti on 5/4/24.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import SwiftUI
|
||||||
import MetalKit
|
import MetalKit
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
class DrawingView: UIView {
|
final class DrawingView: Platform.View {
|
||||||
let tool: Tool
|
private let tool: Tool
|
||||||
let canvas: Canvas
|
private let canvas: Canvas
|
||||||
let history: History
|
private let history: History
|
||||||
let renderView: MTKView
|
let renderView: MTKView
|
||||||
|
|
||||||
var ratio: CGFloat { canvas.size.width / bounds.width }
|
var ratio: CGFloat { canvas.size.width / bounds.width }
|
||||||
@@ -37,6 +37,28 @@ class DrawingView: UIView {
|
|||||||
renderView.drawableSize = size.multiply(by: 2)
|
renderView.drawableSize = size.multiply(by: 2)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
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 {
|
override func gestureRecognizerShouldBegin(_ gestureRecognizer: UIGestureRecognizer) -> Bool {
|
||||||
!canvas.hasValidStroke
|
!canvas.hasValidStroke
|
||||||
}
|
}
|
||||||
@@ -78,6 +100,7 @@ class DrawingView: UIView {
|
|||||||
let point = touch.preciseLocation(in: self)
|
let point = touch.preciseLocation(in: self)
|
||||||
touchEnded(at: point)
|
touchEnded(at: point)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|
||||||
func touchBegan(at point: CGPoint) {
|
func touchBegan(at point: CGPoint) {
|
||||||
guard !disablesUserInteraction else { return }
|
guard !disablesUserInteraction else { return }
|
||||||
|
|||||||
@@ -7,14 +7,28 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct CanvasView: UIViewControllerRepresentable {
|
struct CanvasView: Platform.ViewControllerRepresentable {
|
||||||
@ObservedObject var tool: Tool
|
@ObservedObject private var tool: Tool
|
||||||
@ObservedObject var canvas: Canvas
|
@ObservedObject private var canvas: Canvas
|
||||||
@ObservedObject var history: History
|
@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 {
|
||||||
|
CanvasViewController(tool: tool, canvas: canvas, history: history)
|
||||||
|
}
|
||||||
|
|
||||||
|
func updateNSViewController(_ nsViewController: CanvasViewController, context: Context) { }
|
||||||
|
#else
|
||||||
func makeUIViewController(context: Context) -> CanvasViewController {
|
func makeUIViewController(context: Context) -> CanvasViewController {
|
||||||
CanvasViewController(tool: tool, canvas: canvas, history: history)
|
CanvasViewController(tool: tool, canvas: canvas, history: history)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateUIViewController(_ uiViewController: CanvasViewController, context: Context) { }
|
func updateUIViewController(_ uiViewController: CanvasViewController, context: Context) { }
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,10 +8,16 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ContextMenuViewModifier<MenuContent: View, Preview: View>: ViewModifier {
|
private struct ContextMenuViewModifier<MenuContent: View, Preview: View>: ViewModifier {
|
||||||
let condition: Bool
|
private let condition: Bool
|
||||||
let menuItems: () -> MenuContent
|
private let menuItems: () -> MenuContent
|
||||||
let preview: () -> Preview
|
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
|
@ViewBuilder
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
|
|||||||
@@ -0,0 +1,33 @@
|
|||||||
|
//
|
||||||
|
// OnDismissSearchViewModifier.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/14/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private struct OnDismissSearchViewModifier: ViewModifier {
|
||||||
|
@Environment(\.dismissSearch) private var dismissSearch
|
||||||
|
|
||||||
|
@Binding private var isActive: Bool
|
||||||
|
|
||||||
|
init(isActive: Binding<Bool>) {
|
||||||
|
self._isActive = isActive
|
||||||
|
}
|
||||||
|
|
||||||
|
func body(content: Content) -> some View {
|
||||||
|
content
|
||||||
|
.onChange(of: isActive) { oldValue, newValue in
|
||||||
|
if !newValue {
|
||||||
|
dismissSearch()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
extension View {
|
||||||
|
func onDismissSearch(isActive: Binding<Bool>) -> some View {
|
||||||
|
modifier(OnDismissSearchViewModifier(isActive: isActive))
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -8,10 +8,16 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct OnDragViewModifier<Preview: View>: ViewModifier {
|
private struct OnDragViewModifier<Preview: View>: ViewModifier {
|
||||||
let condition: Bool
|
private let condition: Bool
|
||||||
let data: () -> NSItemProvider
|
private let data: () -> NSItemProvider
|
||||||
let preview: () -> Preview
|
private let preview: () -> Preview
|
||||||
|
|
||||||
|
init(condition: Bool, data: @escaping () -> NSItemProvider, @ViewBuilder preview: @escaping () -> Preview) {
|
||||||
|
self.condition = condition
|
||||||
|
self.data = data
|
||||||
|
self.preview = preview
|
||||||
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func body(content: Content) -> some View {
|
func body(content: Content) -> some View {
|
||||||
|
|||||||
@@ -7,13 +7,19 @@
|
|||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
|
#if os(iOS)
|
||||||
struct CameraView: UIViewControllerRepresentable {
|
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
|
@Environment(\.dismiss) private var dismiss
|
||||||
|
|
||||||
|
init(image: Binding<UIImage?>, canvas: Canvas) {
|
||||||
|
self._image = image
|
||||||
|
self.canvas = canvas
|
||||||
|
}
|
||||||
|
|
||||||
func makeUIViewController(context: Context) -> UIImagePickerController {
|
func makeUIViewController(context: Context) -> UIImagePickerController {
|
||||||
let picker = UIImagePickerController()
|
let picker = UIImagePickerController()
|
||||||
picker.sourceType = .camera
|
picker.sourceType = .camera
|
||||||
@@ -44,3 +50,4 @@ struct CameraView: UIViewControllerRepresentable {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
|
|||||||
@@ -9,14 +9,18 @@ import SwiftUI
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct ColorPicker: View {
|
struct ColorPicker: View {
|
||||||
@State var hue: Double = 1
|
@State private var hue: Double = 1
|
||||||
@State var saturation: Double = 0
|
@State private var saturation: Double = 0
|
||||||
@State var brightness: Double = 1
|
@State private var brightness: Double = 1
|
||||||
@State var alpha: 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<Color>) {
|
||||||
|
self._color = color
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
VStack(spacing: 10) {
|
VStack(spacing: 10) {
|
||||||
@@ -43,7 +47,7 @@ struct ColorPicker: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var colorPicker: some View {
|
private var colorPicker: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
ZStack {
|
ZStack {
|
||||||
Color(hue: hue, saturation: 1, brightness: 1)
|
Color(hue: hue, saturation: 1, brightness: 1)
|
||||||
@@ -92,7 +96,7 @@ struct ColorPicker: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var hueSlider: some View {
|
private var hueSlider: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
LinearGradient(
|
LinearGradient(
|
||||||
@@ -138,7 +142,7 @@ struct ColorPicker: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var alphaSlider: some View {
|
private var alphaSlider: some View {
|
||||||
GeometryReader { proxy in
|
GeometryReader { proxy in
|
||||||
let color = Color(hue: hue, saturation: saturation, brightness: brightness)
|
let color = Color(hue: hue, saturation: saturation, brightness: brightness)
|
||||||
ZStack(alignment: .leading) {
|
ZStack(alignment: .leading) {
|
||||||
@@ -190,7 +194,7 @@ struct ColorPicker: View {
|
|||||||
.frame(height: size)
|
.frame(height: size)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updateColor() {
|
private func updateColor() {
|
||||||
color = Color(hue: hue, saturation: saturation, brightness: brightness).opacity(0.7 * alpha + 0.3)
|
color = Color(hue: hue, saturation: saturation, brightness: brightness).opacity(0.7 * alpha + 0.3)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,9 +8,13 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct Placeholder: View {
|
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 {
|
var body: some View {
|
||||||
VStack(spacing: 15) {
|
VStack(spacing: 15) {
|
||||||
|
|||||||
@@ -1,24 +0,0 @@
|
|||||||
//
|
|
||||||
// UIImage++.swift
|
|
||||||
// Memola
|
|
||||||
//
|
|
||||||
// Created by Dscyre Scotti on 6/15/24.
|
|
||||||
//
|
|
||||||
|
|
||||||
import UIKit
|
|
||||||
import Foundation
|
|
||||||
|
|
||||||
extension UIImage {
|
|
||||||
func imageWithUpOrientation() -> UIImage? {
|
|
||||||
switch imageOrientation {
|
|
||||||
case .up:
|
|
||||||
return self
|
|
||||||
default:
|
|
||||||
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
|
||||||
draw(in: CGRect(origin: .zero, size: size))
|
|
||||||
let result = UIGraphicsGetImageFromCurrentImageContext()
|
|
||||||
UIGraphicsEndImageContext()
|
|
||||||
return result
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
@@ -8,25 +8,68 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct DashboardView: View {
|
struct DashboardView: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
@State var memo: MemoObject?
|
@EnvironmentObject private var application: Application
|
||||||
@State var sidebarItem: SidebarItem? = .memos
|
|
||||||
|
@State private var sidebarItem: SidebarItem? = .memos
|
||||||
|
@AppStorage("memola.app.scene.side-bar.column-visibility") private var columnVisibility: NavigationSplitViewVisibility = .all
|
||||||
|
|
||||||
|
@Namespace private var namespace
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
NavigationSplitView {
|
#if os(macOS)
|
||||||
|
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)
|
||||||
|
.onChange(of: columnVisibility) { oldValue, newValue in
|
||||||
|
application.changeSidebarVisibility(newValue == .all ? .shown : .hidden)
|
||||||
|
}
|
||||||
|
#else
|
||||||
|
NavigationSplitView(columnVisibility: $columnVisibility) {
|
||||||
Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass)
|
Sidebar(sidebarItem: $sidebarItem, horizontalSizeClass: horizontalSizeClass)
|
||||||
} detail: {
|
} detail: {
|
||||||
switch sidebarItem {
|
switch sidebarItem {
|
||||||
case .memos:
|
case .memos:
|
||||||
MemosView(memo: $memo)
|
MemosView()
|
||||||
case .trash:
|
case .trash:
|
||||||
TrashView(memo: $memo, sidebarItem: $sidebarItem)
|
TrashView(sidebarItem: $sidebarItem)
|
||||||
default:
|
default:
|
||||||
MemosView(memo: $memo)
|
MemosView()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.fullScreenCover(item: $memo) { memo in
|
.fullScreenCover(item: $application.memoObject) { memo in
|
||||||
MemoView(memo: memo)
|
MemoView(memo: memo)
|
||||||
.onDisappear {
|
.onDisappear {
|
||||||
withPersistence(\.viewContext) { context in
|
withPersistence(\.viewContext) { context in
|
||||||
@@ -35,5 +78,6 @@ struct DashboardView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,31 +8,32 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MemosView: View {
|
struct MemosView: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.shortcut) private var shortcut
|
||||||
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
@FetchRequest var memoObjects: FetchedResults<MemoObject>
|
@EnvironmentObject private var application: Application
|
||||||
|
|
||||||
@State var query: String = ""
|
@FetchRequest private var memoObjects: FetchedResults<MemoObject>
|
||||||
@State var currentDate: Date = .now
|
|
||||||
|
|
||||||
@Binding var memo: MemoObject?
|
@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.sort") private var sort: Sort = .recent
|
||||||
@AppStorage("memola.memo-objects.memos.filter") var filter: Filter = .none
|
@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
|
query.isEmpty ? .memoEmpty : .memoNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
init(memo: Binding<MemoObject?>) {
|
init() {
|
||||||
_memo = memo
|
|
||||||
let standard = UserDefaults.standard
|
let standard = UserDefaults.standard
|
||||||
var descriptors: [SortDescriptor<MemoObject>] = []
|
var descriptors: [SortDescriptor<MemoObject>] = []
|
||||||
var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")]
|
var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")]
|
||||||
let sort = Sort(rawValue: standard.value(forKey: "memola.memo-objects.sort") as? String ?? "") ?? .recent
|
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.filter") as? String ?? "") ?? .none
|
let filter = Filter(rawValue: standard.value(forKey: "memola.memo-objects.memos.filter") as? String ?? "") ?? .none
|
||||||
if filter == .favorites {
|
if filter == .favorites {
|
||||||
predicates.append(NSPredicate(format: "isFavorite = YES"))
|
predicates.append(NSPredicate(format: "isFavorite = YES"))
|
||||||
}
|
}
|
||||||
@@ -45,10 +46,45 @@ struct MemosView: View {
|
|||||||
MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in
|
MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in
|
||||||
memoCard(memoObject, cellWidth)
|
memoCard(memoObject, cellWidth)
|
||||||
}
|
}
|
||||||
|
.onDismissSearch(isActive: $isActiveSearch)
|
||||||
|
.focusedSceneValue(\.activeSceneKey, .memos)
|
||||||
.navigationTitle(horizontalSizeClass == .compact ? "Memos" : "")
|
.navigationTitle(horizontalSizeClass == .compact ? "Memos" : "")
|
||||||
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.searchable(text: $query, placement: .toolbar, prompt: Text("Search"))
|
#endif
|
||||||
|
.searchable(text: $query, isPresented: $isActiveSearch, placement: .toolbar, prompt: Text("Search"))
|
||||||
|
.onSubmit(of: .search) {
|
||||||
|
isActiveSearch = false
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
#if os(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 {
|
if horizontalSizeClass == .regular {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Text("Memola")
|
Text("Memola")
|
||||||
@@ -73,14 +109,12 @@ struct MemosView: View {
|
|||||||
.tag(sort)
|
.tag(sort)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.automatic)
|
|
||||||
Picker("", selection: $filter) {
|
Picker("", selection: $filter) {
|
||||||
ForEach(Filter.all) { filter in
|
ForEach(Filter.all) { filter in
|
||||||
Text(filter.name)
|
Text(filter.name)
|
||||||
.tag(filter)
|
.tag(filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.automatic)
|
|
||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "ellipsis.circle")
|
Image(systemName: "ellipsis.circle")
|
||||||
@@ -104,13 +138,14 @@ struct MemosView: View {
|
|||||||
.tag(filter)
|
.tag(filter)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.pickerStyle(.automatic)
|
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "line.3.horizontal.decrease.circle")
|
Image(systemName: "line.3.horizontal.decrease.circle")
|
||||||
}
|
}
|
||||||
|
.hoverEffect(.lift)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.onChange(of: sort) { oldValue, newValue in
|
.onChange(of: sort) { oldValue, newValue in
|
||||||
memoObjects.sortDescriptors = newValue.memoSortDescriptors
|
memoObjects.sortDescriptors = newValue.memoSortDescriptors
|
||||||
@@ -124,13 +159,12 @@ struct MemosView: View {
|
|||||||
.onReceive(timer) { date in
|
.onReceive(timer) { date in
|
||||||
currentDate = date
|
currentDate = date
|
||||||
}
|
}
|
||||||
.onAppear {
|
.onReceive(shortcut.publisher) { shortcut in
|
||||||
memoObjects.sortDescriptors = sort.memoSortDescriptors
|
handleShortcut(for: shortcut)
|
||||||
updatePredicate()
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View {
|
private func memoCard(_ memoObject: MemoObject, _ cellWidth: CGFloat) -> some View {
|
||||||
MemoCard(memoObject: memoObject, cellWidth: cellWidth) { card in
|
MemoCard(memoObject: memoObject, cellWidth: cellWidth) { card in
|
||||||
card
|
card
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
@@ -138,11 +172,13 @@ struct MemosView: View {
|
|||||||
openMemo(for: memoObject)
|
openMemo(for: memoObject)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Open", systemImage: "doc.text")
|
Label("Open", systemImage: "doc.text")
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
}
|
}
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
markAsTrash(for: memoObject)
|
markAsTrash(for: memoObject)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Delete", systemImage: "trash")
|
Label("Delete", systemImage: "trash")
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay(alignment: .topTrailing) {
|
.overlay(alignment: .topTrailing) {
|
||||||
@@ -152,7 +188,11 @@ struct MemosView: View {
|
|||||||
.animation(.easeInOut, value: memoObject.isFavorite)
|
.animation(.easeInOut, value: memoObject.isFavorite)
|
||||||
.frame(width: 20, height: 20)
|
.frame(width: 20, height: 20)
|
||||||
.padding(5)
|
.padding(5)
|
||||||
|
#if os(macOS)
|
||||||
|
.background(.gray)
|
||||||
|
#else
|
||||||
.background(.gray.tertiary)
|
.background(.gray.tertiary)
|
||||||
|
#endif
|
||||||
.cornerRadius(5)
|
.cornerRadius(5)
|
||||||
.contentShape(Rectangle())
|
.contentShape(Rectangle())
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
@@ -170,7 +210,7 @@ struct MemosView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func createMemo(title: String) {
|
private func createMemo(title: String) {
|
||||||
let memoObject = MemoObject(\.viewContext)
|
let memoObject = MemoObject(\.viewContext)
|
||||||
memoObject.title = title
|
memoObject.title = title
|
||||||
memoObject.createdAt = .now
|
memoObject.createdAt = .now
|
||||||
@@ -220,11 +260,11 @@ struct MemosView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func openMemo(for memo: MemoObject) {
|
private func openMemo(for memo: MemoObject) {
|
||||||
self.memo = memo
|
application.openMemo(memo)
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePredicate() {
|
private func updatePredicate() {
|
||||||
var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")]
|
var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = NO")]
|
||||||
if !query.isEmpty {
|
if !query.isEmpty {
|
||||||
predicates.append(NSPredicate(format: "title contains[c] %@", query))
|
predicates.append(NSPredicate(format: "title contains[c] %@", query))
|
||||||
@@ -235,18 +275,29 @@ struct MemosView: View {
|
|||||||
memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||||
}
|
}
|
||||||
|
|
||||||
func toggleFavorite(for memo: MemoObject) {
|
private func toggleFavorite(for memo: MemoObject) {
|
||||||
memo.isFavorite.toggle()
|
memo.isFavorite.toggle()
|
||||||
withPersistence(\.viewContext) { context in
|
withPersistence(\.viewContext) { context in
|
||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func markAsTrash(for memo: MemoObject) {
|
private func markAsTrash(for memo: MemoObject) {
|
||||||
memo.isTrash = true
|
memo.isTrash = true
|
||||||
memo.deletedAt = .now
|
memo.deletedAt = .now
|
||||||
withPersistence(\.viewContext) { context in
|
withPersistence(\.viewContext) { context in
|
||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
private func handleShortcut(for shortcut: Shortcuts) {
|
||||||
|
switch shortcut {
|
||||||
|
case .newMemo:
|
||||||
|
if application.memoObject == nil {
|
||||||
|
createMemo(title: "Untitled")
|
||||||
|
}
|
||||||
|
default:
|
||||||
|
break
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,12 +8,12 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MemoCard<Preview: View, Detail: View>: View {
|
struct MemoCard<Preview: View, Detail: View>: View {
|
||||||
let memoObject: MemoObject
|
private let memoObject: MemoObject
|
||||||
let cellWidth: CGFloat
|
private let cellWidth: CGFloat
|
||||||
let modifyPreview: ((MemoPreview) -> Preview)?
|
private let modifyPreview: ((MemoPreview) -> Preview)?
|
||||||
let details: () -> Detail
|
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.memoObject = memoObject
|
||||||
self.cellWidth = cellWidth
|
self.cellWidth = cellWidth
|
||||||
self.modifyPreview = modifyPreview
|
self.modifyPreview = modifyPreview
|
||||||
|
|||||||
@@ -8,12 +8,18 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MemoGrid<Card: View>: View {
|
struct MemoGrid<Card: View>: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
let memoObjects: FetchedResults<MemoObject>
|
private let memoObjects: FetchedResults<MemoObject>
|
||||||
let placeholder: Placeholder.Info
|
private let placeholder: Placeholder.Info
|
||||||
@ViewBuilder let card: (MemoObject, CGFloat) -> Card
|
@ViewBuilder private let card: (MemoObject, CGFloat) -> Card
|
||||||
|
|
||||||
var maxCellWidth: CGFloat {
|
init(memoObjects: FetchedResults<MemoObject>, 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 {
|
if horizontalSizeClass == .compact {
|
||||||
return 180
|
return 180
|
||||||
}
|
}
|
||||||
@@ -42,6 +48,10 @@ struct MemoGrid<Card: View>: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background(Color(uiColor: .secondarySystemBackground))
|
#if os(macOS)
|
||||||
|
.background(Color(color: .windowBackgroundColor))
|
||||||
|
#else
|
||||||
|
.background(Color(color: .secondarySystemBackground))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,11 +8,17 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct MemoPreview: View {
|
struct MemoPreview: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
let preview: Data?
|
private let preview: Data?
|
||||||
let cellWidth: CGFloat
|
private let cellWidth: CGFloat
|
||||||
var cellHeight: CGFloat {
|
|
||||||
|
init(preview: Data?, cellWidth: CGFloat) {
|
||||||
|
self.preview = preview
|
||||||
|
self.cellWidth = cellWidth
|
||||||
|
}
|
||||||
|
|
||||||
|
private var cellHeight: CGFloat {
|
||||||
if horizontalSizeClass == .compact {
|
if horizontalSizeClass == .compact {
|
||||||
return 120
|
return 120
|
||||||
}
|
}
|
||||||
@@ -21,8 +27,8 @@ struct MemoPreview: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
if let preview, let previewImage = UIImage(data: preview) {
|
if let preview, let previewImage = Platform.Image(data: preview) {
|
||||||
Image(uiImage: previewImage)
|
Image(image: previewImage)
|
||||||
.resizable()
|
.resizable()
|
||||||
.aspectRatio(contentMode: .fit)
|
.aspectRatio(contentMode: .fit)
|
||||||
} else {
|
} else {
|
||||||
|
|||||||
@@ -8,23 +8,25 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct TrashView: View {
|
struct TrashView: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.shortcut) private var shortcut
|
||||||
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
@FetchRequest var memoObjects: FetchedResults<MemoObject>
|
@EnvironmentObject private var application: Application
|
||||||
|
|
||||||
@State var query: String = ""
|
@FetchRequest private var memoObjects: FetchedResults<MemoObject>
|
||||||
@State var restoredMemo: MemoObject?
|
|
||||||
@State var deletedMemo: MemoObject?
|
|
||||||
|
|
||||||
@Binding var memo: MemoObject?
|
@State private var query: String = ""
|
||||||
@Binding var sidebarItem: SidebarItem?
|
@State private var restoredMemo: MemoObject?
|
||||||
|
@State private var deletedMemo: MemoObject?
|
||||||
|
@State private var isActiveSearch: Bool = false
|
||||||
|
|
||||||
var placeholder: Placeholder.Info {
|
@Binding private var sidebarItem: SidebarItem?
|
||||||
|
|
||||||
|
private var placeholder: Placeholder.Info {
|
||||||
query.isEmpty ? .trashEmpty : .trashNotFound
|
query.isEmpty ? .trashEmpty : .trashNotFound
|
||||||
}
|
}
|
||||||
|
|
||||||
init(memo: Binding<MemoObject?>, sidebarItem: Binding<SidebarItem?>) {
|
init(sidebarItem: Binding<SidebarItem?>) {
|
||||||
_memo = memo
|
|
||||||
_sidebarItem = sidebarItem
|
_sidebarItem = sidebarItem
|
||||||
let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)]
|
let descriptors = [SortDescriptor(\MemoObject.deletedAt, order: .reverse)]
|
||||||
let predicate = NSPredicate(format: "isTrash = YES")
|
let predicate = NSPredicate(format: "isTrash = YES")
|
||||||
@@ -45,10 +47,24 @@ struct TrashView: View {
|
|||||||
MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in
|
MemoGrid(memoObjects: memoObjects, placeholder: placeholder) { memoObject, cellWidth in
|
||||||
memoCard(memoObject, cellWidth)
|
memoCard(memoObject, cellWidth)
|
||||||
}
|
}
|
||||||
|
.onDismissSearch(isActive: $isActiveSearch)
|
||||||
|
.focusedSceneValue(\.activeSceneKey, .trash)
|
||||||
.navigationTitle(horizontalSizeClass == .compact ? "Trash" : "")
|
.navigationTitle(horizontalSizeClass == .compact ? "Trash" : "")
|
||||||
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(.inline)
|
.navigationBarTitleDisplayMode(.inline)
|
||||||
.searchable(text: $query, placement: .toolbar, prompt: Text("Search"))
|
#endif
|
||||||
|
.searchable(text: $query, isPresented: $isActiveSearch, placement: .toolbar, prompt: Text("Search"))
|
||||||
|
.onSubmit(of: .search) {
|
||||||
|
isActiveSearch = false
|
||||||
|
}
|
||||||
.toolbar {
|
.toolbar {
|
||||||
|
#if os(macOS)
|
||||||
|
ToolbarItem(placement: .navigation) {
|
||||||
|
Text("Memola")
|
||||||
|
.font(.title3)
|
||||||
|
.fontWeight(.bold)
|
||||||
|
}
|
||||||
|
#else
|
||||||
if horizontalSizeClass == .regular {
|
if horizontalSizeClass == .regular {
|
||||||
ToolbarItem(placement: .topBarLeading) {
|
ToolbarItem(placement: .topBarLeading) {
|
||||||
Text("Memola")
|
Text("Memola")
|
||||||
@@ -56,6 +72,7 @@ struct TrashView: View {
|
|||||||
.fontWeight(.bold)
|
.fontWeight(.bold)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.onChange(of: query) { oldValue, newValue in
|
.onChange(of: query) { oldValue, newValue in
|
||||||
updatePredicate()
|
updatePredicate()
|
||||||
@@ -87,7 +104,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
|
MemoCard(memoObject: memoObject, cellWidth: cellWidth) { card in
|
||||||
card
|
card
|
||||||
.contextMenu {
|
.contextMenu {
|
||||||
@@ -95,11 +112,13 @@ struct TrashView: View {
|
|||||||
restoreMemo(for: memoObject)
|
restoreMemo(for: memoObject)
|
||||||
} label: {
|
} label: {
|
||||||
Label("Restore", systemImage: "square.and.arrow.down")
|
Label("Restore", systemImage: "square.and.arrow.down")
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
}
|
}
|
||||||
Button(role: .destructive) {
|
Button(role: .destructive) {
|
||||||
deletedMemo = memoObject
|
deletedMemo = memoObject
|
||||||
} label: {
|
} label: {
|
||||||
Label("Delete Permanently", systemImage: "trash")
|
Label("Delete Permanently", systemImage: "trash")
|
||||||
|
.labelStyle(.titleAndIcon)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
} details: {
|
} details: {
|
||||||
@@ -114,7 +133,7 @@ struct TrashView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func updatePredicate() {
|
private func updatePredicate() {
|
||||||
var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = YES")]
|
var predicates: [NSPredicate] = [NSPredicate(format: "isTrash = YES")]
|
||||||
if !query.isEmpty {
|
if !query.isEmpty {
|
||||||
predicates.append(NSPredicate(format: "title contains[c] %@", query))
|
predicates.append(NSPredicate(format: "title contains[c] %@", query))
|
||||||
@@ -122,7 +141,7 @@ struct TrashView: View {
|
|||||||
memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
memoObjects.nsPredicate = NSCompoundPredicate(type: .and, subpredicates: predicates)
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreMemo(for memo: MemoObject?) {
|
private func restoreMemo(for memo: MemoObject?) {
|
||||||
guard let memo else { return }
|
guard let memo else { return }
|
||||||
memo.isTrash = false
|
memo.isTrash = false
|
||||||
memo.deletedAt = nil
|
memo.deletedAt = nil
|
||||||
@@ -131,13 +150,15 @@ struct TrashView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func restoreAndOpenMemo(for memo: MemoObject?) {
|
private func restoreAndOpenMemo(for memo: MemoObject?) {
|
||||||
restoreMemo(for: memo)
|
restoreMemo(for: memo)
|
||||||
self.sidebarItem = .memos
|
self.sidebarItem = .memos
|
||||||
self.memo = memo
|
if let memo {
|
||||||
|
application.openMemo(memo)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func deleteMemo(for memo: MemoObject?) {
|
private func deleteMemo(for memo: MemoObject?) {
|
||||||
guard let memo else { return }
|
guard let memo else { return }
|
||||||
withPersistenceSync(\.viewContext) { context in
|
withPersistenceSync(\.viewContext) { context in
|
||||||
context.delete(memo)
|
context.delete(memo)
|
||||||
|
|||||||
@@ -8,10 +8,15 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct Sidebar: View {
|
struct Sidebar: View {
|
||||||
let sidebarItems: [SidebarItem] = [.memos, .trash]
|
private let sidebarItems: [SidebarItem] = [.memos, .trash]
|
||||||
@Binding var sidebarItem: SidebarItem?
|
@Binding private var sidebarItem: SidebarItem?
|
||||||
|
|
||||||
let horizontalSizeClass: UserInterfaceSizeClass?
|
private let horizontalSizeClass: UserInterfaceSizeClass?
|
||||||
|
|
||||||
|
init(sidebarItem: Binding<SidebarItem?>, horizontalSizeClass: UserInterfaceSizeClass?) {
|
||||||
|
self._sidebarItem = sidebarItem
|
||||||
|
self.horizontalSizeClass = horizontalSizeClass
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
List(selection: $sidebarItem) {
|
List(selection: $sidebarItem) {
|
||||||
@@ -38,14 +43,20 @@ struct Sidebar: View {
|
|||||||
.listStyle(.sidebar)
|
.listStyle(.sidebar)
|
||||||
.navigationTitle(horizontalSizeClass == .compact ? "Memola" : "")
|
.navigationTitle(horizontalSizeClass == .compact ? "Memola" : "")
|
||||||
.scrollContentBackground(.hidden)
|
.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)
|
.navigationSplitViewColumnWidth(min: 250, ideal: 250, max: 250)
|
||||||
|
#if os(iOS)
|
||||||
.navigationBarTitleDisplayMode(horizontalSizeClass == .compact ? .automatic : .inline)
|
.navigationBarTitleDisplayMode(horizontalSizeClass == .compact ? .automatic : .inline)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension Sidebar {
|
extension Sidebar {
|
||||||
struct SidebarItemButtonStyle: ButtonStyle {
|
fileprivate struct SidebarItemButtonStyle: ButtonStyle {
|
||||||
let state: State
|
let state: State
|
||||||
|
|
||||||
func makeBody(configuration: Configuration) -> some View {
|
func makeBody(configuration: Configuration) -> some View {
|
||||||
|
|||||||
@@ -10,29 +10,29 @@ import PhotosUI
|
|||||||
import AVFoundation
|
import AVFoundation
|
||||||
|
|
||||||
struct ElementToolbar: View {
|
struct ElementToolbar: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.colorScheme) private var colorScheme
|
||||||
let size: CGFloat
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
@ObservedObject var tool: Tool
|
|
||||||
@ObservedObject var canvas: Canvas
|
|
||||||
|
|
||||||
@State var opensCamera: Bool = false
|
private let size: CGFloat = 40
|
||||||
@State var isCameraAccessDenied: Bool = false
|
@ObservedObject private var tool: Tool
|
||||||
@State var photosPickerItem: PhotosPickerItem?
|
@ObservedObject private var canvas: Canvas
|
||||||
|
|
||||||
@Namespace var namespace
|
init(tool: Tool, canvas: Canvas) {
|
||||||
|
self.tool = tool
|
||||||
|
self.canvas = canvas
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
|
#if os(macOS)
|
||||||
|
regularToolbar
|
||||||
|
#else
|
||||||
if horizontalSizeClass == .regular {
|
if horizontalSizeClass == .regular {
|
||||||
regularToolbar
|
regularToolbar
|
||||||
} else {
|
} else {
|
||||||
ZStack(alignment: .bottom) {
|
ZStack(alignment: .bottom) {
|
||||||
if tool.selection == .photo {
|
if tool.selection == .photo {
|
||||||
photoOption
|
PhotoDock(tool: tool, canvas: canvas)
|
||||||
.background {
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.fill(.regularMaterial)
|
|
||||||
}
|
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
.frame(maxWidth: .infinity)
|
.frame(maxWidth: .infinity)
|
||||||
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
||||||
@@ -40,46 +40,13 @@ struct ElementToolbar: View {
|
|||||||
compactToolbar
|
compactToolbar
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.bottom, 10)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.fullScreenCover(isPresented: $opensCamera) {
|
|
||||||
let image: Binding<UIImage?> = 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: UIApplication.openSettingsURLString + "&path=CAMERA/\(String(describing: Bundle.main.bundleIdentifier))") {
|
|
||||||
UIApplication.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.")
|
|
||||||
}
|
|
||||||
.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) {
|
|
||||||
tool.selectPhoto(image, for: canvas.canvasID)
|
|
||||||
}
|
|
||||||
photosPickerItem = nil
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
.foregroundStyle(Color.accentColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
var regularToolbar: some View {
|
private var regularToolbar: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -90,15 +57,19 @@ struct ElementToolbar: View {
|
|||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.frame(width: size, height: size)
|
.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))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
.background {
|
.background {
|
||||||
if tool.selection == .hand {
|
if tool.selection == .hand {
|
||||||
Color.accentColor
|
Color.accentColor
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.matchedGeometryEffect(id: "element.toolbar.bg", in: namespace)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
Button {
|
Button {
|
||||||
@@ -110,15 +81,19 @@ struct ElementToolbar: View {
|
|||||||
.fontWeight(.heavy)
|
.fontWeight(.heavy)
|
||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.frame(width: size, height: size)
|
.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))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
.background {
|
.background {
|
||||||
if tool.selection == .pen {
|
if tool.selection == .pen {
|
||||||
Color.accentColor
|
Color.accentColor
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.matchedGeometryEffect(id: "element.toolbar.bg", in: namespace)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
@@ -130,32 +105,20 @@ struct ElementToolbar: View {
|
|||||||
Image(systemName: "photo")
|
Image(systemName: "photo")
|
||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.frame(width: size, height: size)
|
.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))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
.background {
|
.background {
|
||||||
if tool.selection == .photo {
|
if tool.selection == .photo {
|
||||||
Color.accentColor
|
Color.accentColor
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.matchedGeometryEffect(id: "element.toolbar.bg", in: namespace)
|
|
||||||
}
|
}
|
||||||
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)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.background {
|
|
||||||
if tool.selection == .photo {
|
|
||||||
RoundedRectangle(cornerRadius: 8)
|
|
||||||
.fill(Color.white.tertiary)
|
|
||||||
.transition(.move(edge: .leading).combined(with: .opacity).animation(.easeIn(duration: 0.1)))
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -166,7 +129,7 @@ struct ElementToolbar: View {
|
|||||||
.transition(.move(edge: .top).combined(with: .blurReplace))
|
.transition(.move(edge: .top).combined(with: .blurReplace))
|
||||||
}
|
}
|
||||||
|
|
||||||
var compactToolbar: some View {
|
private var compactToolbar: some View {
|
||||||
HStack(spacing: 0) {
|
HStack(spacing: 0) {
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
@@ -179,7 +142,9 @@ struct ElementToolbar: View {
|
|||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#endif
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
tool.selectTool(.photo)
|
tool.selectTool(.photo)
|
||||||
@@ -190,7 +155,9 @@ struct ElementToolbar: View {
|
|||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
@@ -201,44 +168,5 @@ struct ElementToolbar: View {
|
|||||||
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
||||||
}
|
}
|
||||||
|
|
||||||
var photoOption: some View {
|
|
||||||
HStack(spacing: 0) {
|
|
||||||
Button {
|
|
||||||
openCamera()
|
|
||||||
} label: {
|
|
||||||
Image(systemName: "camera.fill")
|
|
||||||
.contentShape(.circle)
|
|
||||||
.frame(width: size, height: size)
|
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
|
||||||
}
|
|
||||||
.hoverEffect(.lift)
|
|
||||||
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))
|
|
||||||
}
|
|
||||||
.hoverEffect(.lift)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
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
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -9,17 +9,17 @@ import SwiftUI
|
|||||||
import CoreData
|
import CoreData
|
||||||
|
|
||||||
struct MemoView: View {
|
struct MemoView: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
@StateObject var tool: Tool
|
@StateObject private var tool: Tool
|
||||||
@StateObject var canvas: Canvas
|
@StateObject private var canvas: Canvas
|
||||||
@StateObject var history: History
|
@StateObject private var history: History
|
||||||
|
|
||||||
@State var memo: MemoObject
|
@State private var title: String
|
||||||
@State var title: String
|
@FocusState private var textFieldState: Bool
|
||||||
@FocusState var textFieldState: Bool
|
|
||||||
|
|
||||||
let size: CGFloat = 32
|
private let memo: MemoObject
|
||||||
|
private let size: CGFloat = 40
|
||||||
|
|
||||||
init(memo: MemoObject) {
|
init(memo: MemoObject) {
|
||||||
self.memo = memo
|
self.memo = memo
|
||||||
@@ -31,14 +31,18 @@ struct MemoView: View {
|
|||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Group {
|
Group {
|
||||||
|
#if os(macOS)
|
||||||
|
canvasView
|
||||||
|
#else
|
||||||
if horizontalSizeClass == .regular {
|
if horizontalSizeClass == .regular {
|
||||||
canvasView
|
canvasView
|
||||||
} else {
|
} else {
|
||||||
compactCanvasView
|
compactCanvasView
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.overlay(alignment: .top) {
|
.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(textFieldState || tool.isLoadingPhoto)
|
||||||
.disabled(canvas.state == .loading || canvas.state == .closing)
|
.disabled(canvas.state == .loading || canvas.state == .closing)
|
||||||
@@ -57,21 +61,28 @@ struct MemoView: View {
|
|||||||
loadingIndicator("Loading photo...")
|
loadingIndicator("Loading photo...")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
.focusedSceneObject(tool)
|
||||||
|
.focusedSceneObject(canvas)
|
||||||
|
.focusedSceneObject(history)
|
||||||
|
.focusedSceneValue(\.activeSceneKey, .memo)
|
||||||
}
|
}
|
||||||
|
|
||||||
var canvasView: some View {
|
private var canvasView: some View {
|
||||||
CanvasView(tool: tool, canvas: canvas, history: history)
|
CanvasView(tool: tool, canvas: canvas, history: history)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.overlay(alignment: .bottomTrailing) {
|
.overlay(alignment: .trailing) {
|
||||||
switch tool.selection {
|
switch tool.selection {
|
||||||
case .pen:
|
case .pen:
|
||||||
PenDock(tool: tool, canvas: canvas, size: size)
|
PenDock(tool: tool, canvas: canvas)
|
||||||
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
|
||||||
case .photo:
|
case .photo:
|
||||||
if let photoItem = tool.selectedPhotoItem {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
PhotoPreview(photoItem: photoItem, tool: tool)
|
PhotoDock(tool: tool, canvas: canvas)
|
||||||
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
if let photoItem = tool.selectedPhotoItem {
|
||||||
|
PhotoPreview(photoItem: photoItem, tool: tool)
|
||||||
|
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||||
default:
|
default:
|
||||||
EmptyView()
|
EmptyView()
|
||||||
}
|
}
|
||||||
@@ -81,13 +92,13 @@ struct MemoView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var compactCanvasView: some View {
|
private var compactCanvasView: some View {
|
||||||
CanvasView(tool: tool, canvas: canvas, history: history)
|
CanvasView(tool: tool, canvas: canvas, history: history)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.overlay(alignment: .bottom) {
|
.overlay(alignment: .bottom) {
|
||||||
switch tool.selection {
|
switch tool.selection {
|
||||||
case .pen:
|
case .pen:
|
||||||
PenDock(tool: tool, canvas: canvas, size: size)
|
PenDock(tool: tool, canvas: canvas)
|
||||||
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
||||||
case .photo:
|
case .photo:
|
||||||
if let photoItem = tool.selectedPhotoItem {
|
if let photoItem = tool.selectedPhotoItem {
|
||||||
@@ -101,69 +112,86 @@ struct MemoView: View {
|
|||||||
}
|
}
|
||||||
.overlay(alignment: .bottom) {
|
.overlay(alignment: .bottom) {
|
||||||
if tool.selection != .pen {
|
if tool.selection != .pen {
|
||||||
ElementToolbar(size: size, tool: tool, canvas: canvas)
|
ElementToolbar(tool: tool, canvas: canvas)
|
||||||
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
.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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var zoomControl: some View {
|
private var zoomControl: some View {
|
||||||
let upperBound: CGFloat = 400
|
let upperBound: CGFloat = 400
|
||||||
let lowerBound: CGFloat = 10
|
let lowerBound: CGFloat = 10
|
||||||
let zoomScale: CGFloat = (((canvas.zoomScale - canvas.minimumZoomScale) * (upperBound - lowerBound) / (canvas.maximumZoomScale - canvas.minimumZoomScale)) + lowerBound).rounded()
|
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]
|
let zoomScales: [Int] = [400, 200, 100, 75, 50, 25, 10]
|
||||||
if !canvas.locksCanvas {
|
#if os(macOS)
|
||||||
Menu {
|
Menu {
|
||||||
ForEach(zoomScales, id: \.self) { scale in
|
ForEach(zoomScales, id: \.self) { scale in
|
||||||
Button {
|
Button {
|
||||||
let zoomScale = ((CGFloat(scale) - lowerBound) * (canvas.maximumZoomScale - canvas.minimumZoomScale) / (upperBound - lowerBound)) + canvas.minimumZoomScale
|
let zoomScale = ((CGFloat(scale) - lowerBound) * (canvas.maximumZoomScale - canvas.minimumZoomScale) / (upperBound - lowerBound)) + canvas.minimumZoomScale
|
||||||
canvas.zoomPublisher.send(zoomScale)
|
canvas.zoomPublisher.send(zoomScale)
|
||||||
} label: {
|
} label: {
|
||||||
Label {
|
Label {
|
||||||
Text(scale, format: .percent)
|
Text(scale, format: .percent)
|
||||||
} icon: {
|
} icon: {
|
||||||
if CGFloat(scale) == zoomScale {
|
if CGFloat(scale) == zoomScale {
|
||||||
Image(systemName: "checkmark")
|
Image(systemName: "checkmark")
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.font(.headline)
|
|
||||||
}
|
}
|
||||||
|
.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))
|
|
||||||
.padding(10)
|
|
||||||
}
|
}
|
||||||
.hoverEffect(.lift)
|
} label: {
|
||||||
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
Text(zoomScale / 100, format: .percent)
|
||||||
|
.foregroundStyle(Color.accentColor)
|
||||||
|
.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 {
|
private func loadingIndicator(_ title: String) -> some View {
|
||||||
ProgressView {
|
ProgressView {
|
||||||
Text(title)
|
Text(title)
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -8,114 +8,143 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PenDock: View {
|
struct PenDock: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
@ObservedObject var tool: Tool
|
@ObservedObject private var tool: Tool
|
||||||
@ObservedObject var canvas: Canvas
|
@ObservedObject private var canvas: Canvas
|
||||||
|
|
||||||
let size: CGFloat
|
private let size: CGFloat = 40
|
||||||
var width: CGFloat {
|
private let penPropertySize: CGFloat = 32
|
||||||
horizontalSizeClass == .compact ? 30 : 90
|
private var width: CGFloat {
|
||||||
|
horizontalSizeClass == .compact ? size / 2 : size
|
||||||
}
|
}
|
||||||
var height: CGFloat {
|
private var height: CGFloat {
|
||||||
horizontalSizeClass == .compact ? 90 : 30
|
horizontalSizeClass == .compact ? size : size / 2
|
||||||
}
|
}
|
||||||
var factor: CGFloat = 0.9
|
|
||||||
|
|
||||||
@State var refreshScrollId: UUID = UUID()
|
@State private var refreshScrollId: UUID = UUID()
|
||||||
@State var opensColorPicker: Bool = false
|
@State private var opensColorPicker: Bool = false
|
||||||
|
#if os(macOS)
|
||||||
|
@State private var showsThinknessPicker: Bool = false
|
||||||
|
#endif
|
||||||
|
|
||||||
|
init(tool: Tool, canvas: Canvas) {
|
||||||
|
self.tool = tool
|
||||||
|
self.canvas = canvas
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
if horizontalSizeClass == .regular {
|
#if os(macOS)
|
||||||
ZStack(alignment: .bottomTrailing) {
|
GeometryReader { proxy in
|
||||||
if !canvas.locksCanvas {
|
VStack(alignment: .trailing, spacing: 5) {
|
||||||
VStack(alignment: .trailing) {
|
penPropertyTool
|
||||||
penPropertyTool
|
penItemList
|
||||||
penItemList
|
.frame(maxWidth: proxy.size.width * 0.4)
|
||||||
}
|
|
||||||
.fixedSize()
|
|
||||||
.frame(maxHeight: .infinity)
|
|
||||||
.padding(10)
|
|
||||||
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
|
||||||
}
|
|
||||||
lockButton
|
|
||||||
.padding(10)
|
|
||||||
.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
|
|
||||||
}
|
|
||||||
.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 ? 0 : -(height * factor - size + 30))
|
|
||||||
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
|
||||||
}
|
}
|
||||||
|
.fixedSize()
|
||||||
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .trailing)
|
||||||
}
|
}
|
||||||
|
.padding(10)
|
||||||
|
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
||||||
|
#else
|
||||||
|
if horizontalSizeClass == .regular {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
.padding(10)
|
||||||
|
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
||||||
|
} else {
|
||||||
|
GeometryReader { proxy in
|
||||||
|
HStack(alignment: .bottom, spacing: 10) {
|
||||||
|
newPenButton
|
||||||
|
.padding(.leading, 10)
|
||||||
|
.frame(height: height)
|
||||||
|
compactPenItemList
|
||||||
|
.fixedSize(horizontal: false, vertical: true)
|
||||||
|
HStack(spacing: 0) {
|
||||||
|
compactPenPropertyTool
|
||||||
|
Divider()
|
||||||
|
.padding(.vertical, 4)
|
||||||
|
.frame(height: size)
|
||||||
|
.foregroundStyle(Color.accentColor)
|
||||||
|
.padding(.leading, 8)
|
||||||
|
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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.clipped()
|
||||||
|
.background(alignment: .bottom) {
|
||||||
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.fill(.regularMaterial)
|
||||||
|
.frame(height: height)
|
||||||
|
}
|
||||||
|
.padding([.horizontal, .bottom], 10)
|
||||||
|
.frame(maxWidth: min(proxy.size.height, proxy.size.width), maxHeight: .infinity, alignment: .bottom)
|
||||||
|
.frame(maxWidth: .infinity)
|
||||||
|
}
|
||||||
|
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var penItemList: some View {
|
private var penItemList: some View {
|
||||||
ScrollViewReader { proxy in
|
VStack(alignment: .trailing, spacing: 0) {
|
||||||
ScrollView(.vertical, showsIndicators: false) {
|
ScrollViewReader { proxy in
|
||||||
LazyVStack(spacing: 0) {
|
ScrollView(.vertical, showsIndicators: false) {
|
||||||
ForEach(tool.pens) { pen in
|
LazyVStack(spacing: 5) {
|
||||||
penItem(pen)
|
ForEach(tool.pens) { pen in
|
||||||
.id(pen.id)
|
penItem(pen)
|
||||||
.scrollTransition { content, phase in
|
.id(pen.id)
|
||||||
content
|
.scrollTransition { content, phase in
|
||||||
.scaleEffect(phase.isIdentity ? 1 : 0.04, anchor: .trailing)
|
content
|
||||||
}
|
.scaleEffect(phase.isIdentity ? 1 : 0.04, anchor: .trailing)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
.id(refreshScrollId)
|
||||||
}
|
}
|
||||||
.padding(.vertical, 10)
|
.onReceive(tool.scrollPublisher) { id in
|
||||||
.id(refreshScrollId)
|
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
||||||
}
|
withAnimation {
|
||||||
.onReceive(tool.scrollPublisher) { id in
|
proxy.scrollTo(id)
|
||||||
DispatchQueue.main.asyncAfter(deadline: .now() + 0.5) {
|
}
|
||||||
withAnimation {
|
|
||||||
proxy.scrollTo(id)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
newPenButton
|
||||||
|
.padding(.vertical, 5)
|
||||||
|
.frame(width: width)
|
||||||
}
|
}
|
||||||
.frame(maxHeight: ((height * factor + 10) * 6) + 20)
|
.padding(.vertical, 3)
|
||||||
.fixedSize()
|
|
||||||
.background(alignment: .trailing) {
|
.background(alignment: .trailing) {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.fill(.regularMaterial)
|
.fill(.regularMaterial)
|
||||||
.frame(width: width * factor - 18)
|
.frame(width: width)
|
||||||
}
|
|
||||||
.clipShape(.rect(cornerRadii: .init(bottomTrailing: 8, topTrailing: 8)))
|
|
||||||
.overlay(alignment: .bottomLeading) {
|
|
||||||
newPenButton
|
|
||||||
.offset(x: 15, y: 10)
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var compactPenItemList: some View {
|
private var compactPenItemList: some View {
|
||||||
ScrollViewReader { proxy in
|
ScrollViewReader { proxy in
|
||||||
ScrollView(.horizontal, showsIndicators: false) {
|
ScrollView(.horizontal, showsIndicators: false) {
|
||||||
LazyHStack(spacing: 0) {
|
LazyHStack(spacing: 0) {
|
||||||
@@ -128,7 +157,7 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 5)
|
||||||
.id(refreshScrollId)
|
.id(refreshScrollId)
|
||||||
}
|
}
|
||||||
.onReceive(tool.scrollPublisher) { id in
|
.onReceive(tool.scrollPublisher) { id in
|
||||||
@@ -141,7 +170,7 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func penItem(_ pen: Pen) -> some View {
|
private func penItem(_ pen: Pen) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
penShadow(pen)
|
penShadow(pen)
|
||||||
if let tip = pen.style.icon.tip {
|
if let tip = pen.style.icon.tip {
|
||||||
@@ -153,15 +182,10 @@ struct PenDock: View {
|
|||||||
Image(pen.style.icon.base)
|
Image(pen.style.icon.base)
|
||||||
.resizable()
|
.resizable()
|
||||||
}
|
}
|
||||||
.frame(width: width * factor, height: height * factor)
|
.frame(width: width * 1.2, height: height * 0.9)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.contentShape(.rect(cornerRadii: .init(topLeading: 10, bottomLeading: 10)))
|
|
||||||
.onTapGesture {
|
|
||||||
if tool.selectedPen !== pen {
|
|
||||||
tool.selectPen(pen)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.padding(.leading, 10)
|
.padding(.leading, 10)
|
||||||
|
.contentShape(.rect)
|
||||||
.contextMenu(if: pen.strokeStyle != .eraser) {
|
.contextMenu(if: pen.strokeStyle != .eraser) {
|
||||||
ControlGroup {
|
ControlGroup {
|
||||||
Button {
|
Button {
|
||||||
@@ -201,7 +225,10 @@ struct PenDock: View {
|
|||||||
} preview: {
|
} preview: {
|
||||||
penPreview(pen)
|
penPreview(pen)
|
||||||
.drawingGroup()
|
.drawingGroup()
|
||||||
|
#if os(iOS)
|
||||||
.contentShape(.contextMenuPreview, .rect(cornerRadius: 10))
|
.contentShape(.contextMenuPreview, .rect(cornerRadius: 10))
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.onDrag(if: pen.strokeStyle != .eraser) {
|
.onDrag(if: pen.strokeStyle != .eraser) {
|
||||||
tool.draggedPen = pen
|
tool.draggedPen = pen
|
||||||
@@ -211,10 +238,16 @@ struct PenDock: View {
|
|||||||
.contentShape(.dragPreview, .rect(cornerRadius: 10))
|
.contentShape(.dragPreview, .rect(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
.onDrop(of: [.item], delegate: PenDropDelegate(id: pen.id, tool: tool, action: { refreshScrollId = UUID() }))
|
.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 {
|
private func compactPenItem(_ pen: Pen) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
compactPenShadow(pen)
|
compactPenShadow(pen)
|
||||||
if let tip = pen.style.compactIcon.tip {
|
if let tip = pen.style.compactIcon.tip {
|
||||||
@@ -226,7 +259,7 @@ struct PenDock: View {
|
|||||||
Image(pen.style.compactIcon.base)
|
Image(pen.style.compactIcon.base)
|
||||||
.resizable()
|
.resizable()
|
||||||
}
|
}
|
||||||
.frame(width: width * factor, height: height * factor)
|
.frame(width: width * 0.9, height: height * 1.2)
|
||||||
.padding(.top, 5)
|
.padding(.top, 5)
|
||||||
.contentShape(.rect(cornerRadii: .init(topLeading: 10, bottomLeading: 10)))
|
.contentShape(.rect(cornerRadii: .init(topLeading: 10, bottomLeading: 10)))
|
||||||
.onTapGesture {
|
.onTapGesture {
|
||||||
@@ -234,7 +267,7 @@ struct PenDock: View {
|
|||||||
tool.selectPen(pen)
|
tool.selectPen(pen)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.padding(.horizontal, 10)
|
.padding(.horizontal, 6)
|
||||||
.contextMenu(if: pen.strokeStyle != .eraser) {
|
.contextMenu(if: pen.strokeStyle != .eraser) {
|
||||||
ControlGroup {
|
ControlGroup {
|
||||||
Button {
|
Button {
|
||||||
@@ -272,23 +305,26 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
.controlGroupStyle(.menu)
|
.controlGroupStyle(.menu)
|
||||||
} preview: {
|
} preview: {
|
||||||
penPreview(pen)
|
compactPenPreview(pen)
|
||||||
.drawingGroup()
|
.drawingGroup()
|
||||||
|
#if os(iOS)
|
||||||
.contentShape(.contextMenuPreview, .rect(cornerRadius: 10))
|
.contentShape(.contextMenuPreview, .rect(cornerRadius: 10))
|
||||||
|
#else
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
.onDrag(if: pen.strokeStyle != .eraser) {
|
.onDrag(if: pen.strokeStyle != .eraser) {
|
||||||
tool.draggedPen = pen
|
tool.draggedPen = pen
|
||||||
return NSItemProvider(contentsOf: URL(string: pen.id)) ?? NSItemProvider()
|
return NSItemProvider(contentsOf: URL(string: pen.id)) ?? NSItemProvider()
|
||||||
} preview: {
|
} preview: {
|
||||||
penPreview(pen)
|
compactPenPreview(pen)
|
||||||
.contentShape(.dragPreview, .rect(cornerRadius: 10))
|
.contentShape(.dragPreview, .rect(cornerRadius: 10))
|
||||||
}
|
}
|
||||||
.onDrop(of: [.item], delegate: PenDropDelegate(id: pen.id, tool: tool, action: { refreshScrollId = UUID() }))
|
.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
|
@ViewBuilder
|
||||||
var penPropertyTool: some View {
|
private var penPropertyTool: some View {
|
||||||
if let pen = tool.selectedPen {
|
if let pen = tool.selectedPen {
|
||||||
VStack(spacing: 5) {
|
VStack(spacing: 5) {
|
||||||
if pen.strokeStyle == .marker {
|
if pen.strokeStyle == .marker {
|
||||||
@@ -296,37 +332,36 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
penThicknessPicker(pen)
|
penThicknessPicker(pen)
|
||||||
}
|
}
|
||||||
.padding(10)
|
.padding(.vertical, 5)
|
||||||
.frame(width: width * factor - 18)
|
.frame(width: width)
|
||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.fill(.regularMaterial)
|
.fill(.regularMaterial)
|
||||||
}
|
}
|
||||||
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
|
||||||
} else {
|
} else {
|
||||||
Color.clear
|
Color.clear
|
||||||
.frame(width: width * factor - 18, height: 50)
|
.frame(width: width, height: 50)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
var compactPenPropertyTool: some View {
|
private var compactPenPropertyTool: some View {
|
||||||
if let pen = tool.selectedPen {
|
if let pen = tool.selectedPen {
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 8) {
|
||||||
compactPenThicknessPicker(pen)
|
penThicknessPicker(pen)
|
||||||
.frame(width: width)
|
.frame(width: penPropertySize)
|
||||||
.rotationEffect(.degrees(-90))
|
.rotationEffect(.degrees(-90))
|
||||||
if pen.strokeStyle == .marker {
|
if pen.strokeStyle == .marker {
|
||||||
penColorPicker(pen)
|
penColorPicker(pen)
|
||||||
.frame(width: width)
|
.frame(width: penPropertySize)
|
||||||
.transition(.move(edge: .trailing).combined(with: .opacity))
|
.transition(.move(edge: .trailing).combined(with: .opacity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(height: height * factor - 18)
|
.frame(height: height)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func penColorPicker(_ pen: Pen) -> some View {
|
private func penColorPicker(_ pen: Pen) -> some View {
|
||||||
Button {
|
Button {
|
||||||
opensColorPicker = true
|
opensColorPicker = true
|
||||||
} label: {
|
} label: {
|
||||||
@@ -347,7 +382,8 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
.background(baseColor)
|
.background(baseColor)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.frame(height: horizontalSizeClass == .compact ? 30 : 25)
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
|
.frame(width: penPropertySize, height: penPropertySize)
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.stroke(Color.gray, lineWidth: 0.4)
|
.stroke(Color.gray, lineWidth: 0.4)
|
||||||
@@ -356,7 +392,9 @@ struct PenDock: View {
|
|||||||
.drawingGroup()
|
.drawingGroup()
|
||||||
}
|
}
|
||||||
.buttonStyle(.plain)
|
.buttonStyle(.plain)
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#endif
|
||||||
.popover(isPresented: $opensColorPicker) {
|
.popover(isPresented: $opensColorPicker) {
|
||||||
let color = Binding(
|
let color = Binding(
|
||||||
get: { pen.color },
|
get: { pen.color },
|
||||||
@@ -376,75 +414,56 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
func penThicknessPicker(_ pen: Pen) -> some View {
|
private func penThicknessPicker(_ pen: Pen) -> some View {
|
||||||
let minimum: CGFloat = pen.style.thickness.min
|
let minimum: CGFloat = pen.style.thickness.min
|
||||||
let maximum: CGFloat = pen.style.thickness.max
|
let maximum: CGFloat = pen.style.thickness.max
|
||||||
let start: CGFloat = 4
|
let start: CGFloat = 4
|
||||||
let end: CGFloat = 10
|
let end: CGFloat = 10
|
||||||
let selection = Binding(
|
let selection = Binding<CGFloat?>(
|
||||||
get: { pen.thickness },
|
get: { pen.thickness },
|
||||||
set: {
|
set: {
|
||||||
pen.thickness = $0
|
pen.thickness = $0 ?? .zero
|
||||||
tool.objectWillChange.send()
|
tool.objectWillChange.send()
|
||||||
}
|
}
|
||||||
)
|
)
|
||||||
Picker("", selection: selection) {
|
ScrollViewReader { proxy in
|
||||||
ForEach(pen.style.thicknessSteps, id: \.self) { step in
|
ScrollView(showsIndicators: false) {
|
||||||
let size = ((step - minimum) * (end - start) / (maximum - minimum)) + start - (0.5 / step)
|
LazyVStack(spacing: 0) {
|
||||||
Circle()
|
ForEach(pen.style.thicknessSteps, id: \.self) { step in
|
||||||
.fill(.black)
|
let size = ((step - minimum) * (end - start) / (maximum - minimum)) + start - (0.5 / step)
|
||||||
.frame(width: size, height: size)
|
Circle()
|
||||||
.frame(width: size + 2, height: size + 2)
|
.foregroundStyle(.primary)
|
||||||
|
.frame(width: size, height: size)
|
||||||
|
.frame(width: penPropertySize, height: penPropertySize)
|
||||||
|
.contentShape(.rect)
|
||||||
|
.id(step)
|
||||||
|
}
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
.frame(width: penPropertySize, height: penPropertySize)
|
||||||
.hoverEffect(.lift)
|
.background(.gray.quaternary)
|
||||||
.pickerStyle(.wheel)
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.frame(width: width * factor - 18, height: 35)
|
.scrollPosition(id: selection, anchor: .center)
|
||||||
.onChange(of: pen.thickness) { _, _ in
|
.scrollTargetLayout()
|
||||||
withPersistence(\.viewContext) { context in
|
.scrollTargetBehavior(.viewAligned)
|
||||||
try context.saveIfNeeded()
|
.scrollIndicators(.hidden)
|
||||||
|
.onAppear {
|
||||||
|
proxy.scrollTo(selection.wrappedValue)
|
||||||
|
}
|
||||||
|
.onChange(of: pen.thickness) { _, _ in
|
||||||
|
withPersistence(\.viewContext) { context in
|
||||||
|
try context.saveIfNeeded()
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
private var newPenButton: some View {
|
||||||
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(
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.hoverEffect(.lift)
|
|
||||||
.pickerStyle(.wheel)
|
|
||||||
.frame(width: 50, height: 30)
|
|
||||||
.onChange(of: pen.thickness) { _, _ in
|
|
||||||
withPersistence(\.viewContext) { context in
|
|
||||||
try context.saveIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
var newPenButton: some View {
|
|
||||||
Button {
|
Button {
|
||||||
createNewPen()
|
createNewPen()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "plus.circle.fill")
|
Image(systemName: "plus.circle.fill")
|
||||||
.font(.title2)
|
.font(.headline)
|
||||||
.padding(1)
|
.padding(1)
|
||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.background {
|
.background {
|
||||||
@@ -453,10 +472,14 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func penPreview(_ pen: Pen) -> some View {
|
private func penPreview(_ pen: Pen) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
if let tip = pen.style.icon.tip {
|
if let tip = pen.style.icon.tip {
|
||||||
Image(tip)
|
Image(tip)
|
||||||
@@ -467,12 +490,28 @@ struct PenDock: View {
|
|||||||
Image(pen.style.icon.base)
|
Image(pen.style.icon.base)
|
||||||
.resizable()
|
.resizable()
|
||||||
}
|
}
|
||||||
.frame(width: width * factor, height: height * factor)
|
.frame(width: width * 1.2, height: height * 0.9)
|
||||||
.padding(.vertical, 5)
|
.padding(.vertical, 5)
|
||||||
.padding(.leading, 10)
|
.padding(.leading, 10)
|
||||||
}
|
}
|
||||||
|
|
||||||
func penShadow(_ pen: Pen) -> some View {
|
private 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 * 0.9, height: height * 1.2)
|
||||||
|
.padding(.top, 5)
|
||||||
|
.padding(.horizontal, 5)
|
||||||
|
}
|
||||||
|
|
||||||
|
private func penShadow(_ pen: Pen) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Group {
|
Group {
|
||||||
if let tip = pen.style.icon.tip {
|
if let tip = pen.style.icon.tip {
|
||||||
@@ -496,7 +535,7 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
func compactPenShadow(_ pen: Pen) -> some View {
|
private func compactPenShadow(_ pen: Pen) -> some View {
|
||||||
ZStack {
|
ZStack {
|
||||||
Group {
|
Group {
|
||||||
if let tip = pen.style.compactIcon.tip {
|
if let tip = pen.style.compactIcon.tip {
|
||||||
@@ -520,23 +559,7 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var lockButton: some View {
|
private func createNewPen() {
|
||||||
Button {
|
|
||||||
withAnimation {
|
|
||||||
canvas.locksCanvas.toggle()
|
|
||||||
}
|
|
||||||
} label: {
|
|
||||||
Image(systemName: canvas.locksCanvas ? "lock.fill" : "lock.open.fill")
|
|
||||||
.contentShape(.circle)
|
|
||||||
.frame(width: size, height: size)
|
|
||||||
.background(.regularMaterial)
|
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
|
||||||
}
|
|
||||||
.hoverEffect(.lift)
|
|
||||||
.contentTransition(.symbolEffect(.replace))
|
|
||||||
}
|
|
||||||
|
|
||||||
func createNewPen() {
|
|
||||||
let pen = PenObject.createObject(\.viewContext, penStyle: .marker)
|
let pen = PenObject.createObject(\.viewContext, penStyle: .marker)
|
||||||
var selectedPen = tool.selectedPen
|
var selectedPen = tool.selectedPen
|
||||||
selectedPen = (selectedPen?.strokeStyle == .marker ? (selectedPen ?? tool.pens.last) : tool.pens.last)
|
selectedPen = (selectedPen?.strokeStyle == .marker ? (selectedPen ?? tool.pens.last) : tool.pens.last)
|
||||||
|
|||||||
@@ -9,9 +9,15 @@ import SwiftUI
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct PenDropDelegate: DropDelegate {
|
struct PenDropDelegate: DropDelegate {
|
||||||
let id: String
|
private let id: String
|
||||||
@ObservedObject var tool: Tool
|
@ObservedObject private var tool: Tool
|
||||||
let action: () -> Void
|
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 {
|
func performDrop(info: DropInfo) -> Bool {
|
||||||
tool.draggedPen = nil
|
tool.draggedPen = nil
|
||||||
|
|||||||
@@ -0,0 +1,208 @@
|
|||||||
|
//
|
||||||
|
// PhotoDock.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/11/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
import PhotosUI
|
||||||
|
|
||||||
|
struct PhotoDock: View {
|
||||||
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
|
private let size: CGFloat = 40
|
||||||
|
|
||||||
|
@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 {
|
||||||
|
#if os(macOS)
|
||||||
|
photoOption
|
||||||
|
#else
|
||||||
|
if horizontalSizeClass == .regular {
|
||||||
|
photoOption
|
||||||
|
} else {
|
||||||
|
compactPhotoOption
|
||||||
|
}
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
.foregroundStyle(Color.accentColor)
|
||||||
|
#if os(iOS)
|
||||||
|
.fullScreenCover(isPresented: $opensCamera) {
|
||||||
|
let image: Binding<UIImage?> = 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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))
|
||||||
|
}
|
||||||
|
|
||||||
|
private 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
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -5,13 +5,13 @@
|
|||||||
// Created by Dscyre Scotti on 6/16/24.
|
// Created by Dscyre Scotti on 6/16/24.
|
||||||
//
|
//
|
||||||
|
|
||||||
import UIKit
|
import SwiftUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct PhotoItem: Identifiable, Equatable {
|
struct PhotoItem: Identifiable, Equatable {
|
||||||
var id: URL
|
var id: URL
|
||||||
let image: UIImage
|
let image: Platform.Image
|
||||||
let previewImage: UIImage
|
let previewImage: Platform.Image
|
||||||
let dimension: CGSize
|
let dimension: CGSize
|
||||||
let bookmark: Data
|
let bookmark: Data
|
||||||
|
|
||||||
|
|||||||
@@ -8,13 +8,18 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
struct PhotoPreview: View {
|
struct PhotoPreview: View {
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
let photoItem: PhotoItem
|
private let photoItem: PhotoItem
|
||||||
@ObservedObject var tool: Tool
|
@ObservedObject private var tool: Tool
|
||||||
|
|
||||||
|
init(photoItem: PhotoItem, tool: Tool) {
|
||||||
|
self.photoItem = photoItem
|
||||||
|
self.tool = tool
|
||||||
|
}
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
Image(uiImage: photoItem.previewImage)
|
Image(image: photoItem.previewImage)
|
||||||
.resizable()
|
.resizable()
|
||||||
.scaledToFit()
|
.scaledToFit()
|
||||||
.frame(width: horizontalSizeClass == .compact ? 80 : nil, height: horizontalSizeClass == .compact ? nil : 100)
|
.frame(width: horizontalSizeClass == .compact ? 80 : nil, height: horizontalSizeClass == .compact ? nil : 100)
|
||||||
@@ -40,7 +45,9 @@ struct PhotoPreview: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.foregroundStyle(.red)
|
.foregroundStyle(.red)
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#endif
|
||||||
.offset(x: -12, y: -12)
|
.offset(x: -12, y: -12)
|
||||||
}
|
}
|
||||||
.padding(10)
|
.padding(10)
|
||||||
|
|||||||
@@ -9,22 +9,25 @@ import SwiftUI
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Toolbar: View {
|
struct Toolbar: View {
|
||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) private var dismiss
|
||||||
@Environment(\.horizontalSizeClass) var horizontalSizeClass
|
@Environment(\.horizontalSizeClass) private var horizontalSizeClass
|
||||||
|
|
||||||
@ObservedObject var tool: Tool
|
#if os(macOS)
|
||||||
@ObservedObject var canvas: Canvas
|
@EnvironmentObject private var application: Application
|
||||||
@ObservedObject var history: History
|
#endif
|
||||||
|
|
||||||
@State var title: String
|
@ObservedObject private var tool: Tool
|
||||||
@State var memo: MemoObject
|
@ObservedObject private var canvas: Canvas
|
||||||
|
@ObservedObject private var history: History
|
||||||
|
|
||||||
@FocusState var textFieldState: Bool
|
@State private var title: String
|
||||||
|
|
||||||
let size: CGFloat
|
@FocusState private var textFieldState: Bool
|
||||||
|
|
||||||
init(size: CGFloat, memo: MemoObject, tool: Tool, canvas: Canvas, history: History) {
|
private let size: CGFloat = 40
|
||||||
self.size = size
|
private let memo: MemoObject
|
||||||
|
|
||||||
|
init(memo: MemoObject, tool: Tool, canvas: Canvas, history: History) {
|
||||||
self.memo = memo
|
self.memo = memo
|
||||||
self.tool = tool
|
self.tool = tool
|
||||||
self.canvas = canvas
|
self.canvas = canvas
|
||||||
@@ -35,43 +38,49 @@ struct Toolbar: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
HStack(spacing: 5) {
|
HStack(spacing: 5) {
|
||||||
HStack(spacing: 5) {
|
HStack(spacing: 5) {
|
||||||
if !canvas.locksCanvas {
|
closeButton
|
||||||
closeButton
|
titleField
|
||||||
titleField
|
.foregroundStyle(Color.primary)
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
if !canvas.locksCanvas, horizontalSizeClass == .regular {
|
#if os(macOS)
|
||||||
ElementToolbar(size: size, tool: tool, canvas: canvas)
|
ElementToolbar(tool: tool, canvas: canvas)
|
||||||
|
#else
|
||||||
|
if horizontalSizeClass == .regular {
|
||||||
|
ElementToolbar(tool: tool, canvas: canvas)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
HStack(spacing: 5) {
|
HStack(spacing: 5) {
|
||||||
if !canvas.locksCanvas {
|
gridModeControl
|
||||||
gridModeControl
|
historyControl
|
||||||
historyControl
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .trailing)
|
.frame(maxWidth: .infinity, alignment: .trailing)
|
||||||
}
|
}
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
|
.foregroundStyle(Color.accentColor)
|
||||||
}
|
}
|
||||||
|
|
||||||
var closeButton: some View {
|
private var closeButton: some View {
|
||||||
Button {
|
Button {
|
||||||
closeMemo()
|
closeMemo()
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "xmark")
|
Image(systemName: "xmark")
|
||||||
.contentShape(.circle)
|
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.background(.regularMaterial)
|
.background(.regularMaterial)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
.disabled(title.isEmpty)
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
.disabled(textFieldState)
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
.transition(.move(edge: .top).combined(with: .blurReplace))
|
.transition(.move(edge: .top).combined(with: .blurReplace))
|
||||||
}
|
}
|
||||||
|
|
||||||
var titleField: some View {
|
private var titleField: some View {
|
||||||
TextField("", text: $title)
|
TextField("", text: $title)
|
||||||
.focused($textFieldState)
|
.focused($textFieldState)
|
||||||
.textFieldStyle(.plain)
|
.textFieldStyle(.plain)
|
||||||
@@ -93,35 +102,60 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.transition(.move(edge: .top).combined(with: .blurReplace))
|
.transition(.move(edge: .top).combined(with: .blurReplace))
|
||||||
|
.onSubmit(of: .text) {
|
||||||
|
textFieldState = false
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var historyControl: some View {
|
private var historyControl: some View {
|
||||||
HStack {
|
HStack(spacing: 0) {
|
||||||
Button {
|
Button {
|
||||||
history.historyPublisher.send(.undo)
|
history.historyPublisher.send(.undo)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.uturn.backward.circle")
|
Image(systemName: "arrow.uturn.backward.circle")
|
||||||
.contentShape(.circle)
|
.frame(width: size, height: size)
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
.disabled(history.undoDisabled)
|
.disabled(history.undoDisabled)
|
||||||
Button {
|
Button {
|
||||||
history.historyPublisher.send(.redo)
|
history.historyPublisher.send(.redo)
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "arrow.uturn.forward.circle")
|
Image(systemName: "arrow.uturn.forward.circle")
|
||||||
.contentShape(.circle)
|
.frame(width: size, height: size)
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
|
#endif
|
||||||
.disabled(history.redoDisabled)
|
.disabled(history.redoDisabled)
|
||||||
}
|
}
|
||||||
.frame(width: size * 2, height: size)
|
|
||||||
.background(.regularMaterial)
|
.background(.regularMaterial)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.disabled(textFieldState)
|
|
||||||
.transition(.move(edge: .top).combined(with: .blurReplace))
|
.transition(.move(edge: .top).combined(with: .blurReplace))
|
||||||
}
|
}
|
||||||
|
|
||||||
var gridModeControl: some View {
|
private var gridModeControl: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
Button {
|
||||||
|
canvas.toggleGridMode()
|
||||||
|
} 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 {
|
Menu {
|
||||||
ForEach(GridMode.all, id: \.self) { mode in
|
ForEach(GridMode.all, id: \.self) { mode in
|
||||||
Button {
|
Button {
|
||||||
@@ -137,19 +171,24 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: canvas.gridMode.icon)
|
Image(systemName: canvas.gridMode.icon)
|
||||||
.contentShape(.circle)
|
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.background(.regularMaterial)
|
.background(.regularMaterial)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
.contentTransition(.symbolEffect(.replace))
|
.contentTransition(.symbolEffect(.replace))
|
||||||
.transition(.move(edge: .top).combined(with: .blurReplace))
|
.transition(.move(edge: .top).combined(with: .blurReplace))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeMemo() {
|
private func closeMemo() {
|
||||||
canvas.save(for: memo) {
|
canvas.save(for: memo) {
|
||||||
|
#if os(macOS)
|
||||||
|
application.closeMemo()
|
||||||
|
#else
|
||||||
dismiss()
|
dismiss()
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -0,0 +1,5 @@
|
|||||||
|
<?xml version="1.0" encoding="UTF-8"?>
|
||||||
|
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
|
||||||
|
<plist version="1.0">
|
||||||
|
<dict/>
|
||||||
|
</plist>
|
||||||
@@ -101,3 +101,8 @@ func withPersistenceSync(_ keypath: KeyPath<Persistence, NSManagedObjectContext>
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
func withPersistenceContext<T>(_ keypath: KeyPath<Persistence, NSManagedObjectContext>, _ task: @escaping (NSManagedObjectContext) throws -> T) throws -> T {
|
||||||
|
let context = Persistence.shared[keyPath: keypath]
|
||||||
|
return try task(context)
|
||||||
|
}
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import CoreData
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@objc(PenObject)
|
@objc(PenObject)
|
||||||
class PenObject: NSManagedObject {
|
final class PenObject: NSManagedObject {
|
||||||
@NSManaged var color: [CGFloat]
|
@NSManaged var color: [CGFloat]
|
||||||
@NSManaged var style: Int16
|
@NSManaged var style: Int16
|
||||||
@NSManaged var thickness: CGFloat
|
@NSManaged var thickness: CGFloat
|
||||||
@@ -20,7 +20,7 @@ class PenObject: NSManagedObject {
|
|||||||
|
|
||||||
extension PenObject {
|
extension PenObject {
|
||||||
static func createObject(_ keyPath: KeyPath<Persistence, NSManagedObjectContext>, penStyle: any PenStyle) -> PenObject {
|
static func createObject(_ keyPath: KeyPath<Persistence, NSManagedObjectContext>, penStyle: any PenStyle) -> PenObject {
|
||||||
let object = PenObject(context: Persistence.shared[keyPath: keyPath])
|
let object = PenObject(keyPath)
|
||||||
object.color = penStyle.color
|
object.color = penStyle.color
|
||||||
object.style = penStyle.strokeStyle.rawValue
|
object.style = penStyle.strokeStyle.rawValue
|
||||||
object.isSelected = false
|
object.isSelected = false
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import CoreData
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@objc(PhotoObject)
|
@objc(PhotoObject)
|
||||||
class PhotoObject: NSManagedObject {
|
final class PhotoObject: NSManagedObject {
|
||||||
@NSManaged var width: CGFloat
|
@NSManaged var width: CGFloat
|
||||||
@NSManaged var originY: CGFloat
|
@NSManaged var originY: CGFloat
|
||||||
@NSManaged var originX: CGFloat
|
@NSManaged var originX: CGFloat
|
||||||
|
|||||||
@@ -9,7 +9,7 @@ import CoreData
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@objc(ToolObject)
|
@objc(ToolObject)
|
||||||
class ToolObject: NSManagedObject {
|
final class ToolObject: NSManagedObject {
|
||||||
@NSManaged var selection: Int16
|
@NSManaged var selection: Int16
|
||||||
@NSManaged var pens: NSMutableSet
|
@NSManaged var pens: NSMutableSet
|
||||||
@NSManaged var memo: MemoObject?
|
@NSManaged var memo: MemoObject?
|
||||||
|
|||||||
@@ -1,6 +1,33 @@
|
|||||||
{
|
{
|
||||||
"colors" : [
|
"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"
|
"idiom" : "universal"
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,61 @@
|
|||||||
|
//
|
||||||
|
// EditCommands.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
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 {
|
||||||
|
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)
|
||||||
|
}
|
||||||
|
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])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
CommandGroup(replacing: .pasteboard) { }
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
@@ -0,0 +1,26 @@
|
|||||||
|
//
|
||||||
|
// FileCommands.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
struct FileCommands: Commands {
|
||||||
|
@Environment(\.shortcut) private var shortcut
|
||||||
|
@FocusedValue(\.activeSceneKey) private var appScene
|
||||||
|
|
||||||
|
var body: some Commands {
|
||||||
|
CommandGroup(replacing: .newItem) {
|
||||||
|
if appScene == .memos {
|
||||||
|
Button {
|
||||||
|
shortcut.trigger(.newMemo)
|
||||||
|
} label: {
|
||||||
|
Text("New Memo")
|
||||||
|
}
|
||||||
|
.keyboardShortcut("n", modifiers: [.command])
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,54 @@
|
|||||||
|
//
|
||||||
|
// ViewCommands.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
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) {
|
||||||
|
switch appScene {
|
||||||
|
case .memos, .trash:
|
||||||
|
Button {
|
||||||
|
application.activateSearchBar()
|
||||||
|
} label: {
|
||||||
|
Text("Find Memo")
|
||||||
|
}
|
||||||
|
.keyboardShortcut("f", modifiers: [.command])
|
||||||
|
Button {
|
||||||
|
application.toggleSidebar()
|
||||||
|
} label: {
|
||||||
|
switch application.sidebarVisibility {
|
||||||
|
case .shown:
|
||||||
|
Text("Hide Sidebar")
|
||||||
|
case .hidden:
|
||||||
|
Text("Show Sidebar")
|
||||||
|
}
|
||||||
|
}
|
||||||
|
.keyboardShortcut("o", modifiers: [.command])
|
||||||
|
case .memo:
|
||||||
|
Button {
|
||||||
|
canvas?.toggleGridMode()
|
||||||
|
} label: {
|
||||||
|
Text("Change Grid Layout")
|
||||||
|
}
|
||||||
|
.keyboardShortcut("g", modifiers: [.option])
|
||||||
|
default:
|
||||||
|
EmptyView()
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,25 @@
|
|||||||
|
//
|
||||||
|
// Shortcut.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Combine
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
final class Shortcut {
|
||||||
|
static let shared: Shortcut = .init()
|
||||||
|
|
||||||
|
private let _publisher = PassthroughSubject<Shortcuts, Never>()
|
||||||
|
|
||||||
|
lazy var publisher: AnyPublisher<Shortcuts, Never> = {
|
||||||
|
_publisher.eraseToAnyPublisher()
|
||||||
|
}()
|
||||||
|
|
||||||
|
private init() { }
|
||||||
|
|
||||||
|
func trigger(_ shortcut: Shortcuts) {
|
||||||
|
_publisher.send(shortcut)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -0,0 +1,18 @@
|
|||||||
|
//
|
||||||
|
// ShortcutKey.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private struct ShortcutKey: EnvironmentKey {
|
||||||
|
static var defaultValue: Shortcut = .shared
|
||||||
|
}
|
||||||
|
|
||||||
|
extension EnvironmentValues {
|
||||||
|
var shortcut: Shortcut {
|
||||||
|
get { self[ShortcutKey.self] }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -0,0 +1,19 @@
|
|||||||
|
//
|
||||||
|
// ActiveSceneKey.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/12/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import SwiftUI
|
||||||
|
|
||||||
|
private struct ActiveSceneKey: FocusedValueKey {
|
||||||
|
typealias Value = AppScene
|
||||||
|
}
|
||||||
|
|
||||||
|
extension FocusedValues {
|
||||||
|
var activeSceneKey: AppScene? {
|
||||||
|
get { self[ActiveSceneKey.self] }
|
||||||
|
set { self[ActiveSceneKey.self] = newValue }
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -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
|
||||||
|
}
|
||||||
@@ -8,8 +8,16 @@
|
|||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
|
||||||
extension Color {
|
extension Color {
|
||||||
|
init(color: Platform.Color) {
|
||||||
|
#if os(macOS)
|
||||||
|
self = Color(nsColor: color)
|
||||||
|
#else
|
||||||
|
self = Color(uiColor: color)
|
||||||
|
#endif
|
||||||
|
}
|
||||||
|
|
||||||
var components: [CGFloat] {
|
var components: [CGFloat] {
|
||||||
let color = UIColor(self)
|
let color = Platform.Color(self)
|
||||||
return color.components
|
return color.components
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -20,6 +28,16 @@ extension Color {
|
|||||||
|
|
||||||
extension Color {
|
extension Color {
|
||||||
var hsba: (hue: Double, saturation: Double, brightness: Double, alpha: Double) {
|
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)
|
let uiColor = UIColor(self)
|
||||||
var hue: CGFloat = 0
|
var hue: CGFloat = 0
|
||||||
var saturation: CGFloat = 0
|
var saturation: CGFloat = 0
|
||||||
@@ -27,13 +45,21 @@ extension Color {
|
|||||||
var alpha: CGFloat = 0
|
var alpha: CGFloat = 0
|
||||||
uiColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
|
uiColor.getHue(&hue, saturation: &saturation, brightness: &brightness, alpha: &alpha)
|
||||||
return (hue, saturation, brightness, alpha)
|
return (hue, saturation, brightness, alpha)
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
extension UIColor {
|
extension Platform.Color {
|
||||||
var components: [CGFloat] {
|
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 uiColor: UIColor = self
|
||||||
let ciColor: CIColor = .init(color: uiColor)
|
let ciColor: CIColor = .init(color: uiColor)
|
||||||
return [ciColor.red, ciColor.green, ciColor.blue, ciColor.alpha]
|
return [ciColor.red, ciColor.green, ciColor.blue, ciColor.alpha]
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -0,0 +1,89 @@
|
|||||||
|
//
|
||||||
|
// Image++.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 6/15/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
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(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? {
|
||||||
|
switch imageOrientation {
|
||||||
|
case .up:
|
||||||
|
return self
|
||||||
|
default:
|
||||||
|
UIGraphicsBeginImageContextWithOptions(size, false, scale)
|
||||||
|
draw(in: CGRect(origin: .zero, size: size))
|
||||||
|
let result = UIGraphicsGetImageFromCurrentImageContext()
|
||||||
|
UIGraphicsEndImageContext()
|
||||||
|
return result
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
#endif
|
||||||
@@ -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
|
||||||
|
}
|
||||||
|
|
||||||
@@ -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 = NSSyncScrollView
|
||||||
|
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
|
||||||
|
}
|
||||||
@@ -0,0 +1,13 @@
|
|||||||
|
//
|
||||||
|
// SidebarVisibility.swift
|
||||||
|
// Memola
|
||||||
|
//
|
||||||
|
// Created by Dscyre Scotti on 7/14/24.
|
||||||
|
//
|
||||||
|
|
||||||
|
import Foundation
|
||||||
|
|
||||||
|
enum SidebarVisibility {
|
||||||
|
case shown
|
||||||
|
case hidden
|
||||||
|
}
|
||||||
Reference in New Issue
Block a user