146 lines
4.3 KiB
Swift
146 lines
4.3 KiB
Swift
//
|
|
// MenuButton.swift
|
|
// MacBackend
|
|
//
|
|
// Created by david-swift on 22.10.23.
|
|
//
|
|
|
|
import AppKit
|
|
|
|
/// A button widget for menus.
|
|
public struct MenuButton: MenuWidget {
|
|
|
|
/// The button's label.
|
|
var label: String
|
|
/// The button's action handler.
|
|
var handler: () -> Void
|
|
/// The keyboard shortcut.
|
|
var shortcut: KeyboardShortcut?
|
|
/// Whether the button is selected.
|
|
var selected: Bool?
|
|
/// Whether the button is enabled.
|
|
var enabled = true
|
|
|
|
/// The action label.
|
|
var filteredLabel: String { label.filter { $0.isLetter || $0.isNumber || $0 == "-" || $0 == "." } }
|
|
|
|
/// Initialize a menu button.
|
|
/// - Parameters:
|
|
/// - label: The buttons label.
|
|
/// - handler: The button's action handler.
|
|
public init(_ label: String, handler: @escaping () -> Void) {
|
|
self.label = label
|
|
self.handler = handler
|
|
}
|
|
|
|
/// The view storage.
|
|
/// - Parameters:
|
|
/// - data: The widget data.
|
|
/// - type: The type of the views.
|
|
/// - Returns: The view storage.
|
|
public func container<Data>(
|
|
data: WidgetData,
|
|
type: Data.Type
|
|
) -> ViewStorage where Data: ViewRenderData {
|
|
let button = NSMenuItem()
|
|
let storage = ViewStorage(button)
|
|
update(storage, data: data, updateProperties: true, type: type)
|
|
return storage
|
|
}
|
|
|
|
/// Update the stored content.
|
|
/// - Parameters:
|
|
/// - storage: The storage to update.
|
|
/// - data: The widget data.
|
|
/// - updateProperties: Whether to update the properties.
|
|
/// - type: The type of the views.
|
|
public func update<Data>(
|
|
_ storage: ViewStorage,
|
|
data: WidgetData,
|
|
updateProperties: Bool,
|
|
type: Data.Type
|
|
) where Data: ViewRenderData {
|
|
guard let button = storage.pointer as? NSMenuItem else {
|
|
return
|
|
}
|
|
if enabled {
|
|
button.actionClosure = handler
|
|
} else {
|
|
button.action = nil
|
|
}
|
|
guard updateProperties else {
|
|
return
|
|
}
|
|
let previousState = storage.previousState as? Self
|
|
if previousState?.label != label {
|
|
button.title = label
|
|
}
|
|
if let shortcut, previousState?.shortcut != shortcut {
|
|
button.keyEquivalent = shortcut.character.macOSRepresentation
|
|
button.keyEquivalentModifierMask = shortcut.modifiers
|
|
}
|
|
if let selected, previousState?.selected != selected {
|
|
button.state = selected ? .on : .off
|
|
}
|
|
storage.previousState = self
|
|
}
|
|
|
|
/// Create a keyboard shortcut for an application from a button.
|
|
///
|
|
/// Note that the keyboard shortcut is available after the view has been visible for the first time.
|
|
/// - Parameter shortcut: The keyboard shortcut.
|
|
/// - Returns: The button.
|
|
public func keyboardShortcut(_ shortcut: KeyboardShortcut) -> Self {
|
|
modify { $0.shortcut = shortcut }
|
|
}
|
|
|
|
/// Create a keyboard shortcut for an application from a button.
|
|
///
|
|
/// Note that the keyboard shortcut is available after the view has been visible for the first time.
|
|
/// - Parameter shortcut: The keyboard shortcut.
|
|
/// - Returns: The button.
|
|
public func keyboardShortcut(_ shortcut: Character) -> Self {
|
|
modify { $0.shortcut = .init(shortcut) }
|
|
}
|
|
|
|
/// Whether the button is selected.
|
|
/// - Parameter selected: Whether it is selected.
|
|
/// - Returns: The button.
|
|
public func selected(_ selected: Bool = true) -> Self {
|
|
modify { $0.selected = selected }
|
|
}
|
|
|
|
/// Whether the button is enabled.
|
|
/// - Parameter enabled: Whether it is enabled.
|
|
/// - Returns: The button.
|
|
public func enabled(_ enabled: Bool = true) -> Self {
|
|
modify { $0.enabled = enabled }
|
|
}
|
|
|
|
}
|
|
|
|
extension NSMenuItem {
|
|
|
|
/// The closure key.
|
|
private static var closureKey: UInt8 = 0
|
|
|
|
/// The action closure.
|
|
var actionClosure: (() -> Void)? {
|
|
get {
|
|
objc_getAssociatedObject(self, &Self.closureKey) as? () -> Void
|
|
}
|
|
set {
|
|
objc_setAssociatedObject(self, &Self.closureKey, newValue, .OBJC_ASSOCIATION_RETAIN_NONATOMIC)
|
|
self.target = self
|
|
self.action = #selector(menuItemAction)
|
|
}
|
|
}
|
|
|
|
/// The action closure.
|
|
@objc
|
|
private func menuItemAction() {
|
|
actionClosure?()
|
|
}
|
|
|
|
}
|