mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-04-24 09:38:33 +02:00
feat: add zoom scale options
This commit is contained in:
@@ -24,17 +24,19 @@ final class Canvas: ObservableObject, Identifiable, @unchecked Sendable {
|
|||||||
|
|
||||||
var transform: simd_float4x4 = .init()
|
var transform: simd_float4x4 = .init()
|
||||||
var clipBounds: CGRect = .zero
|
var clipBounds: CGRect = .zero
|
||||||
var zoomScale: CGFloat = .zero
|
|
||||||
var bounds: CGRect = .zero
|
var bounds: CGRect = .zero
|
||||||
var uniformsBuffer: MTLBuffer?
|
var uniformsBuffer: MTLBuffer?
|
||||||
|
|
||||||
|
@Published var state: State = .initial
|
||||||
|
@Published var zoomScale: CGFloat = .zero
|
||||||
|
|
||||||
|
let zoomPublisher = PassthroughSubject<CGFloat, Never>()
|
||||||
|
|
||||||
init(size: CGSize, canvasID: NSManagedObjectID) {
|
init(size: CGSize, canvasID: NSManagedObjectID) {
|
||||||
self.size = size
|
self.size = size
|
||||||
self.canvasID = canvasID
|
self.canvasID = canvasID
|
||||||
}
|
}
|
||||||
|
|
||||||
@Published var state: State = .initial
|
|
||||||
|
|
||||||
var hasValidStroke: Bool {
|
var hasValidStroke: Bool {
|
||||||
if let currentStroke = graphicContext.currentStroke {
|
if let currentStroke = graphicContext.currentStroke {
|
||||||
return Date.now.timeIntervalSince(currentStroke.createdAt) * 1000 > 80
|
return Date.now.timeIntervalSince(currentStroke.createdAt) * 1000 > 80
|
||||||
@@ -92,7 +94,10 @@ extension Canvas {
|
|||||||
// MARK: - Zoom Scale
|
// MARK: - Zoom Scale
|
||||||
extension Canvas {
|
extension Canvas {
|
||||||
func setZoomScale(_ zoomScale: CGFloat) {
|
func setZoomScale(_ zoomScale: CGFloat) {
|
||||||
self.zoomScale = zoomScale
|
DispatchQueue.main.async { [weak self] in
|
||||||
|
guard let self else { return }
|
||||||
|
self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale)
|
||||||
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -162,6 +162,12 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
.store(in: &cancellables)
|
.store(in: &cancellables)
|
||||||
|
|
||||||
|
canvas.zoomPublisher
|
||||||
|
.sink { [weak self] zoomScale in
|
||||||
|
self?.zoomChanged(zoomScale)
|
||||||
|
}
|
||||||
|
.store(in: &cancellables)
|
||||||
|
|
||||||
tool.$selectedPen
|
tool.$selectedPen
|
||||||
.sink { [weak self] pen in
|
.sink { [weak self] pen in
|
||||||
self?.penChanged(to: pen)
|
self?.penChanged(to: pen)
|
||||||
@@ -298,6 +304,12 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
extension CanvasViewController {
|
||||||
|
func zoomChanged(_ zoomScale: CGFloat) {
|
||||||
|
scrollView.setZoomScale(zoomScale, animated: true)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
extension CanvasViewController {
|
extension CanvasViewController {
|
||||||
func historyUndid() {
|
func historyUndid() {
|
||||||
guard history.undo() else { return }
|
guard history.undo() else { return }
|
||||||
|
|||||||
@@ -17,6 +17,8 @@ struct MemoView: View {
|
|||||||
@State var title: String
|
@State var title: String
|
||||||
@FocusState var textFieldState: Bool
|
@FocusState var textFieldState: Bool
|
||||||
|
|
||||||
|
let size: CGFloat = 32
|
||||||
|
|
||||||
init(memo: MemoObject) {
|
init(memo: MemoObject) {
|
||||||
self.memo = memo
|
self.memo = memo
|
||||||
self.title = memo.title
|
self.title = memo.title
|
||||||
@@ -30,9 +32,12 @@ struct MemoView: View {
|
|||||||
.overlay(alignment: .trailing) {
|
.overlay(alignment: .trailing) {
|
||||||
PenDock()
|
PenDock()
|
||||||
}
|
}
|
||||||
|
.overlay(alignment: .bottomLeading) {
|
||||||
|
zoomControl
|
||||||
|
}
|
||||||
.disabled(textFieldState)
|
.disabled(textFieldState)
|
||||||
.overlay(alignment: .top) {
|
.overlay(alignment: .top) {
|
||||||
Toolbar(memo: memo)
|
Toolbar(memo: memo, size: size)
|
||||||
}
|
}
|
||||||
.disabled(canvas.state == .loading || canvas.state == .closing)
|
.disabled(canvas.state == .loading || canvas.state == .closing)
|
||||||
.overlay {
|
.overlay {
|
||||||
@@ -50,6 +55,40 @@ struct MemoView: View {
|
|||||||
.environmentObject(history)
|
.environmentObject(history)
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ViewBuilder
|
||||||
|
var zoomControl: some View {
|
||||||
|
let upperBound: CGFloat = 400
|
||||||
|
let lowerBound: CGFloat = 10
|
||||||
|
let zoomScale: CGFloat = (((canvas.zoomScale - canvas.minimumZoomScale) * (upperBound - lowerBound) / (canvas.maximumZoomScale - canvas.minimumZoomScale)) + lowerBound).rounded()
|
||||||
|
let zoomScales: [Int] = [400, 200, 100, 75, 50, 25, 10]
|
||||||
|
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))
|
||||||
|
.padding(10)
|
||||||
|
}
|
||||||
|
}
|
||||||
|
|
||||||
func loadingIndicator(_ title: String) -> some View {
|
func loadingIndicator(_ title: String) -> some View {
|
||||||
ProgressView {
|
ProgressView {
|
||||||
Text(title)
|
Text(title)
|
||||||
|
|||||||
@@ -12,13 +12,17 @@ struct Toolbar: View {
|
|||||||
@Environment(\.dismiss) var dismiss
|
@Environment(\.dismiss) var dismiss
|
||||||
|
|
||||||
@EnvironmentObject var history: History
|
@EnvironmentObject var history: History
|
||||||
|
@EnvironmentObject var canvas: Canvas
|
||||||
|
|
||||||
@State var memo: MemoObject
|
@State var memo: MemoObject
|
||||||
@State var title: String
|
@State var title: String
|
||||||
@FocusState var textFieldState: Bool
|
@FocusState var textFieldState: Bool
|
||||||
|
|
||||||
init(memo: MemoObject) {
|
let size: CGFloat
|
||||||
|
|
||||||
|
init(memo: MemoObject, size: CGFloat) {
|
||||||
self.memo = memo
|
self.memo = memo
|
||||||
|
self.size = size
|
||||||
self.title = memo.title
|
self.title = memo.title
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -27,7 +31,7 @@ struct Toolbar: View {
|
|||||||
closeButton
|
closeButton
|
||||||
titleField
|
titleField
|
||||||
Spacer()
|
Spacer()
|
||||||
historyTool
|
historyControl
|
||||||
}
|
}
|
||||||
.font(.subheadline)
|
.font(.subheadline)
|
||||||
.padding(10)
|
.padding(10)
|
||||||
@@ -39,7 +43,7 @@ struct Toolbar: View {
|
|||||||
} label: {
|
} label: {
|
||||||
Image(systemName: "xmark")
|
Image(systemName: "xmark")
|
||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.padding(10)
|
.frame(width: size, height: size)
|
||||||
.background(.regularMaterial)
|
.background(.regularMaterial)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
@@ -51,9 +55,8 @@ struct Toolbar: View {
|
|||||||
TextField("", text: $title)
|
TextField("", text: $title)
|
||||||
.focused($textFieldState)
|
.focused($textFieldState)
|
||||||
.textFieldStyle(.plain)
|
.textFieldStyle(.plain)
|
||||||
.padding(.vertical, 5)
|
.padding(.horizontal, size / 2.5)
|
||||||
.padding(.horizontal, 10)
|
.frame(width: 140, height: size)
|
||||||
.frame(width: 120)
|
|
||||||
.background(.regularMaterial)
|
.background(.regularMaterial)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.onChange(of: textFieldState) { oldValue, newValue in
|
.onChange(of: textFieldState) { oldValue, newValue in
|
||||||
@@ -67,12 +70,13 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
var historyTool: some View {
|
var historyControl: some View {
|
||||||
HStack {
|
HStack {
|
||||||
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)
|
.contentShape(.circle)
|
||||||
}
|
}
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
@@ -86,7 +90,7 @@ struct Toolbar: View {
|
|||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
.disabled(history.redoDisabled)
|
.disabled(history.redoDisabled)
|
||||||
}
|
}
|
||||||
.padding(10)
|
.frame(width: size * 2, height: size)
|
||||||
.background(.regularMaterial)
|
.background(.regularMaterial)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
.disabled(textFieldState)
|
.disabled(textFieldState)
|
||||||
|
|||||||
Reference in New Issue
Block a user