diff --git a/Memola.xcodeproj/project.pbxproj b/Memola.xcodeproj/project.pbxproj index 922f77a..f0f51e5 100644 --- a/Memola.xcodeproj/project.pbxproj +++ b/Memola.xcodeproj/project.pbxproj @@ -83,6 +83,7 @@ ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.swift */; }; ECA738FC2BE61C5200A4542E /* Persistence.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738FB2BE61C5200A4542E /* Persistence.swift */; }; ECA739082BE623F300A4542E /* PenDock.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA739072BE623F300A4542E /* PenDock.swift */; }; + ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */; }; ECD12A862C19EE3900B96E12 /* ElementObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A852C19EE3900B96E12 /* ElementObject.swift */; }; ECD12A8A2C19EFB000B96E12 /* Element.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A892C19EFB000B96E12 /* Element.swift */; }; ECD12A8C2C1AEAA900B96E12 /* PhotoObject.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */; }; @@ -181,6 +182,7 @@ ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.swift"; sourceTree = ""; }; ECA738FB2BE61C5200A4542E /* Persistence.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Persistence.swift; sourceTree = ""; }; ECA739072BE623F300A4542E /* PenDock.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PenDock.swift; sourceTree = ""; }; + ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoPreview.swift; sourceTree = ""; }; ECD12A852C19EE3900B96E12 /* ElementObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ElementObject.swift; sourceTree = ""; }; ECD12A892C19EFB000B96E12 /* Element.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Element.swift; sourceTree = ""; }; ECD12A8B2C1AEAA900B96E12 /* PhotoObject.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = PhotoObject.swift; sourceTree = ""; }; @@ -389,6 +391,7 @@ ECA7387B2BE5EF3500A4542E /* Memo */ = { isa = PBXGroup; children = ( + ECBE52942C1D58F5006BDB3D /* PhotoPreview */, EC1B783B2BFA0AAC005A34E2 /* Toolbar */, EC5050082BF65D0500B4D86E /* Memo */, EC5050052BF65CCD00B4D86E /* PenDock */, @@ -665,6 +668,14 @@ path = Core; sourceTree = ""; }; + ECBE52942C1D58F5006BDB3D /* PhotoPreview */ = { + isa = PBXGroup; + children = ( + ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */, + ); + path = PhotoPreview; + sourceTree = ""; + }; ECD12A872C19EF8700B96E12 /* Elements */ = { isa = PBXGroup; children = ( @@ -891,6 +902,7 @@ EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */, ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */, ECA738C12BE60E5300A4542E /* PenStyle.swift in Sources */, + ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */, ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */, EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */, EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */, diff --git a/Memola/Canvas/Tool/Core/Tool.swift b/Memola/Canvas/Tool/Core/Tool.swift index 8edaa92..958afe8 100644 --- a/Memola/Canvas/Tool/Core/Tool.swift +++ b/Memola/Canvas/Tool/Core/Tool.swift @@ -14,8 +14,12 @@ public class Tool: NSObject, ObservableObject { let object: ToolObject @Published var pens: [Pen] = [] + + // MARK: - Pen @Published var selectedPen: Pen? @Published var draggedPen: Pen? + // MARK: - Photo + @Published var selectedImage: UIImage? @Published var selection: ToolSelection = .none diff --git a/Memola/Features/Memo/Memo/MemoView.swift b/Memola/Features/Memo/Memo/MemoView.swift index 1d6ed58..c20cb2b 100644 --- a/Memola/Features/Memo/Memo/MemoView.swift +++ b/Memola/Features/Memo/Memo/MemoView.swift @@ -29,10 +29,18 @@ struct MemoView: View { var body: some View { CanvasView(tool: tool, canvas: canvas, history: history) .ignoresSafeArea() - .overlay(alignment: .trailing) { - if tool.selection == .pen { + .overlay(alignment: .bottomTrailing) { + switch tool.selection { + case .pen: PenDock(tool: tool, canvas: canvas) .transition(.move(edge: .trailing)) + case .photo: + if let image = tool.selectedImage { + PhotoPreview(image: image, tool: tool) + .transition(.move(edge: .trailing)) + } + default: + EmptyView() } } .overlay(alignment: .bottomLeading) { diff --git a/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift new file mode 100644 index 0000000..837c331 --- /dev/null +++ b/Memola/Features/Memo/PhotoPreview/PhotoPreview.swift @@ -0,0 +1,48 @@ +// +// PhotoPreview.swift +// Memola +// +// Created by Dscyre Scotti on 6/15/24. +// + +import SwiftUI + +struct PhotoPreview: View { + let image: UIImage + @ObservedObject var tool: Tool + + var body: some View { + Image(uiImage: image) + .resizable() + .scaledToFill() + .frame(width: 100, height: 100) + .cornerRadius(5) + .overlay { + RoundedRectangle(cornerRadius: 5) + .stroke(Color.gray, lineWidth: 0.2) + } + .padding(10) + .background(.regularMaterial) + .cornerRadius(5) + .overlay(alignment: .topLeading) { + Button { + withAnimation { + tool.selectedImage = nil + } + } label: { + Image(systemName: "xmark.circle.fill") + .font(.title2) + .padding(1) + .contentShape(.circle) + .background { + Circle() + .fill(.white) + } + } + .foregroundStyle(.red) + .hoverEffect(.lift) + .offset(x: -12, y: -12) + } + .padding(10) + } +} diff --git a/Memola/Features/Memo/Toolbar/Toolbar.swift b/Memola/Features/Memo/Toolbar/Toolbar.swift index baae7f1..446096c 100644 --- a/Memola/Features/Memo/Toolbar/Toolbar.swift +++ b/Memola/Features/Memo/Toolbar/Toolbar.swift @@ -6,6 +6,7 @@ // import SwiftUI +import PhotosUI import Foundation struct Toolbar: View { @@ -15,8 +16,10 @@ struct Toolbar: View { @ObservedObject var canvas: Canvas @ObservedObject var history: History - @State var memo: MemoObject @State var title: String + @State var memo: MemoObject + @State var photoItem: PhotosPickerItem? + @FocusState var textFieldState: Bool let size: CGFloat @@ -50,6 +53,19 @@ struct Toolbar: View { } .font(.subheadline) .padding(10) + .onChange(of: photoItem) { oldValue, newValue in + if newValue != nil { + Task { + let data = try? await newValue?.loadTransferable(type: Data.self) + if let data { + withAnimation { + tool.selectedImage = UIImage(data: data) + } + } + photoItem = nil + } + } + } } var closeButton: some View { @@ -105,22 +121,52 @@ struct Toolbar: View { .clipShape(.rect(cornerRadius: 8)) } .hoverEffect(.lift) - Button { - withAnimation { - tool.selection = tool.selection == .photo ? .none : .photo + HStack(spacing: 0) { + Button { + withAnimation { + tool.selection = tool.selection == .photo ? .none : .photo + } + } label: { + Image(systemName: "photo") + .contentShape(.circle) + .frame(width: size, height: size) + .background(tool.selection == .photo ? Color.accentColor : Color.clear) + .foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor) + .clipShape(.rect(cornerRadius: 8)) + } + .hoverEffect(.lift) + if tool.selection == .photo { + HStack(spacing: 0) { + Button { + + } label: { + Image(systemName: "camera.fill") + .contentShape(.circle) + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + } + .hoverEffect(.lift) + PhotosPicker(selection: $photoItem, matching: .images) { + Image(systemName: "photo.fill.on.rectangle.fill") + .contentShape(.circle) + .frame(width: size, height: size) + .clipShape(.rect(cornerRadius: 8)) + } + .hoverEffect(.lift) + } + } + } + .background { + if tool.selection == .photo { + RoundedRectangle(cornerRadius: 8) + .fill(Color.white.tertiary) } - } label: { - Image(systemName: "photo") - .contentShape(.circle) - .frame(width: size, height: size) - .background(tool.selection == .photo ? Color.accentColor : Color.clear) - .foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor) - .clipShape(.rect(cornerRadius: 8)) } - .hoverEffect(.lift) } - .background(.regularMaterial) - .clipShape(.rect(cornerRadius: 8)) + .background { + RoundedRectangle(cornerRadius: 8) + .fill(.regularMaterial) + } } var historyControl: some View {