127 lines
5.5 KiB
Swift
127 lines
5.5 KiB
Swift
import Cocoa
|
|
|
|
class ImageTextButtonView: NSStackView {
|
|
class ImageButton: NSButton {
|
|
override var focusRingMaskBounds: NSRect {
|
|
return bounds
|
|
}
|
|
|
|
override func drawFocusRingMask() {
|
|
let path = NSBezierPath(roundedRect: bounds.insetBy(dx: 1, dy: 1),
|
|
xRadius: ImageTextButtonView.cornerRadius, yRadius: ImageTextButtonView.cornerRadius)
|
|
path.fill()
|
|
}
|
|
|
|
override func draw(_ dirtyRect: NSRect) {
|
|
super.draw(dirtyRect)
|
|
if window?.firstResponder == self && NSApp.isActive {
|
|
let path = NSBezierPath(roundedRect: bounds.insetBy(dx: 1, dy: 1),
|
|
xRadius: ImageTextButtonView.cornerRadius, yRadius: ImageTextButtonView.cornerRadius)
|
|
path.lineWidth = 2.0
|
|
path.stroke()
|
|
}
|
|
}
|
|
}
|
|
|
|
static let spacing = CGFloat(5)
|
|
static let cornerRadius = CGFloat(7)
|
|
static let borderWidth = CGFloat(3)
|
|
static let padding = CGFloat(0)
|
|
|
|
var onClick: ActionClosure?
|
|
var button: NSButton!
|
|
var label: NSTextField!
|
|
|
|
var state: NSControl.StateValue = .off {
|
|
didSet {
|
|
button.state = state
|
|
updateStyle()
|
|
}
|
|
}
|
|
|
|
init(title: String, rawName: String, image: WidthHeightImage,
|
|
state: NSControl.StateValue = .off,
|
|
spacing: CGFloat = ImageTextButtonView.spacing,
|
|
cornerRadius: CGFloat = ImageTextButtonView.cornerRadius) {
|
|
super.init(frame: .zero)
|
|
orientation = .vertical
|
|
alignment = .centerX
|
|
self.spacing = spacing
|
|
translatesAutoresizingMaskIntoConstraints = false
|
|
makeButton(rawName, state, image, cornerRadius: cornerRadius)
|
|
makeLabel(title)
|
|
self.state = state
|
|
}
|
|
|
|
required init?(coder: NSCoder) {
|
|
fatalError("Class only supports programmatic initialization")
|
|
}
|
|
|
|
private func makeButton(_ rawName: String, _ state: NSControl.StateValue, _ image: WidthHeightImage,
|
|
padding: CGFloat = ImageTextButtonView.padding,
|
|
cornerRadius: CGFloat = ImageTextButtonView.cornerRadius) {
|
|
button = ImageButton(radioButtonWithTitle: "", target: nil, action: nil)
|
|
button.imagePosition = .imageOnly
|
|
button.focusRingType = .default
|
|
button.translatesAutoresizingMaskIntoConstraints = false
|
|
button.wantsLayer = true
|
|
button.layer?.cornerRadius = cornerRadius
|
|
button.layer?.borderWidth = ImageTextButtonView.borderWidth
|
|
button.state = state
|
|
addArrangedSubview(button)
|
|
// Create an NSView to contain the image and provide padding
|
|
let imageContainer = NSView()
|
|
imageContainer.translatesAutoresizingMaskIntoConstraints = false
|
|
// TODO: The appearance theme functionality has not been implemented yet.
|
|
// We will implement it later; for now, use the light theme.
|
|
let imageView = NSImageView(image: NSImage(named: image.name + "_light")!)
|
|
imageView.translatesAutoresizingMaskIntoConstraints = false
|
|
imageView.imageScaling = .scaleProportionallyUpOrDown
|
|
imageView.wantsLayer = true
|
|
imageView.layer?.cornerRadius = cornerRadius - 3
|
|
imageContainer.addSubview(imageView)
|
|
NSLayoutConstraint.activate([
|
|
imageView.topAnchor.constraint(equalTo: imageContainer.topAnchor, constant: padding),
|
|
imageView.bottomAnchor.constraint(equalTo: imageContainer.bottomAnchor, constant: -padding),
|
|
imageView.leadingAnchor.constraint(equalTo: imageContainer.leadingAnchor, constant: padding),
|
|
imageView.trailingAnchor.constraint(equalTo: imageContainer.trailingAnchor, constant: -padding),
|
|
])
|
|
button.addSubview(imageContainer)
|
|
let imageAspectRatio = image.height / image.width
|
|
NSLayoutConstraint.activate([
|
|
imageContainer.topAnchor.constraint(equalTo: button.topAnchor),
|
|
imageContainer.bottomAnchor.constraint(equalTo: button.bottomAnchor),
|
|
imageContainer.leadingAnchor.constraint(equalTo: button.leadingAnchor),
|
|
imageContainer.trailingAnchor.constraint(equalTo: button.trailingAnchor),
|
|
button.topAnchor.constraint(equalTo: topAnchor, constant: ImageTextButtonView.borderWidth),
|
|
button.leadingAnchor.constraint(equalTo: leadingAnchor, constant: ImageTextButtonView.borderWidth),
|
|
button.trailingAnchor.constraint(equalTo: trailingAnchor, constant: -ImageTextButtonView.borderWidth),
|
|
button.heightAnchor.constraint(equalTo: button.widthAnchor, multiplier: imageAspectRatio),
|
|
])
|
|
button.identifier = NSUserInterfaceItemIdentifier(rawName)
|
|
button.onAction = { control in
|
|
self.state = .on
|
|
self.onClick?(control)
|
|
}
|
|
}
|
|
|
|
private func makeLabel(_ labelText: String) {
|
|
label = NSTextField(labelWithString: labelText)
|
|
label.alignment = .center
|
|
label.translatesAutoresizingMaskIntoConstraints = false
|
|
addArrangedSubview(label)
|
|
}
|
|
|
|
override func draw(_ dirtyRect: NSRect) {
|
|
super.draw(dirtyRect)
|
|
updateStyle()
|
|
}
|
|
|
|
func updateStyle() {
|
|
let isSelected = button.state == .on
|
|
button.layer?.borderColor = isSelected ? NSColor.systemAccentColor.cgColor : NSColor.lightGray.withAlphaComponent(0.3).cgColor
|
|
button.layer?.borderWidth = isSelected ? ImageTextButtonView.borderWidth : ImageTextButtonView.borderWidth
|
|
label.font = isSelected ? NSFont.boldSystemFont(ofSize: 12) : NSFont.systemFont(ofSize: 12)
|
|
}
|
|
}
|