diff --git a/.swiftlint.yml b/.swiftlint.yml
index 78070f28..f6f81ef7 100644
--- a/.swiftlint.yml
+++ b/.swiftlint.yml
@@ -35,6 +35,7 @@ identifier_name:
- AppleSiliconSensorsList
- FanValues
- CombinedModulesSpacings
+ - BatteryInfo
line_length: 200
diff --git a/Kit/Widgets/Battery.swift b/Kit/Widgets/Battery.swift
index 3f34d85a..7e5c854f 100644
--- a/Kit/Widgets/Battery.swift
+++ b/Kit/Widgets/Battery.swift
@@ -457,3 +457,165 @@ public class BatteryWidget: WidgetWrapper {
self.display()
}
}
+
+public class BatteryDetailsWidget: WidgetWrapper {
+ private var mode: String = "percentage"
+ private var timeFormat: String = "short"
+
+ private var percentage: Double? = nil
+ private var time: Int = 0
+
+ public init(title: String, config: NSDictionary?, preview: Bool = false) {
+ super.init(.batteryDetails, title: title, frame: CGRect(
+ x: Constants.Widget.margin.x,
+ y: Constants.Widget.margin.y,
+ width: 20 + (2*Constants.Widget.margin.x),
+ height: Constants.Widget.height - (2*Constants.Widget.margin.y)
+ ))
+
+ self.canDrawConcurrently = true
+
+ if preview {
+ self.percentage = 0.72
+ self.time = 415
+ self.mode = "percentageAndTime"
+ } else {
+ self.mode = Store.shared.string(key: "\(self.title)_\(self.type.rawValue)_mode", defaultValue: self.mode)
+ self.timeFormat = Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat)
+ }
+ }
+
+ required init?(coder: NSCoder) {
+ fatalError("init(coder:) has not been implemented")
+ }
+
+ public override func draw(_ dirtyRect: NSRect) {
+ super.draw(dirtyRect)
+
+ var width: CGFloat = Constants.Widget.margin.x*2
+ let x: CGFloat = Constants.Widget.margin.x
+ let isShortTimeFormat: Bool = self.timeFormat == "short"
+
+ switch self.mode {
+ case "percentage":
+ var value = "n/a"
+ if let percentage = self.percentage {
+ value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
+ }
+ width = self.drawOneRow(value: value, x: x).rounded(.up)
+ case "time":
+ width = self.drawOneRow(
+ value: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
+ x: x
+ ).rounded(.up)
+ case "percentageAndTime":
+ var value = "n/a"
+ if let percentage = self.percentage {
+ value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
+ }
+ width = self.drawTwoRows(
+ first: value,
+ second: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
+ x: x
+ ).rounded(.up)
+ case "timeAndPercentage":
+ var value = "n/a"
+ if let percentage = self.percentage {
+ value = "\(Int((percentage.rounded(toPlaces: 2)) * 100))%"
+ }
+ width = self.drawTwoRows(
+ first: Double(self.time*60).printSecondsToHoursMinutesSeconds(short: isShortTimeFormat),
+ second: value,
+ x: x
+ ).rounded(.up)
+ default: break
+ }
+
+ self.setWidth(width)
+ }
+
+ private func drawOneRow(value: String, x: CGFloat) -> CGFloat {
+ let attributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 12, weight: .regular),
+ NSAttributedString.Key.foregroundColor: isDarkMode ? NSColor.white : NSColor.textColor,
+ NSAttributedString.Key.paragraphStyle: NSMutableParagraphStyle()
+ ]
+
+ let rowWidth = value.widthOfString(usingFont: .systemFont(ofSize: 12, weight: .regular))
+ let rect = CGRect(x: x, y: (Constants.Widget.height-12)/2, width: rowWidth, height: 12)
+ let str = NSAttributedString.init(string: value, attributes: attributes)
+ str.draw(with: rect)
+
+ return rowWidth
+ }
+
+ private func drawTwoRows(first: String, second: String, x: CGFloat) -> CGFloat {
+ let style = NSMutableParagraphStyle()
+ style.alignment = .center
+ let attributes = [
+ NSAttributedString.Key.font: NSFont.systemFont(ofSize: 9, weight: .regular),
+ NSAttributedString.Key.foregroundColor: NSColor.textColor,
+ NSAttributedString.Key.paragraphStyle: style
+ ]
+ let rowHeight: CGFloat = self.frame.height / 2
+
+ let rowWidth = max(
+ first.widthOfString(usingFont: .systemFont(ofSize: 9, weight: .regular)),
+ second.widthOfString(usingFont: .systemFont(ofSize: 9, weight: .regular))
+ )
+
+ var str = NSAttributedString.init(string: first, attributes: attributes)
+ str.draw(with: CGRect(x: x, y: rowHeight+1, width: rowWidth, height: rowHeight))
+
+ str = NSAttributedString.init(string: second, attributes: attributes)
+ str.draw(with: CGRect(x: x, y: 1, width: rowWidth, height: rowHeight))
+
+ return rowWidth
+ }
+
+ public func setValue(percentage: Double? = nil, time: Int? = nil) {
+ var updated: Bool = false
+ let timeFormat: String = Store.shared.string(key: "\(self.title)_timeFormat", defaultValue: self.timeFormat)
+
+ if self.percentage != percentage {
+ self.percentage = percentage
+ updated = true
+ }
+ if let time = time, self.time != time {
+ self.time = time
+ updated = true
+ }
+ if self.timeFormat != timeFormat {
+ self.timeFormat = timeFormat
+ updated = true
+ }
+
+ if updated {
+ DispatchQueue.main.async(execute: {
+ self.display()
+ })
+ }
+ }
+
+ // MARK: - Settings
+
+ public override func settings() -> NSView {
+ let view = SettingsContainerView()
+
+ view.addArrangedSubview(selectSettingsRow(
+ title: localizedString("Mode"),
+ action: #selector(self.toggleMode),
+ items: BatteryInfo,
+ selected: self.mode
+ ))
+
+ return view
+ }
+
+ @objc private func toggleMode(_ sender: NSMenuItem) {
+ guard let key = sender.representedObject as? String else { return }
+ self.mode = key
+ Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_mode", value: key)
+ self.display()
+ }
+}
diff --git a/Kit/module/widget.swift b/Kit/module/widget.swift
index 16d30336..d09a3883 100644
--- a/Kit/module/widget.swift
+++ b/Kit/module/widget.swift
@@ -20,6 +20,7 @@ public enum widget_t: String {
case networkChart = "network_chart"
case speed = "speed"
case battery = "battery"
+ case batteryDetails = "battery_details"
case sensors = "sensors"
case memory = "memory"
case label = "label"
@@ -57,6 +58,9 @@ public enum widget_t: String {
case .battery:
preview = BatteryWidget(title: module, config: widgetConfig, preview: true)
item = BatteryWidget(title: module, config: widgetConfig, preview: false)
+ case .batteryDetails:
+ preview = BatteryDetailsWidget(title: module, config: widgetConfig, preview: true)
+ item = BatteryDetailsWidget(title: module, config: widgetConfig, preview: false)
case .sensors:
preview = SensorsWidget(title: module, config: widgetConfig, preview: true)
item = SensorsWidget(title: module, config: widgetConfig, preview: false)
@@ -128,6 +132,7 @@ public enum widget_t: String {
case .networkChart: return localizedString("Network chart widget")
case .speed: return localizedString("Speed widget")
case .battery: return localizedString("Battery widget")
+ case .batteryDetails: return localizedString("Battery details widget")
case .sensors: return localizedString("Text widget")
case .memory: return localizedString("Memory widget")
case .label: return localizedString("Label widget")
diff --git a/Kit/types.swift b/Kit/types.swift
index 4e2c2ac4..dd81a9ff 100644
--- a/Kit/types.swift
+++ b/Kit/types.swift
@@ -101,6 +101,13 @@ public let BatteryAdditionals: [KeyValue_t] = [
KeyValue_t(key: "timeAndPercentage", value: "Time and percentage")
]
+public let BatteryInfo: [KeyValue_t] = [
+ KeyValue_t(key: "percentage", value: "Percentage"),
+ KeyValue_t(key: "time", value: "Time"),
+ KeyValue_t(key: "percentageAndTime", value: "Percentage and time"),
+ KeyValue_t(key: "timeAndPercentage", value: "Time and percentage")
+]
+
public let ShortLong: [KeyValue_t] = [
KeyValue_t(key: "short", value: "Short"),
KeyValue_t(key: "long", value: "Long")
diff --git a/Modules/Battery/config.plist b/Modules/Battery/config.plist
index 7b9bbf92..aeba13de 100644
--- a/Modules/Battery/config.plist
+++ b/Modules/Battery/config.plist
@@ -69,6 +69,13 @@
Order
3
+ battery_details
+
+ Default
+
+ Order
+ 4
+
diff --git a/Modules/Battery/main.swift b/Modules/Battery/main.swift
index ee72e751..aa8f9ec9 100644
--- a/Modules/Battery/main.swift
+++ b/Modules/Battery/main.swift
@@ -141,6 +141,11 @@ public class Battery: Module {
optimizedCharging: value.optimizedChargingEngaged,
time: value.timeToEmpty == 0 && value.timeToCharge != 0 ? value.timeToCharge : value.timeToEmpty
)
+ case let widget as BatteryDetailsWidget:
+ widget.setValue(
+ percentage: value.level,
+ time: value.timeToEmpty == 0 && value.timeToCharge != 0 ? value.timeToCharge : value.timeToEmpty
+ )
default: break
}
}