From 78ca5d57938bce5c2d58679473e557d81f6ded75 Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Fri, 3 Mar 2023 19:52:10 +0100 Subject: [PATCH] feat: initialized battery details widget (#1293) --- .swiftlint.yml | 1 + Kit/Widgets/Battery.swift | 162 +++++++++++++++++++++++++++++++++++ Kit/module/widget.swift | 5 ++ Kit/types.swift | 7 ++ Modules/Battery/config.plist | 7 ++ Modules/Battery/main.swift | 5 ++ 6 files changed, 187 insertions(+) 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 } }