feat: added portal for the Battery module

This commit is contained in:
Serhiy Mytrovtsiy
2023-03-16 17:50:29 +01:00
parent 4070e49560
commit c46aa38f3f
4 changed files with 154 additions and 24 deletions

View File

@@ -46,10 +46,12 @@ struct Battery_Usage: value_t {
}
public class Battery: Module {
private let popupView: Popup
private let settingsView: Settings
private let portalView: Portal
private var usageReader: UsageReader? = nil
private var processReader: ProcessReader? = nil
private let popupView: Popup
private var settingsView: Settings
private var lowLevelNotificationState: Bool = false
private var highLevelNotificationState: Bool = false
@@ -58,10 +60,12 @@ public class Battery: Module {
public init() {
self.settingsView = Settings("Battery")
self.popupView = Popup("Battery")
self.portalView = Portal("Battery")
super.init(
popup: self.popupView,
settings: self.settingsView
settings: self.settingsView,
portal: self.portalView
)
guard self.available else { return }
@@ -123,6 +127,7 @@ public class Battery: Module {
self.checkLowNotification(value: value)
self.checkHighNotification(value: value)
self.popupView.usageCallback(value)
self.portalView.loadCallback(value)
self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in
switch w.item {

View File

@@ -57,10 +57,10 @@ internal class Popup: NSView, Popup_p {
private var processes: [ProcessView] = []
private var processesInitialized: Bool = false
private var colorState: Bool = false
private var numberOfProcesses: Int {
get {
return Store.shared.int(key: "\(self.title)_processes", defaultValue: 8)
}
return Store.shared.int(key: "\(self.title)_processes", defaultValue: 8)
}
private var processesHeight: CGFloat {
get {
@@ -69,9 +69,7 @@ internal class Popup: NSView, Popup_p {
}
}
private var timeFormat: String {
get {
return Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: "short")
}
Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: "short")
}
public var sizeCallback: ((NSSize) -> Void)? = nil
@@ -87,6 +85,8 @@ internal class Popup: NSView, Popup_p {
))
self.setFrameSize(NSSize(width: self.frame.width, height: self.frame.height + self.detailsHeight + self.processesHeight))
self.colorState = Store.shared.bool(key: "\(self.title)_color", defaultValue: self.colorState)
let gridView: NSGridView = NSGridView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: self.frame.height))
gridView.rowSpacing = 0
gridView.yPlacement = .fill
@@ -347,14 +347,32 @@ internal class Popup: NSView, Popup_p {
// MARK: - Settings
public func settings() -> NSView? {
return nil
let view = SettingsContainerView()
view.addArrangedSubview(toggleSettingRow(
title: localizedString("Colorize battery"),
action: #selector(toggleColor),
state: self.colorState
))
return view
}
@objc private func toggleColor(_ sender: NSControl) {
self.colorState = controlState(sender)
Store.shared.set(key: "\(self.title)_color", value: self.colorState)
self.dashboardBatteryView?.display()
}
}
private class BatteryView: NSView {
internal class BatteryView: NSView {
private var percentage: Double = 0
public override init(frame: NSRect) {
private var colorState: Bool {
return Store.shared.bool(key: "Battery_color", defaultValue: false)
}
public override init(frame: NSRect = NSRect.zero) {
super.init(frame: frame)
}
@@ -365,27 +383,41 @@ private class BatteryView: NSView {
public override func draw(_ dirtyRect: NSRect) {
super.draw(dirtyRect)
let w: CGFloat = 130
let h: CGFloat = 50
guard let ctx = NSGraphicsContext.current?.cgContext else { return }
let w: CGFloat = min(dirtyRect.width, 120)
let h: CGFloat = min(dirtyRect.height, 50)
let x: CGFloat = (dirtyRect.width - w)/2
let y: CGFloat = (dirtyRect.size.height - h) / 2
let radius: CGFloat = 3
let batteryFrame = NSBezierPath(roundedRect: NSRect(x: x+1, y: y, width: w, height: h), xRadius: radius, yRadius: radius)
let batteryFrame = NSBezierPath(roundedRect: NSRect(x: x+1, y: y+1, width: w-8, height: h-2), xRadius: 3, yRadius: 3)
NSColor.textColor.set()
let bPX: CGFloat = x+w+1
let bPY: CGFloat = (dirtyRect.size.height / 2) - 4
let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX, y: bPY, width: 4, height: 8), xRadius: radius, yRadius: radius)
batteryPoint.lineWidth = 1.1
batteryPoint.stroke()
let bPX: CGFloat = batteryFrame.bounds.origin.x + batteryFrame.bounds.width
let bPY: CGFloat = batteryFrame.bounds.origin.y + (batteryFrame.bounds.height/2) - 4
let batteryPoint = NSBezierPath(roundedRect: NSRect(x: bPX-2, y: bPY, width: 8, height: 8), xRadius: 4, yRadius: 4)
batteryPoint.fill()
let batteryPointSeparator = NSBezierPath()
batteryPointSeparator.move(to: CGPoint(x: bPX, y: batteryFrame.bounds.origin.y))
batteryPointSeparator.line(to: CGPoint(x: bPX, y: batteryFrame.bounds.origin.y + batteryFrame.bounds.height))
ctx.saveGState()
ctx.setBlendMode(.destinationOut)
NSColor.textColor.set()
batteryPointSeparator.lineWidth = 4
batteryPointSeparator.stroke()
ctx.restoreGState()
batteryFrame.lineWidth = 1
batteryFrame.stroke()
let maxWidth = w-2
let inner = NSBezierPath(roundedRect: NSRect(x: x+2, y: y+1, width: maxWidth * CGFloat(self.percentage), height: h-2), xRadius: radius, yRadius: radius)
self.percentage.batteryColor(color: true).set()
let inner = NSBezierPath(roundedRect: NSRect(
x: x+2,
y: y+2,
width: (w-10) * CGFloat(self.percentage),
height: h-4
), xRadius: 3, yRadius: 3)
self.percentage.batteryColor(color: self.colorState).set()
inner.lineWidth = 0
inner.stroke()
inner.close()

View File

@@ -0,0 +1,89 @@
//
// portal.swift
// Battery
//
// Created by Serhiy Mytrovtsiy on 16/03/2023
// Using Swift 5.0
// Running on macOS 13.2
//
// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved.
//
import Cocoa
import Kit
internal class Portal: NSStackView, Portal_p {
var name: String
private let batteryView: BatteryView = BatteryView()
private var levelField: NSTextField = ValueField(frame: NSRect.zero, "")
private var timeField: NSTextField = ValueField(frame: NSRect.zero, "")
private var initialized: Bool = false
private var timeFormat: String {
Store.shared.string(key: "\(self.name)_timeFormat", defaultValue: "short")
}
init(_ name: String) {
self.name = name
super.init(frame: NSRect.zero)
self.wantsLayer = true
self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
self.layer?.cornerRadius = 3
self.orientation = .vertical
self.distribution = .fillEqually
self.edgeInsets = NSEdgeInsets(
top: Constants.Popup.margins,
left: Constants.Popup.margins,
bottom: Constants.Popup.margins,
right: Constants.Popup.margins
)
self.spacing = 0
let box: NSStackView = NSStackView()
box.heightAnchor.constraint(equalToConstant: 13).isActive = true
box.orientation = .horizontal
box.spacing = 0
self.levelField.font = NSFont.systemFont(ofSize: 12, weight: .medium)
self.timeField.font = NSFont.systemFont(ofSize: 12, weight: .medium)
box.addArrangedSubview(self.levelField)
box.addArrangedSubview(NSView())
box.addArrangedSubview(self.timeField)
self.addArrangedSubview(self.batteryView)
self.addArrangedSubview(box)
self.heightAnchor.constraint(equalToConstant: Constants.Popup.portalHeight).isActive = true
}
required init?(coder: NSCoder) {
fatalError("init(coder:) has not been implemented")
}
public override func updateLayer() {
self.layer?.backgroundColor = NSColor.windowBackgroundColor.cgColor
}
public func loadCallback(_ value: Battery_Usage) {
DispatchQueue.main.async(execute: {
if (self.window?.isVisible ?? false) || !self.initialized {
self.levelField.stringValue = "\(Int(abs(value.level) * 100))%"
var seconds: Double = 0
if value.timeToEmpty != -1 && value.timeToEmpty != 0 {
seconds = Double((value.powerSource == "Battery Power" ? value.timeToEmpty : value.timeToCharge)*60)
}
self.timeField.stringValue = seconds != 0 ? seconds.printSecondsToHoursMinutesSeconds(short: self.timeFormat == "short") : ""
self.batteryView.setValue(abs(value.level))
self.initialized = true
}
})
}
}

View File

@@ -21,6 +21,7 @@
5CFE493D2926513E000F2856 /* eu.exelban.Stats.SMC.Helper in CopyFiles */ = {isa = PBXBuildFile; fileRef = 5CFE492729264DF1000F2856 /* eu.exelban.Stats.SMC.Helper */; settings = {ATTRIBUTES = (CodeSignOnCopy, ); }; };
5CFE494229265418000F2856 /* uninstall.sh in Resources */ = {isa = PBXBuildFile; fileRef = 5CFE494129265418000F2856 /* uninstall.sh */; };
5CFE494429265421000F2856 /* changelog.py in Resources */ = {isa = PBXBuildFile; fileRef = 5CFE494329265421000F2856 /* changelog.py */; };
5EE8037F29C36BDD0063D37D /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5EE8037E29C36BDD0063D37D /* portal.swift */; };
9A045EB72594F8D100ED58F2 /* Dashboard.swift in Sources */ = {isa = PBXBuildFile; fileRef = 9A045EB62594F8D100ED58F2 /* Dashboard.swift */; };
9A11AAD6266FD77F000C1C05 /* Bluetooth.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 9A11AACF266FD77F000C1C05 /* Bluetooth.framework */; };
9A11AAD7266FD77F000C1C05 /* Bluetooth.framework in Embed Frameworks */ = {isa = PBXBuildFile; fileRef = 9A11AACF266FD77F000C1C05 /* Bluetooth.framework */; settings = {ATTRIBUTES = (CodeSignOnCopy, RemoveHeadersOnCopy, ); }; };
@@ -367,6 +368,7 @@
5CFE493B292650F8000F2856 /* Launchd.plist */ = {isa = PBXFileReference; lastKnownFileType = text.plist.xml; path = Launchd.plist; sourceTree = "<group>"; };
5CFE494129265418000F2856 /* uninstall.sh */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.sh; path = uninstall.sh; sourceTree = "<group>"; };
5CFE494329265421000F2856 /* changelog.py */ = {isa = PBXFileReference; fileEncoding = 4; lastKnownFileType = text.script.python; path = changelog.py; sourceTree = "<group>"; };
5EE8037E29C36BDD0063D37D /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = "<group>"; };
62BA5F74254810C8009D0AC2 /* es */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = es; path = es.lproj/Localizable.strings; sourceTree = "<group>"; };
63A07F97275018DF00352C46 /* ca */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ca; path = ca.lproj/Localizable.strings; sourceTree = "<group>"; };
7A19DAE52552C326001B192F /* fr */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = fr; path = fr.lproj/Localizable.strings; sourceTree = "<group>"; };
@@ -916,6 +918,7 @@
children = (
9ABFF902248BEBD700C9041A /* main.swift */,
9ABFF90F248BEE7200C9041A /* readers.swift */,
5EE8037E29C36BDD0063D37D /* portal.swift */,
9ABFF913248C30A800C9041A /* popup.swift */,
9AD64FA124BF86C100419D59 /* settings.swift */,
9ABFF8F9248BEBCB00C9041A /* Info.plist */,
@@ -1720,6 +1723,7 @@
files = (
9ABFF910248BEE7200C9041A /* readers.swift in Sources */,
9ABFF914248C30A800C9041A /* popup.swift in Sources */,
5EE8037F29C36BDD0063D37D /* portal.swift in Sources */,
9AD64FA224BF86C100419D59 /* settings.swift in Sources */,
9ABFF903248BEBD700C9041A /* main.swift in Sources */,
);