editor tool fixes
This commit is contained in:
@@ -26,6 +26,7 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate,
|
||||
private var toolButtons: [ToolKind: NSButton] = [:]
|
||||
private var deleteButton: NSButton?
|
||||
private let currentColorWell = NSColorWell()
|
||||
private let fillColorWell = NSColorWell()
|
||||
|
||||
// MARK: - Lifecycle
|
||||
|
||||
@@ -221,16 +222,30 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate,
|
||||
delete.heightAnchor.constraint(equalToConstant: 28).isActive = true
|
||||
deleteButton = delete
|
||||
|
||||
// Current colour, shown the way an editor shows its foreground swatch.
|
||||
// Line and fill colour, the way an editor shows foreground and background swatches.
|
||||
currentColorWell.color = Configuration.shared.editorLineColor
|
||||
currentColorWell.target = self
|
||||
currentColorWell.action = #selector(currentColorChanged)
|
||||
currentColorWell.toolTip = "Current colour"
|
||||
currentColorWell.toolTip = "Line colour"
|
||||
currentColorWell.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
fillColorWell.color = Configuration.shared.editorFillColor
|
||||
fillColorWell.target = self
|
||||
fillColorWell.action = #selector(fillColorChanged)
|
||||
fillColorWell.toolTip = "Fill colour"
|
||||
fillColorWell.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
for well in [currentColorWell, fillColorWell] {
|
||||
NSLayoutConstraint.activate([
|
||||
currentColorWell.widthAnchor.constraint(equalToConstant: 44),
|
||||
currentColorWell.heightAnchor.constraint(equalToConstant: 26)
|
||||
well.widthAnchor.constraint(equalToConstant: 26),
|
||||
well.heightAnchor.constraint(equalToConstant: 24)
|
||||
])
|
||||
}
|
||||
|
||||
let wells = NSStackView(views: [currentColorWell, fillColorWell])
|
||||
wells.orientation = .horizontal
|
||||
wells.spacing = 4
|
||||
wells.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
var swatchRows: [[NSView]] = []
|
||||
var swatchPair: [NSView] = []
|
||||
@@ -259,7 +274,7 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate,
|
||||
EditorWindowController.railDivider(),
|
||||
delete,
|
||||
EditorWindowController.railDivider(),
|
||||
currentColorWell,
|
||||
wells,
|
||||
swatchGrid,
|
||||
spacer])
|
||||
stack.orientation = .vertical
|
||||
@@ -392,6 +407,12 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate,
|
||||
apply(color: currentColorWell.color)
|
||||
}
|
||||
|
||||
@objc private func fillColorChanged() {
|
||||
let color = fillColorWell.color
|
||||
Configuration.shared.editorFillColor = color
|
||||
canvas.applyStyleToSelection { $0.fillColor = color }
|
||||
}
|
||||
|
||||
private func apply(color: NSColor) {
|
||||
// The highlighter has its own colour field; everything else uses the line colour.
|
||||
let isHighlight = canvas.tool == .highlight || surface.selection is HighlightElement
|
||||
@@ -423,9 +444,12 @@ final class EditorWindowController: NSWindowController, NSWindowDelegate,
|
||||
}
|
||||
styleBar.update(capabilities: capabilities,
|
||||
style: style,
|
||||
showCrop: surface.cropElement != nil)
|
||||
showCrop: surface.cropElement != nil,
|
||||
toolName: canvas.tool.title)
|
||||
deleteButton?.isEnabled = surface.selection != nil
|
||||
currentColorWell.color = capabilities.contains(.highlightColor) ? style.highlightColor : style.lineColor
|
||||
fillColorWell.color = style.fillColor
|
||||
fillColorWell.isEnabled = capabilities.contains(.fillColor)
|
||||
}
|
||||
|
||||
private func capabilitiesForTool(_ tool: ToolKind) -> StyleCapabilities {
|
||||
|
||||
@@ -1,7 +1,9 @@
|
||||
// StyleBarView.swift
|
||||
// GreenshotMac
|
||||
//
|
||||
// The context sensitive style controls, equivalent to Greenshot's editor field toolbar.
|
||||
// The options row beneath the toolbar. It keeps a constant height whether or not the
|
||||
// active tool has options, so selecting a tool never shifts the canvas. Colour lives
|
||||
// in the tool rail on the left and is deliberately absent here.
|
||||
|
||||
import AppKit
|
||||
|
||||
@@ -14,12 +16,13 @@ protocol StyleBarDelegate: AnyObject {
|
||||
@MainActor
|
||||
final class StyleBarView: NSView {
|
||||
|
||||
/// Fixed height of the options row, occupied or not.
|
||||
static let barHeight: CGFloat = 34
|
||||
|
||||
weak var delegate: StyleBarDelegate?
|
||||
|
||||
private let stack = NSStackView()
|
||||
private let lineColorWell = NSColorWell()
|
||||
private let fillColorWell = NSColorWell()
|
||||
private let highlightColorWell = NSColorWell()
|
||||
private let placeholder = NSTextField(labelWithString: "")
|
||||
private let thicknessSlider = NSSlider()
|
||||
private let thicknessLabel = NSTextField(labelWithString: "2 px")
|
||||
private let fontPopUp = NSPopUpButton()
|
||||
@@ -28,10 +31,11 @@ final class StyleBarView: NSView {
|
||||
private let italicButton = NSButton()
|
||||
private let obfuscatePopUp = NSPopUpButton()
|
||||
private let obfuscateSlider = NSSlider()
|
||||
private let obfuscateLabel = NSTextField(labelWithString: "8")
|
||||
private let shadowButton = NSButton()
|
||||
private let cropButton = NSButton()
|
||||
|
||||
private var labelledPairs: [StyleCapabilities: [NSView]] = [:]
|
||||
private var groups: [StyleCapabilities: [NSView]] = [:]
|
||||
|
||||
override init(frame frameRect: NSRect) {
|
||||
super.init(frame: frameRect)
|
||||
@@ -40,55 +44,43 @@ final class StyleBarView: NSView {
|
||||
|
||||
required init?(coder: NSCoder) { fatalError("init(coder:) is not used") }
|
||||
|
||||
override var intrinsicContentSize: NSSize {
|
||||
NSSize(width: NSView.noIntrinsicMetric, height: StyleBarView.barHeight)
|
||||
}
|
||||
|
||||
private func build() {
|
||||
wantsLayer = true
|
||||
translatesAutoresizingMaskIntoConstraints = false
|
||||
heightAnchor.constraint(equalToConstant: StyleBarView.barHeight).isActive = true
|
||||
|
||||
stack.orientation = .horizontal
|
||||
stack.alignment = .centerY
|
||||
stack.spacing = 10
|
||||
stack.edgeInsets = NSEdgeInsets(top: 6, left: 12, bottom: 6, right: 12)
|
||||
stack.edgeInsets = NSEdgeInsets(top: 0, left: 12, bottom: 0, right: 12)
|
||||
stack.translatesAutoresizingMaskIntoConstraints = false
|
||||
addSubview(stack)
|
||||
NSLayoutConstraint.activate([
|
||||
stack.leadingAnchor.constraint(equalTo: leadingAnchor),
|
||||
stack.trailingAnchor.constraint(lessThanOrEqualTo: trailingAnchor),
|
||||
stack.topAnchor.constraint(equalTo: topAnchor),
|
||||
stack.bottomAnchor.constraint(equalTo: bottomAnchor)
|
||||
stack.centerYAnchor.constraint(equalTo: centerYAnchor)
|
||||
])
|
||||
|
||||
lineColorWell.color = Configuration.shared.editorLineColor
|
||||
lineColorWell.target = self
|
||||
lineColorWell.action = #selector(lineColorChanged)
|
||||
let lineLabel = NSTextField(labelWithString: "Line")
|
||||
labelledPairs[.lineColor] = [lineLabel, lineColorWell]
|
||||
|
||||
fillColorWell.color = Configuration.shared.editorFillColor
|
||||
fillColorWell.target = self
|
||||
fillColorWell.action = #selector(fillColorChanged)
|
||||
let fillLabel = NSTextField(labelWithString: "Fill")
|
||||
labelledPairs[.fillColor] = [fillLabel, fillColorWell]
|
||||
|
||||
highlightColorWell.color = Configuration.shared.editorHighlightColor
|
||||
highlightColorWell.target = self
|
||||
highlightColorWell.action = #selector(highlightColorChanged)
|
||||
let highlightLabel = NSTextField(labelWithString: "Highlight")
|
||||
labelledPairs[.highlightColor] = [highlightLabel, highlightColorWell]
|
||||
placeholder.font = NSFont.systemFont(ofSize: 11)
|
||||
placeholder.textColor = .tertiaryLabelColor
|
||||
|
||||
thicknessSlider.minValue = 0
|
||||
thicknessSlider.maxValue = 20
|
||||
thicknessSlider.doubleValue = Double(Configuration.shared.editorLineThickness)
|
||||
thicknessSlider.target = self
|
||||
thicknessSlider.action = #selector(thicknessChanged)
|
||||
thicknessSlider.widthAnchor.constraint(equalToConstant: 90).isActive = true
|
||||
labelledPairs[.lineThickness] = [NSTextField(labelWithString: "Width"), thicknessSlider, thicknessLabel]
|
||||
thicknessSlider.widthAnchor.constraint(equalToConstant: 100).isActive = true
|
||||
thicknessLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 11, weight: .regular)
|
||||
groups[.lineThickness] = [label("Width"), thicknessSlider, thicknessLabel]
|
||||
|
||||
for family in ["Helvetica", "Helvetica Neue", "Menlo", "SF Pro", "Times New Roman", "Arial", "Georgia"] {
|
||||
fontPopUp.addItem(withTitle: family)
|
||||
}
|
||||
fontPopUp.selectItem(withTitle: Configuration.shared.editorFontFamily)
|
||||
fontPopUp.target = self
|
||||
fontPopUp.action = #selector(fontChanged)
|
||||
|
||||
fontSizeField.stringValue = String(Configuration.shared.editorFontSize)
|
||||
fontSizeField.target = self
|
||||
fontSizeField.action = #selector(fontChanged)
|
||||
fontSizeField.widthAnchor.constraint(equalToConstant: 46).isActive = true
|
||||
@@ -96,22 +88,21 @@ final class StyleBarView: NSView {
|
||||
configureToggle(boldButton, title: "B", action: #selector(fontChanged))
|
||||
boldButton.font = NSFont.boldSystemFont(ofSize: 12)
|
||||
configureToggle(italicButton, title: "I", action: #selector(fontChanged))
|
||||
labelledPairs[.font] = [fontPopUp, fontSizeField, boldButton, italicButton]
|
||||
groups[.font] = [label("Font"), fontPopUp, fontSizeField, boldButton, italicButton]
|
||||
|
||||
obfuscatePopUp.addItems(withTitles: ObfuscateMode.allCases.map(\.title))
|
||||
obfuscatePopUp.target = self
|
||||
obfuscatePopUp.action = #selector(obfuscationChanged)
|
||||
obfuscateSlider.minValue = 2
|
||||
obfuscateSlider.maxValue = 40
|
||||
obfuscateSlider.doubleValue = Double(Configuration.shared.editorPixelSize)
|
||||
obfuscateSlider.target = self
|
||||
obfuscateSlider.action = #selector(obfuscationChanged)
|
||||
obfuscateSlider.widthAnchor.constraint(equalToConstant: 90).isActive = true
|
||||
labelledPairs[.obfuscation] = [obfuscatePopUp, NSTextField(labelWithString: "Strength"), obfuscateSlider]
|
||||
obfuscateSlider.widthAnchor.constraint(equalToConstant: 100).isActive = true
|
||||
obfuscateLabel.font = NSFont.monospacedDigitSystemFont(ofSize: 11, weight: .regular)
|
||||
groups[.obfuscation] = [obfuscatePopUp, label("Strength"), obfuscateSlider, obfuscateLabel]
|
||||
|
||||
configureToggle(shadowButton, title: "Shadow", action: #selector(shadowChanged))
|
||||
shadowButton.state = Configuration.shared.editorDropShadow ? .on : .off
|
||||
labelledPairs[.shadow] = [shadowButton]
|
||||
groups[.shadow] = [shadowButton]
|
||||
|
||||
cropButton.title = "Apply crop"
|
||||
cropButton.bezelStyle = .rounded
|
||||
@@ -119,6 +110,13 @@ final class StyleBarView: NSView {
|
||||
cropButton.action = #selector(applyCrop)
|
||||
}
|
||||
|
||||
private func label(_ text: String) -> NSTextField {
|
||||
let field = NSTextField(labelWithString: text)
|
||||
field.font = NSFont.systemFont(ofSize: 11)
|
||||
field.textColor = .secondaryLabelColor
|
||||
return field
|
||||
}
|
||||
|
||||
private func configureToggle(_ button: NSButton, title: String, action: Selector) {
|
||||
button.title = title
|
||||
button.setButtonType(.pushOnPushOff)
|
||||
@@ -127,16 +125,13 @@ final class StyleBarView: NSView {
|
||||
button.action = action
|
||||
}
|
||||
|
||||
/// Rebuilds the bar so it shows only the fields the current tool or selection supports.
|
||||
func update(capabilities: StyleCapabilities, style: ElementStyle, showCrop: Bool) {
|
||||
/// Rebuilds the row for the current tool or selection. The row itself never disappears.
|
||||
func update(capabilities: StyleCapabilities, style: ElementStyle, showCrop: Bool, toolName: String) {
|
||||
for view in stack.arrangedSubviews {
|
||||
stack.removeArrangedSubview(view)
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
|
||||
lineColorWell.color = style.lineColor
|
||||
fillColorWell.color = style.fillColor
|
||||
highlightColorWell.color = style.highlightColor
|
||||
thicknessSlider.doubleValue = Double(style.lineThickness)
|
||||
thicknessLabel.stringValue = "\(Int(style.lineThickness)) px"
|
||||
fontPopUp.selectItem(withTitle: style.fontFamily)
|
||||
@@ -144,40 +139,27 @@ final class StyleBarView: NSView {
|
||||
boldButton.state = style.bold ? .on : .off
|
||||
italicButton.state = style.italic ? .on : .off
|
||||
obfuscatePopUp.selectItem(withTitle: style.obfuscateMode.title)
|
||||
obfuscateSlider.doubleValue = Double(style.obfuscateMode == .pixelate ? style.pixelSize : style.blurRadius)
|
||||
let strength = style.obfuscateMode == .pixelate ? style.pixelSize : style.blurRadius
|
||||
obfuscateSlider.doubleValue = Double(strength)
|
||||
obfuscateLabel.stringValue = "\(Int(strength))"
|
||||
shadowButton.state = style.dropShadow ? .on : .off
|
||||
|
||||
for capability in [StyleCapabilities.lineColor, .fillColor, .highlightColor,
|
||||
.lineThickness, .font, .obfuscation, .shadow] {
|
||||
guard capabilities.contains(capability), let views = labelledPairs[capability] else { continue }
|
||||
// Colour is intentionally not offered here; the tool rail owns it.
|
||||
for capability in [StyleCapabilities.lineThickness, .font, .obfuscation, .shadow] {
|
||||
guard capabilities.contains(capability), let views = groups[capability] else { continue }
|
||||
for view in views { stack.addArrangedSubview(view) }
|
||||
}
|
||||
if showCrop {
|
||||
stack.addArrangedSubview(cropButton)
|
||||
}
|
||||
isHidden = stack.arrangedSubviews.isEmpty
|
||||
if stack.arrangedSubviews.isEmpty {
|
||||
placeholder.stringValue = "\(toolName) — no options"
|
||||
stack.addArrangedSubview(placeholder)
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Actions
|
||||
|
||||
@objc private func lineColorChanged() {
|
||||
let color = lineColorWell.color
|
||||
Configuration.shared.editorLineColor = color
|
||||
delegate?.styleBar(self) { $0.lineColor = color }
|
||||
}
|
||||
|
||||
@objc private func fillColorChanged() {
|
||||
let color = fillColorWell.color
|
||||
Configuration.shared.editorFillColor = color
|
||||
delegate?.styleBar(self) { $0.fillColor = color }
|
||||
}
|
||||
|
||||
@objc private func highlightColorChanged() {
|
||||
let color = highlightColorWell.color
|
||||
Configuration.shared.editorHighlightColor = color
|
||||
delegate?.styleBar(self) { $0.highlightColor = color }
|
||||
}
|
||||
|
||||
@objc private func thicknessChanged() {
|
||||
let thickness = CGFloat(thicknessSlider.doubleValue.rounded())
|
||||
thicknessLabel.stringValue = "\(Int(thickness)) px"
|
||||
@@ -203,6 +185,7 @@ final class StyleBarView: NSView {
|
||||
@objc private func obfuscationChanged() {
|
||||
let mode = ObfuscateMode.allCases[max(0, obfuscatePopUp.indexOfSelectedItem)]
|
||||
let value = CGFloat(obfuscateSlider.doubleValue.rounded())
|
||||
obfuscateLabel.stringValue = "\(Int(value))"
|
||||
if mode == .pixelate {
|
||||
Configuration.shared.editorPixelSize = Int(value)
|
||||
} else {
|
||||
|
||||
Reference in New Issue
Block a user