From c3961abbc8a5e051956f3a1a8854141f929147d2 Mon Sep 17 00:00:00 2001 From: dscyrescotti Date: Sat, 4 May 2024 21:34:43 +0700 Subject: [PATCH] feat: add memo canvas view --- Memola/App/MemolaApp.swift | 4 +- Memola/Canvas/Core/Canvas.swift | 26 +++--- Memola/Canvas/Core/Textures.swift | 4 + .../SolidPointStrokeGenerator.swift | 5 +- Memola/Canvas/Geometries/Stroke/Stroke.swift | 2 +- Memola/Canvas/Tool/Pen/{ => Core}/Pen.swift | 0 .../Canvas/Tool/Pen/{ => Core}/PenStyle.swift | 0 .../{ => Pen}/PenStyles/EraserPenStyle.swift | 2 +- .../{ => Pen}/PenStyles/MarkerPenStyle.swift | 2 +- Memola/Canvas/Tool/Tool.swift | 1 + .../View/Bridge/CanvasViewController.swift | 4 +- Memola/Features/Memo/MemoView.swift | 73 +++++++++++++++-- Memola/Features/Memo/PenToolView.swift | 82 +++++++++++++++++++ 13 files changed, 179 insertions(+), 26 deletions(-) rename Memola/Canvas/Tool/Pen/{ => Core}/Pen.swift (100%) rename Memola/Canvas/Tool/Pen/{ => Core}/PenStyle.swift (100%) rename Memola/Canvas/Tool/{ => Pen}/PenStyles/EraserPenStyle.swift (90%) rename Memola/Canvas/Tool/{ => Pen}/PenStyles/MarkerPenStyle.swift (90%) create mode 100644 Memola/Features/Memo/PenToolView.swift diff --git a/Memola/App/MemolaApp.swift b/Memola/App/MemolaApp.swift index ad3a960..a5f44a4 100644 --- a/Memola/App/MemolaApp.swift +++ b/Memola/App/MemolaApp.swift @@ -11,7 +11,9 @@ import SwiftUI struct MemolaApp: App { var body: some Scene { WindowGroup { - MemosView() + MemoView() + .environmentObject(Canvas()) + .environment(\.managedObjectContext, Persistence.shared.viewContext) } } } diff --git a/Memola/Canvas/Core/Canvas.swift b/Memola/Canvas/Core/Canvas.swift index 77527cc..baac125 100644 --- a/Memola/Canvas/Core/Canvas.swift +++ b/Memola/Canvas/Core/Canvas.swift @@ -26,7 +26,7 @@ final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicCo var clipBounds: CGRect = .zero var zoomScale: CGFloat = .zero -// weak var board: BoardObject? + weak var memo: Memo? var graphicLoader: (() throws -> GraphicContext)? @Published var state: State = .initial @@ -51,20 +51,20 @@ final class Canvas: NSObject, ObservableObject, Identifiable, Codable, GraphicCo // MARK: - Actions extension Canvas { func load() { - guard let graphicLoader else { return } +// guard let graphicLoader else { return } Task(priority: .high) { [unowned self, graphicLoader] in await MainActor.run { self.state = .loading } do { - let graphicContext = try graphicLoader() +// let graphicContext = try graphicLoader() graphicContext.delegate = self await MainActor.run { - self.graphicContext = graphicContext +// self.graphicContext = graphicContext self.state = .loaded } } catch { - NSLog("[SketchNote] - \(error.localizedDescription)") + NSLog("[Memola] - \(error.localizedDescription)") await MainActor.run { self.state = .failed } @@ -73,14 +73,14 @@ extension Canvas { } func save(on managedObjectContext: NSManagedObjectContext) async { -// guard let board else { return } -// do { -// board.data = try JSONEncoder().encode(self) -// board.updatedAt = Date() -// try managedObjectContext.save() -// } catch { -// NSLog("[SketchNote] - \(error.localizedDescription)") -// } + guard let memo else { return } + do { + memo.data = try JSONEncoder().encode(self) + memo.updatedAt = Date() + try managedObjectContext.save() + } catch { + NSLog("[Memola] - \(error.localizedDescription)") + } } func listen(on managedObjectContext: NSManagedObjectContext) { diff --git a/Memola/Canvas/Core/Textures.swift b/Memola/Canvas/Core/Textures.swift index addc399..57e3d8f 100644 --- a/Memola/Canvas/Core/Textures.swift +++ b/Memola/Canvas/Core/Textures.swift @@ -11,6 +11,10 @@ import Foundation class Textures { static var penTextures: [String: MTLTexture] = [:] + static func hasCreatedPenTexture(of textureName: String) -> Bool { + penTextures[textureName] != nil + } + @discardableResult static func createPenTexture(with textureName: String, on device: MTLDevice) -> MTLTexture? { if let penTexture = penTextures[textureName] { diff --git a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift index 5e18882..5f0add9 100644 --- a/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift +++ b/Memola/Canvas/Geometries/Stroke/Generators/SolidPointStrokeGenerator.swift @@ -110,6 +110,8 @@ struct SolidPointStrokeGenerator: StrokeGenerator { let distance = start.distance(to: end) let factor: CGFloat switch configuration.granularity { + case .automatic: + factor = min(6, 1 / (stroke.thickness * 10 / 500)) case .fixed: factor = 1 / (stroke.thickness * stroke.style.stepRate) case .none: @@ -139,7 +141,7 @@ struct SolidPointStrokeGenerator: StrokeGenerator { extension SolidPointStrokeGenerator { struct Configuration { var rotation: Rotation = .fixed - var granularity: Granularity = .none + var granularity: Granularity = .automatic } enum Rotation { @@ -148,6 +150,7 @@ extension SolidPointStrokeGenerator { } enum Granularity { + case automatic case fixed case none } diff --git a/Memola/Canvas/Geometries/Stroke/Stroke.swift b/Memola/Canvas/Geometries/Stroke/Stroke.swift index 45ba0b1..b8a37e2 100644 --- a/Memola/Canvas/Geometries/Stroke/Stroke.swift +++ b/Memola/Canvas/Geometries/Stroke/Stroke.swift @@ -24,7 +24,6 @@ class Stroke: Codable { var tailVertices: [QuadVertex] = [] var texture: MTLTexture? -// var strokeTexture: MTLTexture? var isEmpty: Bool { vertices.isEmpty @@ -91,6 +90,7 @@ class Stroke: Codable { func finish(at point: CGPoint) { style.generator.finish(at: point, on: self) + NSLog("[Memola] - \(MemoryLayout.stride * vertexCount) bytes") } } diff --git a/Memola/Canvas/Tool/Pen/Pen.swift b/Memola/Canvas/Tool/Pen/Core/Pen.swift similarity index 100% rename from Memola/Canvas/Tool/Pen/Pen.swift rename to Memola/Canvas/Tool/Pen/Core/Pen.swift diff --git a/Memola/Canvas/Tool/Pen/PenStyle.swift b/Memola/Canvas/Tool/Pen/Core/PenStyle.swift similarity index 100% rename from Memola/Canvas/Tool/Pen/PenStyle.swift rename to Memola/Canvas/Tool/Pen/Core/PenStyle.swift diff --git a/Memola/Canvas/Tool/PenStyles/EraserPenStyle.swift b/Memola/Canvas/Tool/Pen/PenStyles/EraserPenStyle.swift similarity index 90% rename from Memola/Canvas/Tool/PenStyles/EraserPenStyle.swift rename to Memola/Canvas/Tool/Pen/PenStyles/EraserPenStyle.swift index 53e4698..25ca7c8 100644 --- a/Memola/Canvas/Tool/PenStyles/EraserPenStyle.swift +++ b/Memola/Canvas/Tool/Pen/PenStyles/EraserPenStyle.swift @@ -12,7 +12,7 @@ struct EraserPenStyle: PenStyle { var textureName: String = "point-texture" - var thinkness: (min: CGFloat, max: CGFloat) = (15, 120) + var thinkness: (min: CGFloat, max: CGFloat) = (1, 120) var color: [CGFloat] = [1, 1, 1, 0] diff --git a/Memola/Canvas/Tool/PenStyles/MarkerPenStyle.swift b/Memola/Canvas/Tool/Pen/PenStyles/MarkerPenStyle.swift similarity index 90% rename from Memola/Canvas/Tool/PenStyles/MarkerPenStyle.swift rename to Memola/Canvas/Tool/Pen/PenStyles/MarkerPenStyle.swift index ee049e9..0b821d8 100644 --- a/Memola/Canvas/Tool/PenStyles/MarkerPenStyle.swift +++ b/Memola/Canvas/Tool/Pen/PenStyles/MarkerPenStyle.swift @@ -12,7 +12,7 @@ struct MarkerPenStyle: PenStyle { var textureName: String = "point-texture" - var thinkness: (min: CGFloat, max: CGFloat) = (15, 120) + var thinkness: (min: CGFloat, max: CGFloat) = (1, 120) var color: [CGFloat] = [1, 0.38, 0.38, 1] diff --git a/Memola/Canvas/Tool/Tool.swift b/Memola/Canvas/Tool/Tool.swift index 636dc75..9df2cbb 100644 --- a/Memola/Canvas/Tool/Tool.swift +++ b/Memola/Canvas/Tool/Tool.swift @@ -18,6 +18,7 @@ class Tool: NSObject, ObservableObject { Pen(for: .eraser) ] super.init() + selectedPen = pens.first } func changePen(_ pen: Pen) { diff --git a/Memola/Canvas/View/Bridge/CanvasViewController.swift b/Memola/Canvas/View/Bridge/CanvasViewController.swift index d52030e..174ac95 100644 --- a/Memola/Canvas/View/Bridge/CanvasViewController.swift +++ b/Memola/Canvas/View/Bridge/CanvasViewController.swift @@ -43,7 +43,7 @@ class CanvasViewController: UIViewController { configureGestures() configureListeners() - loadBoard() + loadMemo() } override func viewWillAppear(_ animated: Bool) { @@ -161,7 +161,7 @@ extension CanvasViewController { } extension CanvasViewController { - func loadBoard() { + func loadMemo() { canvas.load() } diff --git a/Memola/Features/Memo/MemoView.swift b/Memola/Features/Memo/MemoView.swift index 4196f5d..e3ddcdc 100644 --- a/Memola/Features/Memo/MemoView.swift +++ b/Memola/Features/Memo/MemoView.swift @@ -9,14 +9,75 @@ import SwiftUI struct MemoView: View { @Environment(\.dismiss) var dismiss + @Environment(\.managedObjectContext) var managedObjectContext + + @StateObject var tool = Tool() + @StateObject var history = History() + + @EnvironmentObject var canvas: Canvas + var body: some View { - VStack { - Text("Memo View") - Button { - dismiss() - } label: { - Text("Close Memo") + CanvasView() + .ignoresSafeArea() + .overlay(alignment: .bottomTrailing) { + PenToolView() + .padding() } + .overlay(alignment: .topTrailing) { + historyTool + .padding() + } + .overlay(alignment: .topLeading) { + Button { + dismiss() + } label: { + Image(systemName: "xmark") + .padding(15) + .background(.regularMaterial) + .clipShape(RoundedRectangle(cornerRadius: 20)) + } + .hoverEffect(.lift) + .padding() + } + .disabled(canvas.state == .loading) + .overlay { + if canvas.state == .loading { + ProgressView { + Text("Loading memo...") + } + .progressViewStyle(.circular) + .padding(20) + .background(.regularMaterial) + .clipShape(RoundedRectangle(cornerRadius: 20)) + } + } + .environmentObject(tool) + .environmentObject(canvas) + .environmentObject(history) + .task { + canvas.listen(on: managedObjectContext) + } + } + + var historyTool: some View { + HStack { + Button { + history.historyPublisher.send(.undo) + } label: { + Image(systemName: "arrow.uturn.backward.circle") + } + .hoverEffect(.lift) + .disabled(history.undoDisabled) + Button { + history.historyPublisher.send(.redo) + } label: { + Image(systemName: "arrow.uturn.forward.circle") + } + .hoverEffect(.lift) + .disabled(history.redoDisabled) } + .padding(15) + .background(.regularMaterial) + .clipShape(RoundedRectangle(cornerRadius: 20)) } } diff --git a/Memola/Features/Memo/PenToolView.swift b/Memola/Features/Memo/PenToolView.swift new file mode 100644 index 0000000..4bb1075 --- /dev/null +++ b/Memola/Features/Memo/PenToolView.swift @@ -0,0 +1,82 @@ +// +// PenToolView.swift +// Memola +// +// Created by Dscyre Scotti on 5/4/24. +// + +import SwiftUI + +struct PenToolView: View { + @EnvironmentObject var tool: Tool + + var body: some View { + VStack { + if let pen = tool.selectedPen { + let thicknessBounds = pen.style.thinkness + let thickness = Binding { + max(pen.thickness, pen.style.thinkness.min) + } set: { newValue in + tool.selectedPen?.thickness = newValue + } + let color = Binding { + Color.rgba(from: pen.color) + } set: { newValue in + tool.selectedPen?.color = newValue.components + tool.objectWillChange.send() + } + HStack { + ColorPicker("", selection: color) + .frame(width: 40, height: 40) + Slider(value: thickness, in: thicknessBounds.min...thicknessBounds.max) + .frame(width: 180, height: 40) + } + } + HStack { + ForEach(tool.pens) { pen in + penView(pen) + .overlay(alignment: .bottom) { + if tool.selectedPen === pen { + Circle() + .frame(width: 5, height: 5) + .offset(y: 7.5) + .foregroundStyle(Color.rgba(from: pen.color)) + } + } + } + } + .padding(15) + .background(.regularMaterial) + .clipShape(RoundedRectangle(cornerRadius: 20)) + } + } + + @ViewBuilder + func penView(_ pen: Pen) -> some View { + Button { + if tool.selectedPen === pen { + tool.selectedPen = nil + } else { + tool.changePen(pen) + } + } label: { + ZStack { + if let tip = pen.style.icon.tip { + Image(tip) + .resizable() + .renderingMode(.template) + .foregroundStyle(Color.rgba(from: pen.color)) + } + Image(pen.style.icon.base) + .resizable() + } + .frame(width: 30, height: 65) + .drawingGroup() + .hoverEffect(.lift) + } + .buttonStyle(.plain) + } +} + + +