mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-15 00:34:08 +09:00
feat: added an option to control fans from the Sensors module
This commit is contained in:
@@ -237,7 +237,7 @@ public extension NSBezierPath {
|
||||
}
|
||||
}
|
||||
|
||||
public func separatorView(_ title: String, origin: NSPoint, width: CGFloat) -> NSView {
|
||||
public func separatorView(_ title: String, origin: NSPoint = NSPoint(x: 0, y: 0), width: CGFloat) -> NSView {
|
||||
let view: NSView = NSView(frame: NSRect(x: origin.x, y: origin.y, width: width, height: 30))
|
||||
view.heightAnchor.constraint(equalToConstant: view.bounds.height).isActive = true
|
||||
|
||||
|
||||
@@ -12,13 +12,16 @@
|
||||
import Cocoa
|
||||
import Kit
|
||||
|
||||
internal class Popup: NSView, Popup_p {
|
||||
private var list: [String: NSTextField] = [:]
|
||||
internal class Popup: NSStackView, Popup_p {
|
||||
private var list: [String: NSView] = [:]
|
||||
|
||||
public var sizeCallback: ((NSSize) -> Void)? = nil
|
||||
|
||||
public init() {
|
||||
super.init(frame: NSRect( x: 0, y: 0, width: Constants.Popup.width, height: 0))
|
||||
|
||||
self.orientation = .vertical
|
||||
self.spacing = 0
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
@@ -26,24 +29,50 @@ internal class Popup: NSView, Popup_p {
|
||||
}
|
||||
|
||||
internal func setup(_ values: [Sensor_p]?) {
|
||||
guard values != nil else {
|
||||
guard let fans = values?.filter({ $0.type == .fan }),
|
||||
let sensors = values?.filter({ $0.type != .fan }) else {
|
||||
return
|
||||
}
|
||||
|
||||
var types: [SensorType] = []
|
||||
values!.forEach { (s: Sensor_p) in
|
||||
if !types.contains(s.type) {
|
||||
types.append(s.type)
|
||||
}
|
||||
}
|
||||
|
||||
self.subviews.forEach { (v: NSView) in
|
||||
v.removeFromSuperview()
|
||||
}
|
||||
|
||||
var y: CGFloat = 0
|
||||
types.reversed().forEach { (typ: SensorType) in
|
||||
let filtered = values!.filter{ $0.type == typ }
|
||||
if !fans.isEmpty {
|
||||
let container = NSStackView()
|
||||
container.orientation = .vertical
|
||||
container.spacing = Constants.Popup.margins
|
||||
|
||||
fans.forEach { (f: Sensor_p) in
|
||||
if let fan = f as? Fan {
|
||||
let view = FanView(fan, width: self.frame.width) { [weak self] in
|
||||
let h = container.arrangedSubviews.map({ $0.bounds.height + container.spacing }).reduce(0, +) - container.spacing
|
||||
if container.frame.size.height != h {
|
||||
container.setFrameSize(NSSize(width: container.frame.width, height: h))
|
||||
}
|
||||
self?.recalculateHeight()
|
||||
}
|
||||
self.list[fan.key] = view
|
||||
container.addArrangedSubview(view)
|
||||
}
|
||||
}
|
||||
|
||||
let h = container.arrangedSubviews.map({ $0.bounds.height + container.spacing }).reduce(0, +) - container.spacing
|
||||
if container.frame.size.height != h {
|
||||
container.setFrameSize(NSSize(width: container.frame.width, height: h))
|
||||
}
|
||||
self.addArrangedSubview(container)
|
||||
}
|
||||
|
||||
var types: [SensorType] = []
|
||||
sensors.forEach { (s: Sensor_p) in
|
||||
if !types.contains(s.type) {
|
||||
types.append(s.type)
|
||||
}
|
||||
}
|
||||
|
||||
types.forEach { (typ: SensorType) in
|
||||
let filtered = sensors.filter{ $0.type == typ }
|
||||
var groups: [SensorGroup] = []
|
||||
filtered.forEach { (s: Sensor_p) in
|
||||
if !groups.contains(s.group) {
|
||||
@@ -51,38 +80,515 @@ internal class Popup: NSView, Popup_p {
|
||||
}
|
||||
}
|
||||
|
||||
let height: CGFloat = CGFloat((22*filtered.count)) + Constants.Popup.separatorHeight
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: y, width: self.frame.width, height: height))
|
||||
let separator = separatorView(localizedString(typ.rawValue), origin: NSPoint(x: 0, y: view.frame.height - Constants.Popup.separatorHeight), width: self.frame.width)
|
||||
view.addSubview(separator)
|
||||
self.addArrangedSubview(separatorView(
|
||||
localizedString(typ.rawValue),
|
||||
width: self.frame.width
|
||||
))
|
||||
|
||||
var i: CGFloat = 0
|
||||
groups.reversed().forEach { (group: SensorGroup) in
|
||||
filtered.reversed().filter{ $0.group == group }.forEach { (s: Sensor_p) in
|
||||
let (key, value) = popupRow(view, n: i, title: "\(s.name):", value: s.formattedValue)
|
||||
filtered.filter{ $0.group == group }.forEach { (s: Sensor_p) in
|
||||
let (key, value) = popupRow(self, n: 0, title: "\(s.name):", value: s.formattedValue)
|
||||
key.toolTip = s.key
|
||||
self.list[s.key] = value
|
||||
i += 1
|
||||
}
|
||||
}
|
||||
|
||||
self.addSubview(view)
|
||||
y += height
|
||||
}
|
||||
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: y - Constants.Popup.margins))
|
||||
self.sizeCallback?(self.frame.size)
|
||||
self.recalculateHeight()
|
||||
}
|
||||
|
||||
// swiftlint:disable empty_enum_arguments
|
||||
internal func usageCallback(_ values: [Sensor_p]) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if self.window?.isVisible ?? false {
|
||||
values.forEach { (s: Sensor_p) in
|
||||
if self.list[s.key] != nil {
|
||||
self.list[s.key]?.stringValue = s.formattedValue
|
||||
switch self.list[s.key] {
|
||||
case let fan as FanView:
|
||||
if let f = s as? Fan {
|
||||
fan.update(f)
|
||||
}
|
||||
case let sensors as NSTextField:
|
||||
sensors.stringValue = s.formattedValue
|
||||
case .none, .some(_):
|
||||
break
|
||||
}
|
||||
}
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
private func recalculateHeight() {
|
||||
let h = self.arrangedSubviews.map({ $0.bounds.height + self.spacing }).reduce(0, +) - self.spacing
|
||||
if self.frame.size.height != h {
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
self.sizeCallback?(self.frame.size)
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
internal class FanView: NSStackView {
|
||||
public var sizeCallback: (() -> Void)
|
||||
|
||||
private var fan: Fan
|
||||
private var ready: Bool = false
|
||||
|
||||
private var valueField: NSTextField? = nil
|
||||
private var percentageField: NSTextField? = nil
|
||||
private var sliderValueField: NSTextField? = nil
|
||||
|
||||
private var slider: NSSlider? = nil
|
||||
private var controlView: NSView? = nil
|
||||
private var modeButtons: ModeButtons? = nil
|
||||
private var debouncer: DispatchWorkItem? = nil
|
||||
|
||||
private var minBtn: NSButton? = nil
|
||||
private var maxBtn: NSButton? = nil
|
||||
|
||||
private var speedState: Bool {
|
||||
get {
|
||||
return Store.shared.bool(key: "Fans_speed", defaultValue: false)
|
||||
}
|
||||
}
|
||||
private var speedValue: Int? {
|
||||
get {
|
||||
if !Store.shared.exist(key: "fan_\(self.fan.id)_speed") {
|
||||
return nil
|
||||
}
|
||||
return Store.shared.int(key: "fan_\(self.fan.id)_speed", defaultValue: Int(self.fan.minSpeed))
|
||||
}
|
||||
set {
|
||||
if let value = newValue {
|
||||
Store.shared.set(key: "fan_\(self.fan.id)_speed", value: value)
|
||||
} else {
|
||||
Store.shared.remove("fan_\(self.fan.id)_speed")
|
||||
}
|
||||
}
|
||||
}
|
||||
private var speed: Double {
|
||||
get {
|
||||
if let v = self.speedValue, self.speedState {
|
||||
return Double(v)
|
||||
}
|
||||
return self.fan.value
|
||||
}
|
||||
}
|
||||
private var resetModeAfterSleep: Bool = false
|
||||
|
||||
public init(_ fan: Fan, width: CGFloat, callback: @escaping (() -> Void)) {
|
||||
self.fan = fan
|
||||
self.sizeCallback = callback
|
||||
|
||||
let inset: CGFloat = 5
|
||||
super.init(frame: NSRect(x: 0, y: 0, width: width - (inset*2), height: 0))
|
||||
|
||||
self.controlView = self.control()
|
||||
|
||||
self.orientation = .vertical
|
||||
self.alignment = .centerX
|
||||
self.distribution = .fillProportionally
|
||||
self.spacing = 0
|
||||
self.edgeInsets = NSEdgeInsets(top: inset, left: inset, bottom: inset, right: inset)
|
||||
self.wantsLayer = true
|
||||
self.layer?.cornerRadius = 2
|
||||
self.layer?.backgroundColor = NSColor.red.cgColor
|
||||
|
||||
self.addArrangedSubview(self.nameAndSpeed())
|
||||
self.addArrangedSubview(self.keyAndPercentage())
|
||||
self.addArrangedSubview(self.mode())
|
||||
|
||||
if let view = self.controlView, fan.mode == .forced {
|
||||
self.addArrangedSubview(view)
|
||||
}
|
||||
|
||||
let h = self.arrangedSubviews.map({ $0.bounds.height }).reduce(0, +) + (inset*2)
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
self.sizeCallback()
|
||||
|
||||
NSWorkspace.shared.notificationCenter.addObserver(self, selector: #selector(self.wakeListener), name: NSWorkspace.didWakeNotification, object: nil)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
deinit {
|
||||
NSWorkspace.shared.notificationCenter.removeObserver(self)
|
||||
}
|
||||
|
||||
override func updateLayer() {
|
||||
self.layer?.backgroundColor = isDarkMode ? NSColor(hexString: "#111111", alpha: 0.25).cgColor : NSColor(hexString: "#f5f5f5", alpha: 1).cgColor
|
||||
}
|
||||
|
||||
private func nameAndSpeed() -> NSView {
|
||||
let row: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 16))
|
||||
row.heightAnchor.constraint(equalToConstant: row.bounds.height).isActive = true
|
||||
|
||||
let valueWidth: CGFloat = 80
|
||||
let nameField: NSTextField = TextView(frame: NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: row.frame.width - valueWidth,
|
||||
height: row.frame.height
|
||||
))
|
||||
nameField.stringValue = self.fan.name
|
||||
nameField.cell?.truncatesLastVisibleLine = true
|
||||
|
||||
let valueField: NSTextField = TextView(frame: NSRect(
|
||||
x: row.frame.width - valueWidth,
|
||||
y: 0,
|
||||
width: valueWidth,
|
||||
height: row.frame.height
|
||||
))
|
||||
valueField.font = NSFont.systemFont(ofSize: 13, weight: .regular)
|
||||
valueField.stringValue = self.fan.formattedValue
|
||||
valueField.alignment = .right
|
||||
|
||||
row.addSubview(nameField)
|
||||
row.addSubview(valueField)
|
||||
self.valueField = valueField
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
private func keyAndPercentage() -> NSView {
|
||||
let row: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 14))
|
||||
row.heightAnchor.constraint(equalToConstant: row.bounds.height).isActive = true
|
||||
|
||||
let value = self.fan.value
|
||||
var percentage = ""
|
||||
if value != 1 && self.fan.maxSpeed != 1 {
|
||||
percentage = "\((100*Int(value)) / Int(self.fan.maxSpeed))%"
|
||||
}
|
||||
let percentageWidth: CGFloat = 40
|
||||
|
||||
let keyField: NSTextField = TextView(frame: NSRect(
|
||||
x: 0,
|
||||
y: 0,
|
||||
width: row.frame.width - percentageWidth,
|
||||
height: row.frame.height
|
||||
))
|
||||
keyField.font = NSFont.systemFont(ofSize: 11, weight: .light)
|
||||
keyField.textColor = .secondaryLabelColor
|
||||
keyField.stringValue = "Fan #\(self.fan.id)"
|
||||
keyField.alignment = .left
|
||||
|
||||
let percentageField: NSTextField = TextView(frame: NSRect(
|
||||
x: row.frame.width - percentageWidth,
|
||||
y: 0,
|
||||
width: percentageWidth,
|
||||
height: row.frame.height
|
||||
))
|
||||
percentageField.font = NSFont.systemFont(ofSize: 11, weight: .light)
|
||||
percentageField.textColor = .secondaryLabelColor
|
||||
percentageField.stringValue = percentage
|
||||
percentageField.alignment = .right
|
||||
|
||||
row.addSubview(keyField)
|
||||
row.addSubview(percentageField)
|
||||
self.percentageField = percentageField
|
||||
|
||||
return row
|
||||
}
|
||||
|
||||
private func mode() -> NSView {
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 30))
|
||||
view.heightAnchor.constraint(equalToConstant: view.bounds.height).isActive = true
|
||||
|
||||
let buttons = ModeButtons(frame: NSRect(
|
||||
x: 0,
|
||||
y: 4,
|
||||
width: view.frame.width,
|
||||
height: view.frame.height - 8
|
||||
), mode: self.fan.mode)
|
||||
buttons.callback = { [weak self] (mode: FanMode) in
|
||||
if let fan = self?.fan, fan.mode != mode {
|
||||
self?.fan.mode = mode
|
||||
SMCHelper.shared.setFanMode(fan.id, mode: mode.rawValue)
|
||||
}
|
||||
self?.toggleControlView(mode == .forced)
|
||||
}
|
||||
buttons.turbo = { [weak self] in
|
||||
if let fan = self?.fan {
|
||||
if self?.fan.mode != .forced {
|
||||
self?.fan.mode = .forced
|
||||
SMCHelper.shared.setFanMode(fan.id, mode: FanMode.forced.rawValue)
|
||||
}
|
||||
SMCHelper.shared.setFanSpeed(fan.id, speed: Int(fan.maxSpeed))
|
||||
}
|
||||
self?.toggleControlView(false)
|
||||
}
|
||||
|
||||
view.addSubview(buttons)
|
||||
self.modeButtons = buttons
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private func control() -> NSView {
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 46))
|
||||
view.identifier = NSUserInterfaceItemIdentifier(rawValue: "control")
|
||||
view.heightAnchor.constraint(equalToConstant: view.bounds.height).isActive = true
|
||||
|
||||
let controls: NSStackView = NSStackView(frame: NSRect(x: 0, y: 14, width: view.frame.width, height: 30))
|
||||
controls.orientation = .horizontal
|
||||
controls.spacing = 0
|
||||
|
||||
let slider: NSSlider = NSSlider(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 26))
|
||||
slider.minValue = self.fan.minSpeed
|
||||
slider.maxValue = self.fan.maxSpeed
|
||||
slider.doubleValue = self.speed
|
||||
slider.isContinuous = true
|
||||
slider.action = #selector(self.sliderCallback)
|
||||
slider.target = self
|
||||
|
||||
let levels: NSView = NSView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 16))
|
||||
|
||||
let minBtn: NSButton = NSButton(frame: NSRect(x: 0, y: 0, width: 50, height: levels.frame.height))
|
||||
minBtn.title = "\(Int(self.fan.minSpeed))"
|
||||
minBtn.toolTip = localizedString("Min")
|
||||
minBtn.setButtonType(.toggle)
|
||||
minBtn.isBordered = false
|
||||
minBtn.target = self
|
||||
minBtn.state = .off
|
||||
minBtn.action = #selector(self.setMin)
|
||||
minBtn.wantsLayer = true
|
||||
minBtn.layer?.cornerRadius = 3
|
||||
minBtn.layer?.borderWidth = 1
|
||||
minBtn.layer?.borderColor = NSColor.lightGray.cgColor
|
||||
|
||||
let valueField: NSTextField = TextView(frame: NSRect(x: 80, y: 0, width: levels.frame.width - 160, height: levels.frame.height))
|
||||
valueField.font = NSFont.systemFont(ofSize: 11, weight: .light)
|
||||
valueField.textColor = .secondaryLabelColor
|
||||
valueField.alignment = .center
|
||||
|
||||
let maxBtn: NSButton = NSButton(frame: NSRect(x: levels.frame.width - 50, y: 0, width: 50, height: levels.frame.height))
|
||||
maxBtn.title = "\(Int(self.fan.maxSpeed))"
|
||||
maxBtn.toolTip = localizedString("Max")
|
||||
maxBtn.setButtonType(.toggle)
|
||||
maxBtn.isBordered = false
|
||||
maxBtn.target = self
|
||||
maxBtn.state = .off
|
||||
maxBtn.wantsLayer = true
|
||||
maxBtn.action = #selector(self.setMax)
|
||||
maxBtn.layer?.cornerRadius = 3
|
||||
maxBtn.layer?.borderWidth = 1
|
||||
maxBtn.layer?.borderColor = NSColor.lightGray.cgColor
|
||||
|
||||
controls.addArrangedSubview(slider)
|
||||
|
||||
levels.addSubview(minBtn)
|
||||
levels.addSubview(valueField)
|
||||
levels.addSubview(maxBtn)
|
||||
|
||||
view.addSubview(controls)
|
||||
view.addSubview(levels)
|
||||
|
||||
self.slider = slider
|
||||
self.sliderValueField = valueField
|
||||
self.minBtn = minBtn
|
||||
self.maxBtn = maxBtn
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
private func toggleControlView(_ state: Bool) {
|
||||
guard let view = self.controlView else {
|
||||
return
|
||||
}
|
||||
|
||||
if state {
|
||||
self.slider?.doubleValue = self.speed
|
||||
if self.speedState {
|
||||
self.setSpeed(value: Int(self.speed), then: {
|
||||
DispatchQueue.main.async {
|
||||
self.sliderValueField?.textColor = .systemBlue
|
||||
}
|
||||
})
|
||||
}
|
||||
self.addArrangedSubview(view)
|
||||
} else {
|
||||
self.sliderValueField?.stringValue = ""
|
||||
self.sliderValueField?.textColor = .secondaryLabelColor
|
||||
self.minBtn?.state = .off
|
||||
self.maxBtn?.state = .off
|
||||
view.removeFromSuperview()
|
||||
}
|
||||
|
||||
let h = self.arrangedSubviews.map({ $0.bounds.height }).reduce(0, +) + 10
|
||||
self.setFrameSize(NSSize(width: self.frame.width, height: h))
|
||||
self.sizeCallback()
|
||||
}
|
||||
|
||||
private func setSpeed(value: Int, then: @escaping () -> Void = {}) {
|
||||
self.sliderValueField?.stringValue = "\(value) RPM"
|
||||
self.sliderValueField?.textColor = .secondaryLabelColor
|
||||
self.speedValue = value
|
||||
|
||||
self.debouncer?.cancel()
|
||||
|
||||
let task = DispatchWorkItem { [weak self] in
|
||||
DispatchQueue.global(qos: .userInteractive).async { [weak self] in
|
||||
if let id = self?.fan.id {
|
||||
SMCHelper.shared.setFanSpeed(id, speed: value)
|
||||
}
|
||||
then()
|
||||
}
|
||||
}
|
||||
|
||||
self.debouncer = task
|
||||
DispatchQueue.main.asyncAfter(deadline: DispatchTime.now() + 0.3, execute: task)
|
||||
}
|
||||
|
||||
@objc private func sliderCallback(_ sender: NSSlider) {
|
||||
let value = sender.doubleValue
|
||||
|
||||
self.minBtn?.state = .off
|
||||
self.maxBtn?.state = .off
|
||||
|
||||
self.setSpeed(value: Int(value), then: {
|
||||
DispatchQueue.main.async {
|
||||
self.sliderValueField?.textColor = .systemBlue
|
||||
}
|
||||
})
|
||||
}
|
||||
|
||||
@objc func setMin(_ sender: NSButton) {
|
||||
self.slider?.doubleValue = self.fan.minSpeed
|
||||
self.maxBtn?.state = .off
|
||||
self.setSpeed(value: Int(self.fan.minSpeed))
|
||||
}
|
||||
|
||||
@objc func setMax(_ sender: NSButton) {
|
||||
self.slider?.doubleValue = self.fan.maxSpeed
|
||||
self.minBtn?.state = .off
|
||||
self.setSpeed(value: Int(self.fan.maxSpeed))
|
||||
}
|
||||
|
||||
@objc private func wakeListener(aNotification: NSNotification) {
|
||||
self.resetModeAfterSleep = true
|
||||
}
|
||||
|
||||
public func update(_ value: Fan) {
|
||||
DispatchQueue.main.async(execute: {
|
||||
if (self.window?.isVisible ?? false) || !self.ready {
|
||||
self.fan.value = value.value
|
||||
|
||||
var percentage = ""
|
||||
if value.value != 1 && self.fan.maxSpeed != 1 {
|
||||
percentage = "\((100*Int(value.value)) / Int(self.fan.maxSpeed))%"
|
||||
}
|
||||
|
||||
self.percentageField?.stringValue = percentage
|
||||
self.valueField?.stringValue = value.formattedValue
|
||||
|
||||
if self.resetModeAfterSleep && value.mode != .automatic {
|
||||
if self.sliderValueField?.stringValue != "" && self.slider?.doubleValue != value.value {
|
||||
self.slider?.doubleValue = value.value
|
||||
self.sliderValueField?.stringValue = ""
|
||||
}
|
||||
self.modeButtons?.setManualMode()
|
||||
self.resetModeAfterSleep = false
|
||||
}
|
||||
|
||||
self.ready = true
|
||||
}
|
||||
})
|
||||
}
|
||||
}
|
||||
|
||||
private class ModeButtons: NSStackView {
|
||||
public var callback: (FanMode) -> Void = {_ in }
|
||||
public var turbo: () -> Void = {}
|
||||
|
||||
private var autoBtn: NSButton = NSButton(title: localizedString("Automatic"), target: nil, action: #selector(autoMode))
|
||||
private var manualBtn: NSButton = NSButton(title: localizedString("Manual"), target: nil, action: #selector(manualMode))
|
||||
private var turboBtn: NSButton = NSButton(image: NSImage(named: NSImage.Name("ac_unit"))!, target: nil, action: #selector(turboMode))
|
||||
|
||||
public init(frame: NSRect, mode: FanMode) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.orientation = .horizontal
|
||||
self.alignment = .centerY
|
||||
self.distribution = .fillProportionally
|
||||
self.spacing = 0
|
||||
self.wantsLayer = true
|
||||
self.layer?.cornerRadius = 3
|
||||
self.layer?.borderWidth = 1
|
||||
self.layer?.borderColor = NSColor.lightGray.cgColor
|
||||
|
||||
let modes: NSStackView = NSStackView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
|
||||
modes.orientation = .horizontal
|
||||
modes.alignment = .centerY
|
||||
modes.distribution = .fillEqually
|
||||
|
||||
self.autoBtn.setButtonType(.toggle)
|
||||
self.autoBtn.isBordered = false
|
||||
self.autoBtn.target = self
|
||||
self.autoBtn.state = mode == .automatic ? .on : .off
|
||||
|
||||
self.manualBtn.setButtonType(.toggle)
|
||||
self.manualBtn.isBordered = false
|
||||
self.manualBtn.target = self
|
||||
self.manualBtn.state = mode == .forced ? .on : .off
|
||||
|
||||
modes.addArrangedSubview(self.autoBtn)
|
||||
modes.addArrangedSubview(self.manualBtn)
|
||||
|
||||
self.turboBtn.setButtonType(.toggle)
|
||||
self.turboBtn.isBordered = false
|
||||
self.turboBtn.target = self
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
self.turboBtn.widthAnchor.constraint(equalToConstant: 26),
|
||||
self.turboBtn.heightAnchor.constraint(equalToConstant: self.frame.height),
|
||||
modes.heightAnchor.constraint(equalToConstant: self.frame.height)
|
||||
])
|
||||
|
||||
self.addArrangedSubview(modes)
|
||||
self.addArrangedSubview(self.turboBtn)
|
||||
}
|
||||
|
||||
required init?(coder: NSCoder) {
|
||||
fatalError("init(coder:) has not been implemented")
|
||||
}
|
||||
|
||||
@objc private func autoMode(_ sender: NSButton) {
|
||||
if sender.state.rawValue == 0 {
|
||||
self.autoBtn.state = .on
|
||||
return
|
||||
}
|
||||
|
||||
self.manualBtn.state = .off
|
||||
self.turboBtn.state = .off
|
||||
self.callback(.automatic)
|
||||
}
|
||||
|
||||
@objc private func manualMode(_ sender: NSButton) {
|
||||
if sender.state.rawValue == 0 {
|
||||
self.manualBtn.state = .on
|
||||
return
|
||||
}
|
||||
|
||||
self.autoBtn.state = .off
|
||||
self.turboBtn.state = .off
|
||||
self.callback(.forced)
|
||||
}
|
||||
|
||||
@objc private func turboMode(_ sender: NSButton) {
|
||||
if sender.state.rawValue == 0 {
|
||||
self.turboBtn.state = .on
|
||||
return
|
||||
}
|
||||
|
||||
self.manualBtn.state = .off
|
||||
self.autoBtn.state = .off
|
||||
self.turbo()
|
||||
}
|
||||
|
||||
public func setManualMode() {
|
||||
self.manualBtn.state = .on
|
||||
self.autoBtn.state = .off
|
||||
self.turboBtn.state = .off
|
||||
self.callback(.forced)
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user