mirror of
https://github.com/morgan9e/macos-stats
synced 2026-04-14 00:04:15 +09:00
feat: redesigned Dashboard to new style
This commit is contained in:
@@ -13,22 +13,182 @@ import Cocoa
|
||||
import Kit
|
||||
|
||||
class Dashboard: NSStackView {
|
||||
private var uptimeField: NSTextField? = nil
|
||||
private var processorValue: String {
|
||||
guard let cpu = SystemKit.shared.device.info.cpu, cpu.name != nil || cpu.physicalCores != nil || cpu.logicalCores != nil else {
|
||||
return localizedString("Unknown")
|
||||
}
|
||||
|
||||
var value = ""
|
||||
|
||||
if let name = cpu.name {
|
||||
value += name
|
||||
}
|
||||
|
||||
if cpu.physicalCores != nil || cpu.logicalCores != nil {
|
||||
if !value.isEmpty {
|
||||
value += "\n"
|
||||
}
|
||||
|
||||
var mini = ""
|
||||
if let cores = cpu.physicalCores {
|
||||
mini += localizedString("Number of cores", "\(cores)")
|
||||
}
|
||||
if let threads = cpu.logicalCores {
|
||||
if mini != "" {
|
||||
mini += ", "
|
||||
}
|
||||
mini += localizedString("Number of threads", "\(threads)")
|
||||
}
|
||||
value += "\(mini)"
|
||||
}
|
||||
|
||||
if cpu.eCores != nil || cpu.pCores != nil {
|
||||
if !value.isEmpty {
|
||||
value += "\n"
|
||||
}
|
||||
|
||||
var mini = ""
|
||||
if let eCores = cpu.eCores {
|
||||
mini += localizedString("Number of e-cores", "\(eCores)")
|
||||
}
|
||||
if let pCores = cpu.pCores {
|
||||
if mini != "" {
|
||||
mini += "\n"
|
||||
}
|
||||
mini += localizedString("Number of p-cores", "\(pCores)")
|
||||
}
|
||||
value += "\(mini)"
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
private var memoryValue: String {
|
||||
guard let dimms = SystemKit.shared.device.info.ram?.dimms else {
|
||||
return localizedString("Unknown")
|
||||
}
|
||||
|
||||
let sizeFormatter = ByteCountFormatter()
|
||||
sizeFormatter.allowedUnits = [.useGB]
|
||||
sizeFormatter.countStyle = .memory
|
||||
|
||||
var value = ""
|
||||
for i in 0..<dimms.count {
|
||||
let dimm = dimms[i]
|
||||
var row = ""
|
||||
|
||||
if let size = dimm.size {
|
||||
row += size
|
||||
}
|
||||
|
||||
if let speed = dimm.speed {
|
||||
if !row.isEmpty && row.last != " " {
|
||||
row += " "
|
||||
}
|
||||
row += speed
|
||||
}
|
||||
|
||||
if let type = dimm.type {
|
||||
if !row.isEmpty && row.last != " " {
|
||||
row += " "
|
||||
}
|
||||
row += type
|
||||
}
|
||||
|
||||
if dimm.bank != nil || dimm.channel != nil {
|
||||
if !row.isEmpty && row.last != " " {
|
||||
row += " "
|
||||
}
|
||||
|
||||
var mini = "("
|
||||
if let bank = dimm.bank {
|
||||
mini += "slot \(bank)"
|
||||
}
|
||||
if let ch = dimm.channel {
|
||||
mini += "\(mini == "(" ? "" : "/")ch \(ch)"
|
||||
}
|
||||
row += "\(mini))"
|
||||
}
|
||||
|
||||
value += "\(row)\(i == dimms.count-1 ? "" : "\n")"
|
||||
}
|
||||
return value
|
||||
}
|
||||
private var graphicsValue: String {
|
||||
guard let gpus = SystemKit.shared.device.info.gpu else {
|
||||
return localizedString("Unknown")
|
||||
}
|
||||
|
||||
var value = ""
|
||||
for i in 0..<gpus.count {
|
||||
var row = gpus[i].name != nil ? gpus[i].name! : localizedString("Unknown")
|
||||
|
||||
if gpus[i].vram != nil || gpus[i].cores != nil {
|
||||
row += " ("
|
||||
if let cores = gpus[i].cores {
|
||||
row += localizedString("Number of cores", "\(cores)")
|
||||
}
|
||||
if let size = gpus[i].vram {
|
||||
if gpus[i].cores != nil {
|
||||
row += ", \(size)"
|
||||
} else {
|
||||
row += "\(size)"
|
||||
}
|
||||
}
|
||||
row += ")"
|
||||
}
|
||||
|
||||
value += "\(row)\(i == gpus.count-1 ? "" : "\n")"
|
||||
}
|
||||
return value
|
||||
}
|
||||
private var uptimeValue: String {
|
||||
let form = DateComponentsFormatter()
|
||||
form.maximumUnitCount = 2
|
||||
form.unitsStyle = .full
|
||||
form.allowedUnits = [.day, .hour, .minute]
|
||||
|
||||
var value = localizedString("Unknown")
|
||||
if let bootDate = SystemKit.shared.device.bootDate {
|
||||
if let duration = form.string(from: bootDate, to: Date()) {
|
||||
value = duration
|
||||
}
|
||||
}
|
||||
|
||||
return value
|
||||
}
|
||||
|
||||
private var uptimeField: NSTextField?
|
||||
|
||||
init() {
|
||||
super.init(frame: NSRect.zero)
|
||||
|
||||
self.translatesAutoresizingMaskIntoConstraints = false
|
||||
let scrollView = ScrollableStackView(orientation: .vertical)
|
||||
scrollView.stackView.edgeInsets = NSEdgeInsets(
|
||||
top: Constants.Settings.margin,
|
||||
left: Constants.Settings.margin,
|
||||
bottom: Constants.Settings.margin,
|
||||
right: Constants.Settings.margin
|
||||
)
|
||||
scrollView.stackView.spacing = Constants.Settings.margin
|
||||
|
||||
let scrollView = ScrollableStackView()
|
||||
scrollView.stackView.spacing = 10
|
||||
scrollView.stackView.addArrangedSubview(self.deviceView())
|
||||
|
||||
let separator = NSBox()
|
||||
separator.boxType = .separator
|
||||
scrollView.stackView.addArrangedSubview(PreferencesSection([
|
||||
PreferencesRow(localizedString("Processor"), component: textView(self.processorValue)),
|
||||
PreferencesRow(localizedString("Memory"), component: textView(self.memoryValue)),
|
||||
PreferencesRow(localizedString("Graphics"), component: textView(self.graphicsValue))
|
||||
]))
|
||||
|
||||
scrollView.stackView.addArrangedSubview(self.versions())
|
||||
scrollView.stackView.addArrangedSubview(separator)
|
||||
scrollView.stackView.addArrangedSubview(self.specs())
|
||||
scrollView.stackView.addArrangedSubview(PreferencesSection([
|
||||
PreferencesRow(localizedString("Model id"), component: textView(SystemKit.shared.device.model.id)),
|
||||
PreferencesRow(localizedString("Production year"), component: textView("\(SystemKit.shared.device.model.year)")),
|
||||
PreferencesRow(localizedString("Serial number"), component: textView(SystemKit.shared.device.serialNumber ?? localizedString("Unknown")))
|
||||
]))
|
||||
|
||||
self.uptimeField = textView(self.uptimeValue)
|
||||
scrollView.stackView.addArrangedSubview(PreferencesSection([
|
||||
PreferencesRow(localizedString("Uptime"), component: self.uptimeField!)
|
||||
]))
|
||||
|
||||
self.addArrangedSubview(scrollView)
|
||||
|
||||
@@ -43,17 +203,19 @@ class Dashboard: NSStackView {
|
||||
NotificationCenter.default.removeObserver(self, name: .openModuleSettings, object: nil)
|
||||
}
|
||||
|
||||
private func versions() -> NSView {
|
||||
private func deviceView() -> NSView {
|
||||
let container: NSGridView = NSGridView()
|
||||
container.rowSpacing = 0
|
||||
container.yPlacement = .center
|
||||
container.xPlacement = .center
|
||||
|
||||
let deviceImageView: NSImageView = NSImageView(image: SystemKit.shared.device.model.icon)
|
||||
deviceImageView.widthAnchor.constraint(equalToConstant: 140).isActive = true
|
||||
deviceImageView.heightAnchor.constraint(equalToConstant: 140).isActive = true
|
||||
|
||||
let deviceNameField: NSTextField = TextView()
|
||||
deviceNameField.alignment = .center
|
||||
deviceNameField.font = NSFont.systemFont(ofSize: 14, weight: .regular)
|
||||
deviceNameField.font = NSFont.systemFont(ofSize: 17, weight: .semibold)
|
||||
deviceNameField.stringValue = SystemKit.shared.device.model.name
|
||||
deviceNameField.isSelectable = true
|
||||
deviceNameField.toolTip = SystemKit.shared.device.model.id
|
||||
@@ -74,274 +236,8 @@ class Dashboard: NSStackView {
|
||||
return container
|
||||
}
|
||||
|
||||
private func specs() -> NSView {
|
||||
let view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: self.frame.width, height: 0))
|
||||
let grid: NSGridView = NSGridView(frame: NSRect(x: 0, y: 0, width: view.frame.width, height: 0))
|
||||
grid.rowSpacing = 10
|
||||
grid.columnSpacing = 20
|
||||
grid.xPlacement = .trailing
|
||||
grid.rowAlignment = .firstBaseline
|
||||
grid.translatesAutoresizingMaskIntoConstraints = false
|
||||
|
||||
let separator = NSBox()
|
||||
separator.boxType = .separator
|
||||
|
||||
grid.addRow(with: self.processor())
|
||||
grid.addRow(with: self.ram())
|
||||
grid.addRow(with: self.gpu())
|
||||
grid.addRow(with: self.disk())
|
||||
grid.addRow(with: self.serialNumber())
|
||||
|
||||
grid.addRow(with: [separator])
|
||||
grid.row(at: 5).mergeCells(in: NSRange(location: 0, length: 2))
|
||||
grid.row(at: 5).topPadding = 5
|
||||
grid.row(at: 5).bottomPadding = 5
|
||||
|
||||
grid.addRow(with: self.upTime())
|
||||
|
||||
view.addSubview(grid)
|
||||
|
||||
var height: CGFloat = (CGFloat(grid.numberOfRows)-2) * grid.rowSpacing
|
||||
for i in 0..<grid.numberOfRows {
|
||||
let row = grid.row(at: i)
|
||||
for a in 0..<row.numberOfCells {
|
||||
if let contentView = row.cell(at: a).contentView {
|
||||
height += contentView.frame.height
|
||||
}
|
||||
}
|
||||
}
|
||||
view.setFrameSize(NSSize(width: view.frame.width, height: height))
|
||||
view.heightAnchor.constraint(equalToConstant: height).isActive = true
|
||||
|
||||
NSLayoutConstraint.activate([
|
||||
grid.centerXAnchor.constraint(equalTo: view.centerXAnchor),
|
||||
grid.centerYAnchor.constraint(equalTo: view.centerYAnchor)
|
||||
])
|
||||
|
||||
return view
|
||||
}
|
||||
|
||||
@objc private func windowOpens(_ notification: Notification) {
|
||||
guard notification.userInfo?["module"] as? String == "Dashboard" else {
|
||||
return
|
||||
}
|
||||
|
||||
let form = DateComponentsFormatter()
|
||||
form.maximumUnitCount = 2
|
||||
form.unitsStyle = .full
|
||||
form.allowedUnits = [.day, .hour, .minute]
|
||||
if let bootDate = SystemKit.shared.device.bootDate {
|
||||
if let duration = form.string(from: bootDate, to: Date()) {
|
||||
self.uptimeField?.stringValue = duration
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
// MARK: - Views
|
||||
|
||||
private func processor() -> [NSView] {
|
||||
var value = ""
|
||||
|
||||
if let cpu = SystemKit.shared.device.info.cpu, cpu.name != nil || cpu.physicalCores != nil || cpu.logicalCores != nil {
|
||||
if let name = cpu.name {
|
||||
value += name
|
||||
}
|
||||
|
||||
if cpu.physicalCores != nil || cpu.logicalCores != nil {
|
||||
if !value.isEmpty {
|
||||
value += "\n"
|
||||
}
|
||||
|
||||
var mini = ""
|
||||
if let cores = cpu.physicalCores {
|
||||
mini += localizedString("Number of cores", "\(cores)")
|
||||
}
|
||||
if let threads = cpu.logicalCores {
|
||||
if mini != "" {
|
||||
mini += ", "
|
||||
}
|
||||
mini += localizedString("Number of threads", "\(threads)")
|
||||
}
|
||||
value += "\(mini)"
|
||||
}
|
||||
|
||||
if cpu.eCores != nil || cpu.pCores != nil {
|
||||
if !value.isEmpty {
|
||||
value += "\n"
|
||||
}
|
||||
|
||||
var mini = ""
|
||||
if let eCores = cpu.eCores {
|
||||
mini += localizedString("Number of e-cores", "\(eCores)")
|
||||
}
|
||||
if let pCores = cpu.pCores {
|
||||
if mini != "" {
|
||||
mini += "\n"
|
||||
}
|
||||
mini += localizedString("Number of p-cores", "\(pCores)")
|
||||
}
|
||||
value += "\(mini)"
|
||||
}
|
||||
} else {
|
||||
value = localizedString("Unknown")
|
||||
}
|
||||
|
||||
return [
|
||||
self.titleView("\(localizedString("Processor")):"),
|
||||
self.valueView(value)
|
||||
]
|
||||
}
|
||||
|
||||
private func ram() -> [NSView] {
|
||||
let sizeFormatter = ByteCountFormatter()
|
||||
sizeFormatter.allowedUnits = [.useGB]
|
||||
sizeFormatter.countStyle = .memory
|
||||
|
||||
var value = ""
|
||||
if let dimms = SystemKit.shared.device.info.ram?.dimms {
|
||||
for i in 0..<dimms.count {
|
||||
let dimm = dimms[i]
|
||||
var row = ""
|
||||
|
||||
if let size = dimm.size {
|
||||
row += size
|
||||
}
|
||||
|
||||
if let speed = dimm.speed {
|
||||
if !row.isEmpty && row.last != " " {
|
||||
row += " "
|
||||
}
|
||||
row += speed
|
||||
}
|
||||
|
||||
if let type = dimm.type {
|
||||
if !row.isEmpty && row.last != " " {
|
||||
row += " "
|
||||
}
|
||||
row += type
|
||||
}
|
||||
|
||||
if dimm.bank != nil || dimm.channel != nil {
|
||||
if !row.isEmpty && row.last != " " {
|
||||
row += " "
|
||||
}
|
||||
|
||||
var mini = "("
|
||||
if let bank = dimm.bank {
|
||||
mini += "slot \(bank)"
|
||||
}
|
||||
if let ch = dimm.channel {
|
||||
mini += "\(mini == "(" ? "" : "/")ch \(ch)"
|
||||
}
|
||||
row += "\(mini))"
|
||||
}
|
||||
|
||||
value += "\(row)\(i == dimms.count-1 ? "" : "\n")"
|
||||
}
|
||||
} else {
|
||||
value = localizedString("Unknown")
|
||||
}
|
||||
|
||||
return [
|
||||
self.titleView("\(localizedString("Memory")):"),
|
||||
self.valueView("\(value)")
|
||||
]
|
||||
}
|
||||
|
||||
private func gpu() -> [NSView] {
|
||||
var value = ""
|
||||
if let gpus = SystemKit.shared.device.info.gpu {
|
||||
for i in 0..<gpus.count {
|
||||
var row = gpus[i].name != nil ? gpus[i].name! : localizedString("Unknown")
|
||||
|
||||
if gpus[i].vram != nil || gpus[i].cores != nil {
|
||||
row += " ("
|
||||
if let cores = gpus[i].cores {
|
||||
row += localizedString("Number of cores", "\(cores)")
|
||||
}
|
||||
if let size = gpus[i].vram {
|
||||
if gpus[i].cores != nil {
|
||||
row += ", \(size)"
|
||||
} else {
|
||||
row += "\(size)"
|
||||
}
|
||||
}
|
||||
row += ")"
|
||||
}
|
||||
|
||||
value += "\(row)\(i == gpus.count-1 ? "" : "\n")"
|
||||
}
|
||||
} else {
|
||||
value = localizedString("Unknown")
|
||||
}
|
||||
|
||||
return [
|
||||
self.titleView("\(localizedString("Graphics")):"),
|
||||
self.valueView(value)
|
||||
]
|
||||
}
|
||||
|
||||
private func disk() -> [NSView] {
|
||||
var text = "\(SystemKit.shared.device.info.disk?.model ?? SystemKit.shared.device.info.disk?.name ?? localizedString("Unknown"))"
|
||||
|
||||
if let size = SystemKit.shared.device.info.disk?.size, size != 0 {
|
||||
text += " (\(DiskSize(size).getReadableMemory()))"
|
||||
}
|
||||
|
||||
return [
|
||||
self.titleView("\(localizedString("Disk")):"),
|
||||
self.valueView(text)
|
||||
]
|
||||
}
|
||||
|
||||
private func serialNumber() -> [NSView] {
|
||||
return [
|
||||
self.titleView("\(localizedString("Serial number")):"),
|
||||
self.valueView("\(SystemKit.shared.device.serialNumber ?? localizedString("Unknown"))")
|
||||
]
|
||||
}
|
||||
|
||||
private func upTime() -> [NSView] {
|
||||
let form = DateComponentsFormatter()
|
||||
form.maximumUnitCount = 2
|
||||
form.unitsStyle = .full
|
||||
form.allowedUnits = [.day, .hour, .minute]
|
||||
|
||||
var value = localizedString("Unknown")
|
||||
if let bootDate = SystemKit.shared.device.bootDate {
|
||||
if let duration = form.string(from: bootDate, to: Date()) {
|
||||
value = duration
|
||||
}
|
||||
}
|
||||
|
||||
let valueView = self.valueView(value)
|
||||
self.uptimeField = valueView
|
||||
|
||||
return [
|
||||
self.titleView("\(localizedString("Uptime")):"),
|
||||
valueView
|
||||
]
|
||||
}
|
||||
|
||||
// MARK: - Helpers
|
||||
|
||||
private func titleView(_ value: String) -> NSTextField {
|
||||
let field: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: 120, height: 17))
|
||||
field.font = NSFont.systemFont(ofSize: 13, weight: .light)
|
||||
field.textColor = .labelColor
|
||||
field.stringValue = value
|
||||
|
||||
return field
|
||||
}
|
||||
|
||||
private func valueView(_ value: String) -> NSTextField {
|
||||
let field: NSTextField = TextView(frame: NSRect(x: 0, y: 0, width: 0, height: 17))
|
||||
field.font = NSFont.systemFont(ofSize: 13, weight: .regular)
|
||||
field.textColor = .labelColor
|
||||
field.alignment = .right
|
||||
field.stringValue = value
|
||||
field.isSelectable = true
|
||||
|
||||
return field
|
||||
guard notification.userInfo?["module"] as? String == "Dashboard" else { return }
|
||||
self.uptimeField?.stringValue = self.uptimeValue
|
||||
}
|
||||
}
|
||||
|
||||
Reference in New Issue
Block a user