mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
feat: added portal to the Clock module (#1748)
This commit is contained in:
@@ -484,19 +484,26 @@ public class ScrollableStackView: NSView {
|
||||
private let clipView: NSClipView = NSClipView()
|
||||
private let scrollView: NSScrollView = NSScrollView()
|
||||
|
||||
public override init(frame: NSRect) {
|
||||
public init(frame: NSRect = NSRect.zero, orientation: NSUserInterfaceLayoutOrientation = .vertical) {
|
||||
super.init(frame: frame)
|
||||
|
||||
self.clipView.drawsBackground = false
|
||||
|
||||
self.stackView.orientation = .vertical
|
||||
self.stackView.orientation = orientation
|
||||
self.stackView.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
self.scrollView.translatesAutoresizingMaskIntoConstraints = false
|
||||
self.scrollView.hasVerticalScroller = true
|
||||
self.scrollView.hasHorizontalScroller = false
|
||||
self.scrollView.autohidesScrollers = true
|
||||
self.scrollView.horizontalScrollElasticity = .none
|
||||
if orientation == .vertical {
|
||||
self.scrollView.hasVerticalScroller = true
|
||||
self.scrollView.hasHorizontalScroller = false
|
||||
self.scrollView.autohidesScrollers = true
|
||||
self.scrollView.horizontalScrollElasticity = .none
|
||||
} else {
|
||||
self.scrollView.hasVerticalScroller = false
|
||||
self.scrollView.hasHorizontalScroller = true
|
||||
self.scrollView.autohidesScrollers = true
|
||||
self.scrollView.verticalScrollElasticity = .none
|
||||
}
|
||||
self.scrollView.drawsBackground = false
|
||||
self.scrollView.contentView = self.clipView
|
||||
self.scrollView.documentView = self.stackView
|
||||
@@ -510,9 +517,14 @@ public class ScrollableStackView: NSView {
|
||||
self.scrollView.bottomAnchor.constraint(equalTo: self.bottomAnchor),
|
||||
|
||||
self.stackView.leftAnchor.constraint(equalTo: self.clipView.leftAnchor),
|
||||
self.stackView.rightAnchor.constraint(equalTo: self.clipView.rightAnchor),
|
||||
self.stackView.topAnchor.constraint(equalTo: self.clipView.topAnchor)
|
||||
])
|
||||
|
||||
if orientation == .vertical {
|
||||
self.stackView.rightAnchor.constraint(equalTo: self.clipView.rightAnchor).isActive = true
|
||||
} else {
|
||||
self.stackView.bottomAnchor.constraint(equalTo: self.clipView.bottomAnchor).isActive = true
|
||||
}
|
||||
}
|
||||
|
||||
required public init?(coder: NSCoder) {
|
||||
|
||||
@@ -55,11 +55,12 @@ internal class ClockReader: Reader<Date> {
|
||||
|
||||
public class Clock: Module {
|
||||
private let popupView: Popup = Popup()
|
||||
private let portalView: Portal
|
||||
private let settingsView: Settings = Settings()
|
||||
|
||||
private var reader: ClockReader = ClockReader(.clock)
|
||||
|
||||
private var list: [Clock_t] {
|
||||
static var list: [Clock_t] {
|
||||
if let objects = Store.shared.data(key: "\(Clock.title)_list") {
|
||||
let decoder = JSONDecoder()
|
||||
if let objectsDecoded = try? decoder.decode(Array.self, from: objects) as [Clock_t] {
|
||||
@@ -70,9 +71,12 @@ public class Clock: Module {
|
||||
}
|
||||
|
||||
public init() {
|
||||
self.portalView = Portal("Clock", list: Clock.list)
|
||||
|
||||
super.init(
|
||||
popup: self.popupView,
|
||||
settings: self.settingsView
|
||||
settings: self.settingsView,
|
||||
portal: self.portalView
|
||||
)
|
||||
guard self.available else { return }
|
||||
|
||||
@@ -88,7 +92,7 @@ public class Clock: Module {
|
||||
}
|
||||
|
||||
private func callback(_ value: Date) {
|
||||
var clocks: [Clock_t] = self.list
|
||||
var clocks: [Clock_t] = Clock.list
|
||||
var widgetList: [Stack_t] = []
|
||||
|
||||
for (i, c) in clocks.enumerated() {
|
||||
@@ -100,6 +104,7 @@ public class Clock: Module {
|
||||
|
||||
DispatchQueue.main.async(execute: {
|
||||
self.popupView.callback(clocks)
|
||||
self.portalView.callback(clocks)
|
||||
})
|
||||
|
||||
self.menuBar.widgets.filter{ $0.isActive }.forEach { (w: Widget) in
|
||||
|
||||
125
Modules/Clock/portal.swift
Normal file
125
Modules/Clock/portal.swift
Normal file
@@ -0,0 +1,125 @@
|
||||
//
|
||||
// portal.swift
|
||||
// Clock
|
||||
//
|
||||
// Created by Serhiy Mytrovtsiy on 28/12/2023
|
||||
// Using Swift 5.0
|
||||
// Running on macOS 14.2
|
||||
//
|
||||
// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved.
|
||||
//
|
||||
|
||||
import AppKit
|
||||
import Kit
|
||||
|
||||
public class Portal: NSStackView, Portal_p {
|
||||
public var name: String
|
||||
|
||||
private var initialized: Bool = false
|
||||
|
||||
private var oneContainer: NSGridView = NSGridView()
|
||||
private var multiplyContainer: ScrollableStackView = ScrollableStackView(orientation: .horizontal)
|
||||
|
||||
init(_ name: String, list: [Clock_t]) {
|
||||
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.spacing = Constants.Popup.spacing*2
|
||||
self.edgeInsets = NSEdgeInsets(
|
||||
top: Constants.Popup.spacing*2,
|
||||
left: Constants.Popup.spacing*2,
|
||||
bottom: Constants.Popup.spacing*2,
|
||||
right: Constants.Popup.spacing*2
|
||||
)
|
||||
self.addArrangedSubview(PortalHeader(name))
|
||||
|
||||
self.oneContainer.rowSpacing = 0
|
||||
self.oneContainer.yPlacement = .center
|
||||
self.oneContainer.xPlacement = .center
|
||||
|
||||
self.addArrangedSubview(self.oneContainer)
|
||||
self.addArrangedSubview(self.multiplyContainer)
|
||||
|
||||
self.callback(list)
|
||||
|
||||
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 callback(_ list: [Clock_t]) {
|
||||
let list = list.filter({ $0.popupState })
|
||||
|
||||
if (self.window?.isVisible ?? false) || !self.initialized {
|
||||
if list.count == 1, let c = list.first {
|
||||
self.loadOne(c)
|
||||
} else {
|
||||
self.loadMultiply(list)
|
||||
}
|
||||
self.initialized = true
|
||||
}
|
||||
}
|
||||
|
||||
private func loadOne(_ clock: Clock_t) {
|
||||
self.addArrangedSubview(self.oneContainer)
|
||||
self.multiplyContainer.removeFromSuperview()
|
||||
|
||||
let views = self.oneContainer.subviews.compactMap{ $0 as? ClockChart }
|
||||
if let view = views.first(where: { $0.identifier?.rawValue == clock.id }) {
|
||||
if let value = clock.value {
|
||||
view.setValue(value.convertToTimeZone(TimeZone(fromUTC: clock.tz)))
|
||||
}
|
||||
} else {
|
||||
self.oneContainer.addRow(with: [self.clockView(clock)])
|
||||
}
|
||||
}
|
||||
|
||||
private func loadMultiply(_ list: [Clock_t]) {
|
||||
self.addArrangedSubview(self.multiplyContainer)
|
||||
self.oneContainer.removeFromSuperview()
|
||||
|
||||
let sorted = list.sorted(by: { $0.popupIndex < $1.popupIndex })
|
||||
var views = self.multiplyContainer.stackView.subviews.compactMap{ $0 as? ClockChart }
|
||||
|
||||
if sorted.count < views.count && !views.isEmpty {
|
||||
views.forEach{ $0.removeFromSuperview() }
|
||||
views = []
|
||||
}
|
||||
|
||||
sorted.forEach { (c: Clock_t) in
|
||||
if let view = views.first(where: { $0.identifier?.rawValue == c.id }) {
|
||||
if let value = c.value {
|
||||
view.setValue(value.convertToTimeZone(TimeZone(fromUTC: c.tz)))
|
||||
}
|
||||
} else {
|
||||
self.multiplyContainer.stackView.addArrangedSubview(clockView(c))
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
private func clockView(_ clock: Clock_t) -> ClockChart {
|
||||
let view = ClockChart(frame: NSRect(x: 0, y: 0, width: 57, height: 57))
|
||||
view.widthAnchor.constraint(equalToConstant: view.frame.width).isActive = true
|
||||
view.heightAnchor.constraint(equalToConstant: view.frame.height).isActive = true
|
||||
view.identifier = NSUserInterfaceItemIdentifier(clock.id)
|
||||
|
||||
if let value = clock.value {
|
||||
view.setValue(value.convertToTimeZone(TimeZone(fromUTC: clock.tz)))
|
||||
}
|
||||
|
||||
return view
|
||||
}
|
||||
}
|
||||
@@ -7,6 +7,7 @@
|
||||
objects = {
|
||||
|
||||
/* Begin PBXBuildFile section */
|
||||
5C044F7A2B3DE6F3005F6951 /* portal.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C044F792B3DE6F3005F6951 /* portal.swift */; };
|
||||
5C0A2A8A292A5B4D009B4C1F /* SMJobBlessUtil.py in Resources */ = {isa = PBXBuildFile; fileRef = 5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */; };
|
||||
5C21D80B296C7B81005BA16D /* CombinedView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 5C21D80A296C7B81005BA16D /* CombinedView.swift */; };
|
||||
5C2229A329CCB3C400F00E69 /* Clock.framework in Frameworks */ = {isa = PBXBuildFile; fileRef = 5C22299D29CCB3C400F00E69 /* Clock.framework */; };
|
||||
@@ -385,6 +386,7 @@
|
||||
40BE2B202745D63800AE9396 /* da */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = da; path = da.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
47665544298DC92F00F7B709 /* sk */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = sk; path = sk.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
4921436D25319699000A1C47 /* ko */ = {isa = PBXFileReference; lastKnownFileType = text.plist.strings; name = ko; path = ko.lproj/Localizable.strings; sourceTree = "<group>"; };
|
||||
5C044F792B3DE6F3005F6951 /* portal.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = portal.swift; sourceTree = "<group>"; };
|
||||
5C0A2A89292A5B4D009B4C1F /* SMJobBlessUtil.py */ = {isa = PBXFileReference; lastKnownFileType = text.script.python; path = SMJobBlessUtil.py; sourceTree = "<group>"; };
|
||||
5C21D80A296C7B81005BA16D /* CombinedView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CombinedView.swift; sourceTree = "<group>"; };
|
||||
5C22299D29CCB3C400F00E69 /* Clock.framework */ = {isa = PBXFileReference; explicitFileType = wrapper.framework; includeInIndex = 0; path = Clock.framework; sourceTree = BUILT_PRODUCTS_DIR; };
|
||||
@@ -696,6 +698,7 @@
|
||||
children = (
|
||||
5C2229A829CCB41900F00E69 /* main.swift */,
|
||||
5C2229B729CE3F3300F00E69 /* popup.swift */,
|
||||
5C044F792B3DE6F3005F6951 /* portal.swift */,
|
||||
5C2229AE29CDC08700F00E69 /* settings.swift */,
|
||||
5C2229AA29CCB53E00F00E69 /* config.plist */,
|
||||
);
|
||||
@@ -1697,6 +1700,7 @@
|
||||
buildActionMask = 2147483647;
|
||||
files = (
|
||||
5C2229AF29CDC08700F00E69 /* settings.swift in Sources */,
|
||||
5C044F7A2B3DE6F3005F6951 /* portal.swift in Sources */,
|
||||
5C2229B829CE3F3300F00E69 /* popup.swift in Sources */,
|
||||
5C2229A929CCB41900F00E69 /* main.swift in Sources */,
|
||||
);
|
||||
|
||||
Reference in New Issue
Block a user