macbackend/Sources/MacBackend/Menu/MenuButton.swift
david-swift e989bea14e
All checks were successful
Deploy Docs / publish (push) Successful in 2m38s
Initial commit
2024-12-02 22:14:13 +01:00

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?()
}
}