mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-07-12 15:52:41 +02:00
feat: add photo picker to select photo from library
This commit is contained in:
@@ -83,6 +83,7 @@
|
|||||||
ECA738F62BE612B700A4542E /* MTLDevice++.swift in Sources */ = {isa = PBXBuildFile; fileRef = ECA738F52BE612B700A4542E /* MTLDevice++.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 */; };
|
||||||
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 */; };
|
||||||
@@ -181,6 +182,7 @@
|
|||||||
ECA738F52BE612B700A4542E /* MTLDevice++.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "MTLDevice++.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>"; };
|
||||||
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>"; };
|
||||||
@@ -389,6 +391,7 @@
|
|||||||
ECA7387B2BE5EF3500A4542E /* Memo */ = {
|
ECA7387B2BE5EF3500A4542E /* Memo */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
|
ECBE52942C1D58F5006BDB3D /* PhotoPreview */,
|
||||||
EC1B783B2BFA0AAC005A34E2 /* Toolbar */,
|
EC1B783B2BFA0AAC005A34E2 /* Toolbar */,
|
||||||
EC5050082BF65D0500B4D86E /* Memo */,
|
EC5050082BF65D0500B4D86E /* Memo */,
|
||||||
EC5050052BF65CCD00B4D86E /* PenDock */,
|
EC5050052BF65CCD00B4D86E /* PenDock */,
|
||||||
@@ -665,6 +668,14 @@
|
|||||||
path = Core;
|
path = Core;
|
||||||
sourceTree = "<group>";
|
sourceTree = "<group>";
|
||||||
};
|
};
|
||||||
|
ECBE52942C1D58F5006BDB3D /* PhotoPreview */ = {
|
||||||
|
isa = PBXGroup;
|
||||||
|
children = (
|
||||||
|
ECBE52952C1D5900006BDB3D /* PhotoPreview.swift */,
|
||||||
|
);
|
||||||
|
path = PhotoPreview;
|
||||||
|
sourceTree = "<group>";
|
||||||
|
};
|
||||||
ECD12A872C19EF8700B96E12 /* Elements */ = {
|
ECD12A872C19EF8700B96E12 /* Elements */ = {
|
||||||
isa = PBXGroup;
|
isa = PBXGroup;
|
||||||
children = (
|
children = (
|
||||||
@@ -891,6 +902,7 @@
|
|||||||
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */,
|
EC5050072BF65CED00B4D86E /* PenDropDelegate.swift in Sources */,
|
||||||
ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */,
|
ECA738A32BE6020A00A4542E /* CGFloat++.swift in Sources */,
|
||||||
ECA738C12BE60E5300A4542E /* PenStyle.swift in Sources */,
|
ECA738C12BE60E5300A4542E /* PenStyle.swift in Sources */,
|
||||||
|
ECBE52962C1D5900006BDB3D /* PhotoPreview.swift in Sources */,
|
||||||
ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */,
|
ECA738DE2BE610A000A4542E /* ViewPortRenderPass.swift in Sources */,
|
||||||
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */,
|
EC7F6BEC2BE5E6E300A34A7B /* MemolaApp.swift in Sources */,
|
||||||
EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */,
|
EC2BEBF82C0F601A005DB0AF /* Node.swift in Sources */,
|
||||||
|
|||||||
@@ -14,8 +14,12 @@ public class Tool: NSObject, ObservableObject {
|
|||||||
let object: ToolObject
|
let object: ToolObject
|
||||||
|
|
||||||
@Published var pens: [Pen] = []
|
@Published var pens: [Pen] = []
|
||||||
|
|
||||||
|
// MARK: - Pen
|
||||||
@Published var selectedPen: Pen?
|
@Published var selectedPen: Pen?
|
||||||
@Published var draggedPen: Pen?
|
@Published var draggedPen: Pen?
|
||||||
|
// MARK: - Photo
|
||||||
|
@Published var selectedImage: UIImage?
|
||||||
|
|
||||||
@Published var selection: ToolSelection = .none
|
@Published var selection: ToolSelection = .none
|
||||||
|
|
||||||
|
|||||||
@@ -29,10 +29,18 @@ struct MemoView: View {
|
|||||||
var body: some View {
|
var body: some View {
|
||||||
CanvasView(tool: tool, canvas: canvas, history: history)
|
CanvasView(tool: tool, canvas: canvas, history: history)
|
||||||
.ignoresSafeArea()
|
.ignoresSafeArea()
|
||||||
.overlay(alignment: .trailing) {
|
.overlay(alignment: .bottomTrailing) {
|
||||||
if tool.selection == .pen {
|
switch tool.selection {
|
||||||
|
case .pen:
|
||||||
PenDock(tool: tool, canvas: canvas)
|
PenDock(tool: tool, canvas: canvas)
|
||||||
.transition(.move(edge: .trailing))
|
.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) {
|
.overlay(alignment: .bottomLeading) {
|
||||||
|
|||||||
@@ -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)
|
||||||
|
}
|
||||||
|
}
|
||||||
@@ -6,6 +6,7 @@
|
|||||||
//
|
//
|
||||||
|
|
||||||
import SwiftUI
|
import SwiftUI
|
||||||
|
import PhotosUI
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
struct Toolbar: View {
|
struct Toolbar: View {
|
||||||
@@ -15,8 +16,10 @@ struct Toolbar: View {
|
|||||||
@ObservedObject var canvas: Canvas
|
@ObservedObject var canvas: Canvas
|
||||||
@ObservedObject var history: History
|
@ObservedObject var history: History
|
||||||
|
|
||||||
@State var memo: MemoObject
|
|
||||||
@State var title: String
|
@State var title: String
|
||||||
|
@State var memo: MemoObject
|
||||||
|
@State var photoItem: PhotosPickerItem?
|
||||||
|
|
||||||
@FocusState var textFieldState: Bool
|
@FocusState var textFieldState: Bool
|
||||||
|
|
||||||
let size: CGFloat
|
let size: CGFloat
|
||||||
@@ -50,6 +53,19 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.padding(10)
|
.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 {
|
var closeButton: some View {
|
||||||
@@ -105,22 +121,52 @@ struct Toolbar: View {
|
|||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
Button {
|
HStack(spacing: 0) {
|
||||||
withAnimation {
|
Button {
|
||||||
tool.selection = tool.selection == .photo ? .none : .photo
|
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)
|
.background {
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
RoundedRectangle(cornerRadius: 8)
|
||||||
|
.fill(.regularMaterial)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var historyControl: some View {
|
var historyControl: some View {
|
||||||
|
|||||||
Reference in New Issue
Block a user