From 4b213c43926f8a0adeb72df3f522bf4b9764cc1b Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Tue, 10 Sep 2024 18:14:46 +0200 Subject: [PATCH] feat: added a new Text widget that allows to visualize custom values in the menu bar --- Kit/Widgets/Text.swift | 88 +++++++++++++++++++++++++++++++++ Kit/module/widget.swift | 5 ++ Stats.xcodeproj/project.pbxproj | 4 ++ 3 files changed, 97 insertions(+) create mode 100644 Kit/Widgets/Text.swift diff --git a/Kit/Widgets/Text.swift b/Kit/Widgets/Text.swift new file mode 100644 index 00000000..040af668 --- /dev/null +++ b/Kit/Widgets/Text.swift @@ -0,0 +1,88 @@ +// +// Text.swift +// Kit +// +// Created by Serhiy Mytrovtsiy on 08/09/2024 +// Using Swift 5.0 +// Running on macOS 14.6 +// +// Copyright © 2024 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public class TextWidget: WidgetWrapper { + private var value: String = "" + + public init(title: String, config: NSDictionary?, preview: Bool = false) { + super.init(.text, title: title, frame: CGRect( + x: 0, + y: Constants.Widget.margin.y, + width: 30 + (2*Constants.Widget.margin.x), + height: Constants.Widget.height - (2*Constants.Widget.margin.y) + )) + + if preview { + self.value = "Text" + } + + self.canDrawConcurrently = true + } + + required public init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + public override func draw(_ dirtyRect: NSRect) { + super.draw(dirtyRect) + + var value: String = "" + self.queue.sync { + value = self.value + } + + if value.isEmpty { + self.setWidth(0) + return + } + + let valueSize: CGFloat = 12 + let style = NSMutableParagraphStyle() + style.alignment = .center + let stringAttributes = [ + NSAttributedString.Key.font: NSFont.systemFont(ofSize: valueSize, weight: .regular), + NSAttributedString.Key.foregroundColor: NSColor.textColor, + NSAttributedString.Key.paragraphStyle: style + ] + let attributedString = NSAttributedString(string: value, attributes: stringAttributes) + let size = attributedString.boundingRect( + with: CGSize(width: CGFloat.greatestFiniteMagnitude, height: CGFloat.greatestFiniteMagnitude), + options: [.usesLineFragmentOrigin, .usesFontLeading] + ) + let width = (size.width+Constants.Widget.margin.x*2).roundedUpToNearestTen() + let origin: CGPoint = CGPoint(x: Constants.Widget.margin.x, y: ((Constants.Widget.height-valueSize-1)/2)) + let rect = CGRect(x: origin.x, y: origin.y, width: width - (Constants.Widget.margin.x*2), height: valueSize) + attributedString.draw(with: rect) + + self.setWidth(width) + } + + public func setValue(_ newValue: String) { + guard self.value != newValue else { return } + self.value = newValue + DispatchQueue.main.async(execute: { + self.display() + }) + } + + static public func parseText(_ raw: String) -> [KeyValue_t] { + var pairs: [KeyValue_t] = [] + raw.split(separator: " ", omittingEmptySubsequences: true).filter({ $0.hasPrefix("$") }).forEach { v in + let arr = v.split(separator: ".", omittingEmptySubsequences: true) + guard let key = arr.first else { return } + let value = arr.count == 1 ? nil : arr.last + pairs.append(KeyValue_t(key: String(key), value: String(value ?? ""))) + } + return pairs + } +} diff --git a/Kit/module/widget.swift b/Kit/module/widget.swift index b5e1503a..8df41b9a 100644 --- a/Kit/module/widget.swift +++ b/Kit/module/widget.swift @@ -26,6 +26,7 @@ public enum widget_t: String { case label = "label" case tachometer = "tachometer" case state = "state" + case text = "text" public func new(module: String, config: NSDictionary, defaultWidget: widget_t) -> SWidget? { guard let widgetConfig: NSDictionary = config[self.rawValue] as? NSDictionary else { return nil } @@ -74,6 +75,9 @@ public enum widget_t: String { case .state: preview = StateWidget(title: module, config: widgetConfig, preview: true) item = StateWidget(title: module, config: widgetConfig, preview: false) + case .text: + preview = TextWidget(title: module, config: widgetConfig, preview: true) + item = TextWidget(title: module, config: widgetConfig, preview: false) default: break } @@ -137,6 +141,7 @@ public enum widget_t: String { case .label: return localizedString("Label widget") case .tachometer: return localizedString("Tachometer widget") case .state: return localizedString("State widget") + case .text: return localizedString("Text widget") default: return "" } } diff --git a/Stats.xcodeproj/project.pbxproj b/Stats.xcodeproj/project.pbxproj index 7e1bc1ce..8cee4098 100644 --- a/Stats.xcodeproj/project.pbxproj +++ b/Stats.xcodeproj/project.pbxproj @@ -58,6 +58,7 @@ 5C7C1DF42C29A3A00060387D /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C7C1DF32C29A3A00060387D /* notifications.swift */; }; 5C8E001029269C7F0027C75A /* protocol.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CFE493829265055000F2856 /* protocol.swift */; }; 5CA518382B543FE600EBCCC4 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CA518372B543FE600EBCCC4 /* portal.swift */; }; + 5CAA50722C8E417700B13E13 /* Text.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CAA50712C8E417700B13E13 /* Text.swift */; }; 5CB3878A2C35A7110030459D /* widget.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CB387892C35A7110030459D /* widget.swift */; }; 5CD342F42B2F2FB700225631 /* notifications.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5CD342F32B2F2FB700225631 /* notifications.swift */; }; 5CE7E78C2C318512006BC92C /* WidgetKit.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5CE7E78B2C318512006BC92C /* WidgetKit.framework */; }; @@ -534,6 +535,7 @@ 5C7C1DF32C29A3A00060387D /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; 5C9F90A02A76B30500D41748 /* et */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = et; path = et.lproj/Localizable.strings; sourceTree = ""; }; 5CA518372B543FE600EBCCC4 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = ""; }; + 5CAA50712C8E417700B13E13 /* Text.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = Text.swift; sourceTree = ""; }; 5CB387892C35A7110030459D /* widget.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = widget.swift; sourceTree = ""; }; 5CD342F32B2F2FB700225631 /* notifications.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = notifications.swift; sourceTree = ""; }; 5CE7E78A2C318512006BC92C /* WidgetsExtension.appex */ = {isa = PBXFileReference; explicitFileType = "wrapper.app-extension"; includeInIndex = 0; path = WidgetsExtension.appex; sourceTree = BUILT_PRODUCTS_DIR; }; @@ -1018,6 +1020,7 @@ 9A28475C2666AA2700EC1F6D /* Speed.swift */, 9A9B8C9C27149A3700218374 /* Tachometer.swift */, 9AEBBE4C28D773430082A6A1 /* State.swift */, + 5CAA50712C8E417700B13E13 /* Text.swift */, ); path = Widgets; sourceTree = ""; @@ -2064,6 +2067,7 @@ 9A28480A2666AB3000EC1F6D /* SystemKit.swift in Sources */, 9A28477D2666AA5000EC1F6D /* widget.swift in Sources */, 9A2848212666AB3600EC1F6D /* helpers.swift in Sources */, + 5CAA50722C8E417700B13E13 /* Text.swift in Sources */, 9A28477A2666AA5000EC1F6D /* settings.swift in Sources */, 9A28475F2666AA2700EC1F6D /* LineChart.swift in Sources */, 9A302614286A2A3B00B41D57 /* Repeater.swift in Sources */,