Files
SnipMC/Sources/Editor/EditorToolbarView.swift
T
marcoandClaude Opus 4.6 062285abbf Add image editor with annotation tools
Integrates an editor accessible via "Letzte Aufnahme bearbeiten…" or
"Bild bearbeiten…" in the menu bar. Supports arrow, rectangle, ellipse,
and text annotations with color picker, line width, undo/redo, and
export to file or clipboard.

New files in Sources/Editor/:
- AnnotationItem/Document: data model and state management
- AnnotationCanvasView: NSView with CG rendering and mouse interaction
- AnnotationRenderer: shared rendering for canvas and image export
- EditorToolbarView: SwiftUI toolbar with tool selection and actions
- EditorWindowController: window lifecycle following SettingsWindowController pattern

Co-Authored-By: Claude Opus 4.6 <noreply@anthropic.com>
2026-07-01 17:44:20 +02:00

116 lines
3.5 KiB
Swift

import SwiftUI
struct EditorToolbarView: View {
@Bindable var document: AnnotationDocument
var onSave: () -> Void
var onCopyToClipboard: () -> Void
var onCancel: () -> Void
var body: some View {
HStack(spacing: 12) {
toolButtons
Divider().frame(height: 24)
colorPicker
lineWidthPicker
Divider().frame(height: 24)
undoRedoButtons
Spacer()
actionButtons
}
.padding(.horizontal, 16)
.padding(.vertical, 8)
.background(.bar)
}
private var toolButtons: some View {
HStack(spacing: 4) {
toolButton(.arrow, icon: "arrow.up.right", label: "Pfeil")
toolButton(.rectangle, icon: "rectangle", label: "Rechteck")
toolButton(.ellipse, icon: "circle", label: "Ellipse")
toolButton(.text, icon: "textformat", label: "Text")
}
}
private func toolButton(_ kind: AnnotationKind, icon: String, label: String) -> some View {
Button {
document.currentTool = kind
} label: {
Image(systemName: icon)
.frame(width: 28, height: 28)
}
.buttonStyle(.bordered)
.tint(document.currentTool == kind ? .accentColor : nil)
.help(label)
}
private var colorPicker: some View {
HStack(spacing: 4) {
ForEach(presetColors, id: \.self) { color in
Circle()
.fill(Color(nsColor: color))
.frame(width: 20, height: 20)
.overlay {
if document.currentColor == color {
Circle().strokeBorder(.white, lineWidth: 2)
}
}
.shadow(radius: document.currentColor == color ? 2 : 0)
.onTapGesture { document.currentColor = color }
}
ColorPicker("", selection: Binding(
get: { Color(nsColor: document.currentColor) },
set: { document.currentColor = NSColor($0) }
))
.labelsHidden()
.frame(width: 28)
}
}
private var presetColors: [NSColor] {
[.systemRed, .systemBlue, .systemGreen, .systemYellow, .black, .white]
}
private var lineWidthPicker: some View {
Picker("", selection: $document.currentLineWidth) {
Text("Dünn").tag(CGFloat(2))
Text("Mittel").tag(CGFloat(3))
Text("Dick").tag(CGFloat(5))
}
.pickerStyle(.segmented)
.frame(width: 160)
.labelsHidden()
}
private var undoRedoButtons: some View {
HStack(spacing: 4) {
Button {
document.undo()
} label: {
Image(systemName: "arrow.uturn.backward")
}
.disabled(!document.canUndo)
.help("Widerrufen (⌘Z)")
Button {
document.redo()
} label: {
Image(systemName: "arrow.uturn.forward")
}
.disabled(!document.canRedo)
.help("Wiederholen (⌘⇧Z)")
}
}
private var actionButtons: some View {
HStack(spacing: 8) {
Button("Abbrechen", role: .cancel) { onCancel() }
.keyboardShortcut(.escape)
Button("In Zwischenablage") { onCopyToClipboard() }
Button("Speichern") { onSave() }
.keyboardShortcut("s")
.buttonStyle(.borderedProminent)
}
}
}