mirror of
https://github.com/dscyrescotti/Memola.git
synced 2026-04-27 11:07:07 +02:00
feat: fine tune memo canvas view for macos runtime
This commit is contained in:
@@ -1254,6 +1254,7 @@
|
|||||||
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_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\"";
|
||||||
@@ -1291,6 +1292,7 @@
|
|||||||
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_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\"";
|
||||||
|
|||||||
@@ -162,7 +162,13 @@ extension Canvas {
|
|||||||
// MARK: - Zoom Scale
|
// MARK: - Zoom Scale
|
||||||
extension Canvas {
|
extension Canvas {
|
||||||
func setZoomScale(_ zoomScale: CGFloat) {
|
func setZoomScale(_ zoomScale: CGFloat) {
|
||||||
|
#if os(macOS)
|
||||||
self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale)
|
self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale)
|
||||||
|
#else
|
||||||
|
DispatchQueue.main.async { [unowned self] in
|
||||||
|
self.zoomScale = min(max(zoomScale, minimumZoomScale), maximumZoomScale)
|
||||||
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -169,15 +169,15 @@ 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
|
DispatchQueue.main.async { [unowned canvas] in
|
||||||
canvas.setZoomScale(canvas.defaultZoomScale)
|
canvas.setZoomScale(canvas.defaultZoomScale)
|
||||||
}
|
}
|
||||||
|
|
||||||
#if os(macOS)
|
|
||||||
scrollView.contentView.setBoundsSize(newFrame.size)
|
scrollView.contentView.setBoundsSize(newFrame.size)
|
||||||
let center = NSPoint(x: newFrame.midX, y: newFrame.midY)
|
let center = NSPoint(x: newFrame.midX, y: newFrame.midY)
|
||||||
scrollView.setMagnification(canvas.defaultZoomScale, centeredAt: center)
|
scrollView.setMagnification(canvas.defaultZoomScale, centeredAt: center)
|
||||||
#else
|
#else
|
||||||
|
canvas.setZoomScale(canvas.defaultZoomScale)
|
||||||
scrollView.setZoomScale(canvas.defaultZoomScale, animated: true)
|
scrollView.setZoomScale(canvas.defaultZoomScale, animated: true)
|
||||||
centerDocumentView(to: newSize)
|
centerDocumentView(to: newSize)
|
||||||
#endif
|
#endif
|
||||||
@@ -427,9 +427,6 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func draggingStarted() {
|
func draggingStarted() {
|
||||||
#if os(macOS)
|
|
||||||
canvas.setZoomScale(scrollView.magnification)
|
|
||||||
#endif
|
|
||||||
guard !renderer.updatesViewPort else { return }
|
guard !renderer.updatesViewPort else { return }
|
||||||
canvas.updateClipBounds(scrollView, on: drawingView)
|
canvas.updateClipBounds(scrollView, on: drawingView)
|
||||||
drawingView.disableUserInteraction()
|
drawingView.disableUserInteraction()
|
||||||
@@ -437,9 +434,6 @@ extension CanvasViewController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
func draggingEnded() {
|
func draggingEnded() {
|
||||||
#if os(macOS)
|
|
||||||
canvas.setZoomScale(scrollView.magnification)
|
|
||||||
#endif
|
|
||||||
renderer.setUpdatesViewPort(false)
|
renderer.setUpdatesViewPort(false)
|
||||||
renderer.setRedrawsGraphicRender()
|
renderer.setRedrawsGraphicRender()
|
||||||
renderView.draw()
|
renderView.draw()
|
||||||
|
|||||||
@@ -23,6 +23,9 @@ struct ElementToolbar: View {
|
|||||||
|
|
||||||
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 {
|
||||||
@@ -42,6 +45,7 @@ struct ElementToolbar: View {
|
|||||||
}
|
}
|
||||||
.padding(.bottom, 10)
|
.padding(.bottom, 10)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.fullScreenCover(isPresented: $opensCamera) {
|
.fullScreenCover(isPresented: $opensCamera) {
|
||||||
@@ -94,9 +98,12 @@ struct ElementToolbar: View {
|
|||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.foregroundStyle(tool.selection == .hand ? Color.white : Color.accentColor)
|
.foregroundStyle(tool.selection == .hand ? Color.white : Color.accentColor)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
.background {
|
.background {
|
||||||
if tool.selection == .hand {
|
if tool.selection == .hand {
|
||||||
@@ -116,9 +123,12 @@ struct ElementToolbar: View {
|
|||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.foregroundStyle(tool.selection == .pen ? Color.white : Color.accentColor)
|
.foregroundStyle(tool.selection == .pen ? Color.white : Color.accentColor)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
.background {
|
.background {
|
||||||
if tool.selection == .pen {
|
if tool.selection == .pen {
|
||||||
@@ -138,9 +148,12 @@ struct ElementToolbar: View {
|
|||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor)
|
.foregroundStyle(tool.selection == .photo ? Color.white : Color.accentColor)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
.background {
|
.background {
|
||||||
if tool.selection == .photo {
|
if tool.selection == .photo {
|
||||||
@@ -159,13 +172,13 @@ struct ElementToolbar: View {
|
|||||||
.transition(.blurReplace.animation(.easeIn(duration: 0.1)))
|
.transition(.blurReplace.animation(.easeIn(duration: 0.1)))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
.background {
|
// .background {
|
||||||
if tool.selection == .photo {
|
// if tool.selection == .photo {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
// RoundedRectangle(cornerRadius: 8)
|
||||||
.fill(Color.white.tertiary)
|
// .fill(Color.white.tertiary)
|
||||||
.transition(.move(edge: .leading).combined(with: .opacity).animation(.easeIn(duration: 0.1)))
|
// .transition(.move(edge: .leading).combined(with: .opacity).animation(.easeIn(duration: 0.1)))
|
||||||
}
|
// }
|
||||||
}
|
// }
|
||||||
}
|
}
|
||||||
.background {
|
.background {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
@@ -222,18 +235,24 @@ struct ElementToolbar: View {
|
|||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) {
|
PhotosPicker(selection: $photosPickerItem, matching: .images, preferredItemEncoding: .compatible) {
|
||||||
Image(systemName: "photo.fill.on.rectangle.fill")
|
Image(systemName: "photo.fill.on.rectangle.fill")
|
||||||
.contentShape(.circle)
|
.contentShape(.circle)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -31,11 +31,15 @@ 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(size: size, memo: memo, tool: tool, canvas: canvas, history: history)
|
||||||
@@ -66,7 +70,6 @@ struct MemoView: View {
|
|||||||
switch tool.selection {
|
switch tool.selection {
|
||||||
case .pen:
|
case .pen:
|
||||||
PenDock(tool: tool, canvas: canvas, size: size)
|
PenDock(tool: tool, canvas: canvas, size: size)
|
||||||
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
|
||||||
case .photo:
|
case .photo:
|
||||||
if let photoItem = tool.selectedPhotoItem {
|
if let photoItem = tool.selectedPhotoItem {
|
||||||
PhotoPreview(photoItem: photoItem, tool: tool)
|
PhotoPreview(photoItem: photoItem, tool: tool)
|
||||||
@@ -106,7 +109,7 @@ struct MemoView: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.overlay(alignment: .bottom) {
|
.overlay(alignment: .bottom) {
|
||||||
if tool.selection != .hand {
|
if tool.selection != .hand && !canvas.locksCanvas {
|
||||||
Button {
|
Button {
|
||||||
withAnimation {
|
withAnimation {
|
||||||
tool.selectTool(.hand)
|
tool.selectTool(.hand)
|
||||||
@@ -121,6 +124,7 @@ struct MemoView: View {
|
|||||||
.contentShape(.capsule)
|
.contentShape(.capsule)
|
||||||
}
|
}
|
||||||
.offset(y: 5)
|
.offset(y: 5)
|
||||||
|
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -132,6 +136,40 @@ struct MemoView: View {
|
|||||||
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 !canvas.locksCanvas {
|
||||||
|
#if os(macOS)
|
||||||
|
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)
|
||||||
|
.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 {
|
Menu {
|
||||||
ForEach(zoomScales, id: \.self) { scale in
|
ForEach(zoomScales, id: \.self) { scale in
|
||||||
Button {
|
Button {
|
||||||
@@ -156,12 +194,12 @@ struct MemoView: View {
|
|||||||
.frame(height: size)
|
.frame(height: size)
|
||||||
.background(.regularMaterial)
|
.background(.regularMaterial)
|
||||||
.clipShape(.rect(cornerRadius: 8))
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.contentShape(.rect(cornerRadius: 8))
|
||||||
.padding(10)
|
.padding(10)
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
#endif
|
|
||||||
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
.transition(.move(edge: .bottom).combined(with: .blurReplace))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
|||||||
@@ -23,8 +23,28 @@ struct PenDock: View {
|
|||||||
|
|
||||||
@State var refreshScrollId: UUID = UUID()
|
@State var refreshScrollId: UUID = UUID()
|
||||||
@State var opensColorPicker: Bool = false
|
@State var opensColorPicker: Bool = false
|
||||||
|
#if os(macOS)
|
||||||
|
@State var showsThinknessPicker: Bool = false
|
||||||
|
#endif
|
||||||
|
|
||||||
var body: some View {
|
var body: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
ZStack(alignment: .bottomTrailing) {
|
||||||
|
if !canvas.locksCanvas {
|
||||||
|
VStack(alignment: .trailing) {
|
||||||
|
penPropertyTool
|
||||||
|
penItemList
|
||||||
|
}
|
||||||
|
.fixedSize()
|
||||||
|
.frame(maxHeight: .infinity)
|
||||||
|
.padding(10)
|
||||||
|
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
||||||
|
}
|
||||||
|
lockButton
|
||||||
|
.padding(10)
|
||||||
|
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
||||||
|
}
|
||||||
|
#else
|
||||||
if horizontalSizeClass == .regular {
|
if horizontalSizeClass == .regular {
|
||||||
ZStack(alignment: .bottomTrailing) {
|
ZStack(alignment: .bottomTrailing) {
|
||||||
if !canvas.locksCanvas {
|
if !canvas.locksCanvas {
|
||||||
@@ -73,6 +93,7 @@ struct PenDock: View {
|
|||||||
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
.transition(.move(edge: .trailing).combined(with: .blurReplace))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
@ViewBuilder
|
||||||
@@ -319,12 +340,12 @@ struct PenDock: View {
|
|||||||
var compactPenPropertyTool: some View {
|
var compactPenPropertyTool: some View {
|
||||||
if let pen = tool.selectedPen {
|
if let pen = tool.selectedPen {
|
||||||
HStack(spacing: 10) {
|
HStack(spacing: 10) {
|
||||||
compactPenThicknessPicker(pen)
|
penThicknessPicker(pen)
|
||||||
.frame(width: width)
|
.frame(width: size)
|
||||||
.rotationEffect(.degrees(-90))
|
.rotationEffect(.degrees(-90))
|
||||||
if pen.strokeStyle == .marker {
|
if pen.strokeStyle == .marker {
|
||||||
penColorPicker(pen)
|
penColorPicker(pen)
|
||||||
.frame(width: width)
|
.frame(width: size)
|
||||||
.transition(.move(edge: .trailing).combined(with: .opacity))
|
.transition(.move(edge: .trailing).combined(with: .opacity))
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
@@ -353,7 +374,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(height: size)
|
||||||
.overlay {
|
.overlay {
|
||||||
RoundedRectangle(cornerRadius: 8)
|
RoundedRectangle(cornerRadius: 8)
|
||||||
.stroke(Color.gray, lineWidth: 0.4)
|
.stroke(Color.gray, lineWidth: 0.4)
|
||||||
@@ -389,66 +411,52 @@ struct PenDock: View {
|
|||||||
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) {
|
#if os(macOS)
|
||||||
|
let _width = width * factor - 38
|
||||||
|
#else
|
||||||
|
let _width = horizontalSizeClass == .compact ? self.size : width * factor - 38
|
||||||
|
#endif
|
||||||
|
ScrollViewReader { proxy in
|
||||||
|
ScrollView(showsIndicators: false) {
|
||||||
|
LazyVStack(spacing: 0) {
|
||||||
ForEach(pen.style.thicknessSteps, id: \.self) { step in
|
ForEach(pen.style.thicknessSteps, id: \.self) { step in
|
||||||
let size = ((step - minimum) * (end - start) / (maximum - minimum)) + start - (0.5 / step)
|
let size = ((step - minimum) * (end - start) / (maximum - minimum)) + start - (0.5 / step)
|
||||||
Circle()
|
Circle()
|
||||||
.fill(.black)
|
.foregroundStyle(.primary)
|
||||||
.frame(width: size, height: size)
|
.frame(width: size, height: size)
|
||||||
.frame(width: size + 2, height: size + 2)
|
.frame(width: _width, height: self.size)
|
||||||
|
.contentShape(.rect)
|
||||||
|
.id(step)
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
}
|
||||||
.hoverEffect(.lift)
|
#if os(macOS)
|
||||||
.pickerStyle(.wheel)
|
.frame(height: size)
|
||||||
|
#else
|
||||||
|
.frame(width: _width, height: size)
|
||||||
#endif
|
#endif
|
||||||
.frame(width: width * factor - 18, height: 35)
|
.background(.gray.quaternary)
|
||||||
|
.clipShape(.rect(cornerRadius: 8))
|
||||||
|
.scrollPosition(id: selection, anchor: .center)
|
||||||
|
.scrollTargetLayout()
|
||||||
|
.scrollTargetBehavior(.viewAligned)
|
||||||
|
.scrollIndicators(.hidden)
|
||||||
|
.onAppear {
|
||||||
|
proxy.scrollTo(selection.wrappedValue)
|
||||||
|
}
|
||||||
.onChange(of: pen.thickness) { _, _ in
|
.onChange(of: pen.thickness) { _, _ in
|
||||||
withPersistence(\.viewContext) { context in
|
withPersistence(\.viewContext) { context in
|
||||||
try context.saveIfNeeded()
|
try context.saveIfNeeded()
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ViewBuilder
|
|
||||||
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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
#if os(iOS)
|
|
||||||
.hoverEffect(.lift)
|
|
||||||
.pickerStyle(.wheel)
|
|
||||||
#endif
|
|
||||||
.frame(width: 50, height: 30)
|
|
||||||
.onChange(of: pen.thickness) { _, _ in
|
|
||||||
withPersistence(\.viewContext) { context in
|
|
||||||
try context.saveIfNeeded()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
var newPenButton: some View {
|
var newPenButton: some View {
|
||||||
@@ -467,6 +475,8 @@ struct PenDock: View {
|
|||||||
.foregroundStyle(.green)
|
.foregroundStyle(.green)
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
@@ -541,13 +551,15 @@ struct PenDock: View {
|
|||||||
}
|
}
|
||||||
} label: {
|
} label: {
|
||||||
Image(systemName: canvas.locksCanvas ? "lock.fill" : "lock.open.fill")
|
Image(systemName: canvas.locksCanvas ? "lock.fill" : "lock.open.fill")
|
||||||
.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))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
.contentTransition(.symbolEffect(.replace))
|
.contentTransition(.symbolEffect(.replace))
|
||||||
}
|
}
|
||||||
|
|||||||
@@ -41,9 +41,15 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
}
|
}
|
||||||
.frame(maxWidth: .infinity, alignment: .leading)
|
.frame(maxWidth: .infinity, alignment: .leading)
|
||||||
|
#if os(macOS)
|
||||||
|
if !canvas.locksCanvas {
|
||||||
|
ElementToolbar(size: size, tool: tool, canvas: canvas)
|
||||||
|
}
|
||||||
|
#else
|
||||||
if !canvas.locksCanvas, horizontalSizeClass == .regular {
|
if !canvas.locksCanvas, horizontalSizeClass == .regular {
|
||||||
ElementToolbar(size: size, tool: tool, canvas: canvas)
|
ElementToolbar(size: size, tool: tool, canvas: canvas)
|
||||||
}
|
}
|
||||||
|
#endif
|
||||||
HStack(spacing: 5) {
|
HStack(spacing: 5) {
|
||||||
if !canvas.locksCanvas {
|
if !canvas.locksCanvas {
|
||||||
gridModeControl
|
gridModeControl
|
||||||
@@ -61,13 +67,15 @@ struct Toolbar: View {
|
|||||||
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))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#endif
|
||||||
.disabled(textFieldState)
|
.disabled(textFieldState)
|
||||||
.transition(.move(edge: .top).combined(with: .blurReplace))
|
.transition(.move(edge: .top).combined(with: .blurReplace))
|
||||||
@@ -98,29 +106,34 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var historyControl: some View {
|
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)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#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)
|
#if os(iOS)
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
|
#else
|
||||||
|
.buttonStyle(.plain)
|
||||||
#endif
|
#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)
|
.disabled(textFieldState)
|
||||||
@@ -128,6 +141,27 @@ struct Toolbar: View {
|
|||||||
}
|
}
|
||||||
|
|
||||||
var gridModeControl: some View {
|
var gridModeControl: some View {
|
||||||
|
#if os(macOS)
|
||||||
|
Button {
|
||||||
|
switch canvas.gridMode {
|
||||||
|
case .none:
|
||||||
|
canvas.gridMode = .point
|
||||||
|
case .point:
|
||||||
|
canvas.gridMode = .line
|
||||||
|
case .line:
|
||||||
|
canvas.gridMode = .none
|
||||||
|
}
|
||||||
|
} 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 {
|
||||||
@@ -143,16 +177,15 @@ 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))
|
||||||
}
|
}
|
||||||
#if os(iOS)
|
|
||||||
.hoverEffect(.lift)
|
.hoverEffect(.lift)
|
||||||
#endif
|
|
||||||
.contentTransition(.symbolEffect(.replace))
|
.contentTransition(.symbolEffect(.replace))
|
||||||
.transition(.move(edge: .top).combined(with: .blurReplace))
|
.transition(.move(edge: .top).combined(with: .blurReplace))
|
||||||
|
#endif
|
||||||
}
|
}
|
||||||
|
|
||||||
func closeMemo() {
|
func closeMemo() {
|
||||||
|
|||||||
Reference in New Issue
Block a user