diff --git a/Kit/Widgets/BarChart.swift b/Kit/Widgets/BarChart.swift index 27a1dae94cb..57eede22c28 100644 --- a/Kit/Widgets/BarChart.swift +++ b/Kit/Widgets/BarChart.swift @@ -83,7 +83,7 @@ public class BarChart: WidgetWrapper { } self.setFrameSize(NSSize(width: 36, height: self.frame.size.height)) self.invalidateIntrinsicContentSize() - self.display() + self.renderToLayer() } let style = NSMutableParagraphStyle() diff --git a/Kit/Widgets/Battery.swift b/Kit/Widgets/Battery.swift index 4d9926b50ea..44ed9467f66 100644 --- a/Kit/Widgets/Battery.swift +++ b/Kit/Widgets/Battery.swift @@ -407,18 +407,17 @@ public class BatteryWidget: WidgetWrapper { } if updated { - self.needsDisplay = true DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } } - + // MARK: - Settings - + public override func settings() -> NSView { let view = SettingsContainerView() - + var additionalOptions = BatteryAdditionals if self.title == "Bluetooth" { additionalOptions = additionalOptions.filter({ $0.key == "none" || $0.key == "percentage" }) @@ -455,31 +454,31 @@ public class BatteryWidget: WidgetWrapper { guard let key = sender.representedObject as? String else { return } self.additional = key Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_additional", value: key) - self.display() + self.renderToLayer() } @objc private func toggleHideAdditionalWhenFull(_ sender: NSControl) { self.hideAdditionalWhenFull = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_hideAdditionalWhenFull", value: self.hideAdditionalWhenFull) - self.display() + self.renderToLayer() } @objc private func toggleColor(_ sender: NSControl) { self.colorState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState) - self.display() + self.renderToLayer() } @objc private func toggleXLSize(_ sender: NSControl) { self.xlSizeState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_xlSize", value: self.xlSizeState) - self.display() + self.renderToLayer() } @objc private func toggleChargerIconInside(_ sender: NSControl) { self.chargerIconInside = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_chargerInside", value: self.chargerIconInside) - self.display() + self.renderToLayer() } } @@ -624,15 +623,15 @@ public class BatteryDetailsWidget: WidgetWrapper { } if updated { - self.needsDisplay = true DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } } - + // MARK: - Settings - + + public override func settings() -> NSView { let view = SettingsContainerView() @@ -651,6 +650,6 @@ public class BatteryDetailsWidget: WidgetWrapper { 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() + self.renderToLayer() } } diff --git a/Kit/Widgets/Dot.swift b/Kit/Widgets/Dot.swift index 917a906ed73..376d3fcc12a 100644 --- a/Kit/Widgets/Dot.swift +++ b/Kit/Widgets/Dot.swift @@ -54,7 +54,7 @@ public class DotWidget: WidgetWrapper { guard self.value != value else { return } self.value = value DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } } diff --git a/Kit/Widgets/LineChart.swift b/Kit/Widgets/LineChart.swift index 50abb5cecd2..e1b6bd164d2 100644 --- a/Kit/Widgets/LineChart.swift +++ b/Kit/Widgets/LineChart.swift @@ -233,7 +233,9 @@ public class LineChart: WidgetWrapper { height: box.bounds.height - offset ) self.chart.setColor(color) - self.chart.setFrameSize(chartSize) + if self.chart.frame.size != chartSize { + self.chart.setFrameSize(chartSize) + } self.chart.draw(NSRect(origin: .zero, size: chartSize)) context.restoreGState() @@ -251,15 +253,15 @@ public class LineChart: WidgetWrapper { DispatchQueue.main.async(execute: { self._value = newValue self.chart.addValue(newValue) - self.display() + self.renderToLayer() }) } - + public func setPressure(_ newPressureLevel: RAMPressure) { DispatchQueue.main.async(execute: { guard self._pressureLevel != newPressureLevel else { return } self._pressureLevel = newPressureLevel - self.display() + self.renderToLayer() }) } @@ -317,7 +319,7 @@ public class LineChart: WidgetWrapper { @objc private func toggleLabel(_ sender: NSControl) { self.labelState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState) - self.display() + self.renderToLayer() } @objc private func toggleBox(_ sender: NSControl) { @@ -330,7 +332,7 @@ public class LineChart: WidgetWrapper { Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState) } - self.display() + self.renderToLayer() } @objc private func toggleFrame(_ sender: NSControl) { @@ -343,26 +345,26 @@ public class LineChart: WidgetWrapper { Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState) } - self.display() + self.renderToLayer() } @objc private func toggleValue(_ sender: NSControl) { self.valueState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState) - self.display() + self.renderToLayer() } @objc private func toggleColor(_ sender: NSMenuItem) { guard let key = sender.representedObject as? String else { return } self.colorState = SColor.fromString(key, defaultValue: self.colorState) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState.key) - self.display() + self.renderToLayer() } @objc private func toggleValueColor(_ sender: NSControl) { self.valueColorState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_valueColor", value: self.valueColorState) - self.display() + self.renderToLayer() } @objc private func toggleHistoryCount(_ sender: NSMenuItem) { @@ -370,7 +372,7 @@ public class LineChart: WidgetWrapper { self.historyCount = value Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_historyCount", value: value) self.chart.reinit(value) - self.display() + self.renderToLayer() } @objc private func toggleScale(_ sender: NSMenuItem) { @@ -379,6 +381,6 @@ public class LineChart: WidgetWrapper { self.scaleState = value self.chart.setScale(value) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_scale", value: key) - self.display() + self.renderToLayer() } } diff --git a/Kit/Widgets/Memory.swift b/Kit/Widgets/Memory.swift index 1a72f03c413..25d95418022 100644 --- a/Kit/Widgets/Memory.swift +++ b/Kit/Widgets/Memory.swift @@ -131,17 +131,17 @@ public class MemoryWidget: WidgetWrapper { public func setValue(_ value: (String, String), usedPercentage: Double) { self.value = value self.percentage = usedPercentage - + DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } - + public func setPressure(_ newPressureLevel: RAMPressure) { guard self.pressureLevel != newPressureLevel else { return } self.pressureLevel = newPressureLevel DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } @@ -170,19 +170,19 @@ public class MemoryWidget: WidgetWrapper { @objc private func toggleOrder(_ sender: NSControl) { self.orderReversedState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_orderReversed", value: self.orderReversedState) - self.display() + self.renderToLayer() } @objc private func toggleSymbols(_ sender: NSControl) { self.symbolsState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_symbols", value: self.symbolsState) - self.display() + self.renderToLayer() } @objc private func toggleColor(_ sender: NSMenuItem) { guard let key = sender.representedObject as? String else { return } self.colorState = SColor.fromString(key, defaultValue: self.colorState) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState.key) - self.display() + self.renderToLayer() } } diff --git a/Kit/Widgets/Mini.swift b/Kit/Widgets/Mini.swift index 74ac98a2a25..663d5e9bc79 100644 --- a/Kit/Widgets/Mini.swift +++ b/Kit/Widgets/Mini.swift @@ -151,7 +151,7 @@ public class Mini: WidgetWrapper { guard self._value != newValue else { return } self._value = newValue DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } @@ -159,10 +159,10 @@ public class Mini: WidgetWrapper { guard self._pressureLevel != newPressureLevel else { return } self._pressureLevel = newPressureLevel DispatchQueue.main.async(execute: { - self.needsDisplay = true + self.renderToLayer() }) } - + public func setTitle(_ newTitle: String?) { var title = self.defaultLabel if let new = newTitle { @@ -171,23 +171,23 @@ public class Mini: WidgetWrapper { guard self._label != title else { return } self._label = title DispatchQueue.main.async(execute: { - self.needsDisplay = true + self.renderToLayer() }) } - + public func setColorZones(_ newColorZones: colorZones) { guard self._colorZones != newColorZones else { return } self._colorZones = newColorZones DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } - + public func setSuffix(_ newSuffix: String) { guard self._suffix != newSuffix else { return } self._suffix = newSuffix DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } @@ -220,13 +220,13 @@ public class Mini: WidgetWrapper { guard let key = sender.representedObject as? String else { return } self.colorState = SColor.fromString(key, defaultValue: self.colorState) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_color", value: self.colorState.key) - self.display() + self.renderToLayer() } @objc private func toggleLabel(_ sender: NSControl) { self.labelState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState) - self.display() + self.renderToLayer() } @objc private func toggleAlignment(_ sender: NSMenuItem) { @@ -235,6 +235,6 @@ public class Mini: WidgetWrapper { self.alignmentState = newAlignment.key } Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_alignment", value: key) - self.display() + self.renderToLayer() } } diff --git a/Kit/Widgets/NetworkChart.swift b/Kit/Widgets/NetworkChart.swift index e7e7136889b..177be3143da 100644 --- a/Kit/Widgets/NetworkChart.swift +++ b/Kit/Widgets/NetworkChart.swift @@ -266,9 +266,9 @@ public class NetworkChart: WidgetWrapper { DispatchQueue.main.async(execute: { self.points.remove(at: 0) self.points.append((upload, download)) - + if self.window?.isVisible ?? false { - self.display() + self.renderToLayer() } }) } @@ -328,7 +328,7 @@ public class NetworkChart: WidgetWrapper { @objc private func toggleLabel(_ sender: NSControl) { self.labelState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_label", value: self.labelState) - self.display() + self.renderToLayer() } @objc private func toggleBox(_ sender: NSControl) { @@ -341,7 +341,7 @@ public class NetworkChart: WidgetWrapper { Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_frame", value: self.frameState) } - self.display() + self.renderToLayer() } @objc private func toggleFrame(_ sender: NSControl) { @@ -354,7 +354,7 @@ public class NetworkChart: WidgetWrapper { Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_box", value: self.boxState) } - self.display() + self.renderToLayer() } @objc private func toggleHistoryCount(_ sender: NSMenuItem) { @@ -368,21 +368,21 @@ public class NetworkChart: WidgetWrapper { self.points = Array(repeating: (0, 0), count: num - self.points.count) + self.points } - self.display() + self.renderToLayer() } @objc private func toggleDownloadColor(_ sender: NSMenuItem) { guard let key = sender.representedObject as? String else { return } self.downloadColor = SColor.fromString(key, defaultValue: self.downloadColor) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_downloadColor", value: self.downloadColor.key) - self.display() + self.renderToLayer() } @objc private func toggleUploadColor(_ sender: NSMenuItem) { guard let key = sender.representedObject as? String else { return } self.uploadColor = SColor.fromString(key, defaultValue: self.uploadColor) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_uploadColor", value: self.uploadColor.key) - self.display() + self.renderToLayer() } @objc private func toggleScale(_ sender: NSMenuItem) { @@ -390,12 +390,12 @@ public class NetworkChart: WidgetWrapper { let value = Scale.allCases.first(where: { $0.key == key }) else { return } self.scaleState = value Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_scale", value: key) - self.display() + self.renderToLayer() } @objc private func toggleReverseOrder(_ sender: NSControl) { self.reverseOrderState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_reverseOrder", value: self.reverseOrderState) - self.display() + self.renderToLayer() } } diff --git a/Kit/Widgets/Speed.swift b/Kit/Widgets/Speed.swift index b547a162988..3e855643c2e 100644 --- a/Kit/Widgets/Speed.swift +++ b/Kit/Widgets/Speed.swift @@ -588,14 +588,14 @@ public class SpeedWidget: WidgetWrapper { self.displayModeView?.isEnabled = key.count > 1 Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_displayValue", value: key) - self.display() + self.renderToLayer() } @objc private func changeDisplayMode(_ sender: NSMenuItem) { guard let key = sender.representedObject as? String else { return } self.modeState = key Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_mode", value: key) - self.display() + self.renderToLayer() } @objc private func toggleValue(_ sender: NSControl) { @@ -604,13 +604,13 @@ public class SpeedWidget: WidgetWrapper { self.valueColorView?.isEnabled = self.valueState self.valueAlignmentView?.isEnabled = self.valueState Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_value", value: self.valueState) - self.display() + self.renderToLayer() } @objc private func toggleUnits(_ sender: NSControl) { self.unitsState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_units", value: self.unitsState) - self.display() + self.renderToLayer() } @objc private func toggleIcon(_ sender: NSMenuItem) { @@ -619,13 +619,13 @@ public class SpeedWidget: WidgetWrapper { self.iconColorView?.isEnabled = self.icon != "none" self.iconAlignmentView?.isEnabled = self.icon != "none" Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_icon", value: key) - self.display() + self.renderToLayer() } @objc private func toggleMonochrome(_ sender: NSControl) { self.monochromeState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_monochrome", value: self.monochromeState) - self.display() + self.renderToLayer() } @objc private func toggleValueColor(_ sender: NSMenuItem) { @@ -634,7 +634,7 @@ public class SpeedWidget: WidgetWrapper { self.valueColorState = newColor.key } Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_valueColor", value: key) - self.display() + self.renderToLayer() } @objc private func toggleOutputColor(_ sender: NSMenuItem) { @@ -664,18 +664,18 @@ public class SpeedWidget: WidgetWrapper { if updated { DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } } - + @objc private func toggleValueAlignment(_ sender: NSMenuItem) { guard let key = sender.representedObject as? String else { return } if let newAlignment = Alignments.first(where: { $0.key == key }) { self.valueAlignmentState = newAlignment.key } Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_valueAlignment", value: key) - self.display() + self.renderToLayer() } @objc private func toggleIconAlignment(_ sender: NSMenuItem) { @@ -684,7 +684,7 @@ public class SpeedWidget: WidgetWrapper { self.iconAlignmentState = newAlignment.key } Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_iconAlignment", value: key) - self.display() + self.renderToLayer() } @objc private func toggleIconColor(_ sender: NSMenuItem) { @@ -693,6 +693,6 @@ public class SpeedWidget: WidgetWrapper { self.iconColorState = newColor.key } Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_iconColor", value: key) - self.display() + self.renderToLayer() } } diff --git a/Kit/Widgets/Stack.swift b/Kit/Widgets/Stack.swift index 7060c57dc3a..e613261a3b4 100644 --- a/Kit/Widgets/Stack.swift +++ b/Kit/Widgets/Stack.swift @@ -249,7 +249,7 @@ public class StackWidget: WidgetWrapper { if tableNeedsToBeUpdated { self.orderTableView.update() } - self.display() + self.renderToLayer() }) } @@ -291,19 +291,19 @@ public class StackWidget: WidgetWrapper { guard let key = sender.representedObject as? String else { return } self.modeState = StackMode(rawValue: key) ?? .auto Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_mode", value: key) - self.display() + self.renderToLayer() } @objc private func toggleSize(_ sender: NSControl) { self.fixedSizeState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_size", value: self.fixedSizeState) - self.display() + self.renderToLayer() } @objc private func toggleMonospacedFont(_ sender: NSControl) { self.monospacedFontState = controlState(sender) Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_monospacedFont", value: self.monospacedFontState) - self.display() + self.renderToLayer() } @objc private func toggleAlignment(_ sender: NSMenuItem) { @@ -312,7 +312,7 @@ public class StackWidget: WidgetWrapper { self.alignmentState = newAlignment.key } Store.shared.set(key: "\(self.title)_\(self.type.rawValue)_alignment", value: key) - self.display() + self.renderToLayer() } } diff --git a/Kit/Widgets/Text.swift b/Kit/Widgets/Text.swift index bb661289a59..29371bd6bb4 100644 --- a/Kit/Widgets/Text.swift +++ b/Kit/Widgets/Text.swift @@ -71,7 +71,7 @@ public class TextWidget: WidgetWrapper { guard self.value != newValue else { return } self.value = newValue DispatchQueue.main.async(execute: { - self.display() + self.renderToLayer() }) } diff --git a/Kit/module/widget.swift b/Kit/module/widget.swift index 45b622ddeb8..0a97fe678e0 100644 --- a/Kit/module/widget.swift +++ b/Kit/module/widget.swift @@ -11,6 +11,13 @@ import Cocoa +public extension NSStatusItem { + var hasValidBackingWindow: Bool { + guard let window = self.button?.window else { return false } + return window.windowNumber > 0 + } +} + public enum widget_t: String { case unknown = "" case mini = "mini" @@ -162,32 +169,105 @@ open class WidgetWrapper: NSView, widget_p { public var onClick: (() -> Void)? = nil public var shadowSize: CGSize internal var queue: DispatchQueue - + + private var hasUsableHost: Bool { + guard self.superview != nil else { return false } + guard let window = self.window else { return false } + return window.windowNumber > 0 + } + public init(_ type: widget_t, title: String, frame: NSRect) { self.type = type self.title = title self.shadowSize = frame.size self.queue = DispatchQueue(label: "eu.exelban.Stats.WidgetWrapper.\(type.rawValue).\(title)") - + super.init(frame: frame) + + self.wantsLayer = true + self.layerContentsRedrawPolicy = .never + self.layer?.contentsGravity = .center } - + required public init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + + open override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + if self.window != nil { + self.renderToLayer() + } + } + + open override func viewDidChangeEffectiveAppearance() { + super.viewDidChangeEffectiveAppearance() + self.renderToLayer() + } + + open override func viewDidChangeBackingProperties() { + super.viewDidChangeBackingProperties() + self.renderToLayer() + } + + public func renderToLayer() { + if !Thread.isMainThread { + DispatchQueue.main.async { [weak self] in self?.renderToLayer() } + return + } + guard let layer = self.layer else { return } + guard self.frame.width > 0, self.frame.height > 0 else { return } + + let scale = self.window?.backingScaleFactor ?? NSScreen.main?.backingScaleFactor ?? 2.0 + let pixelWidth = Int((self.frame.width * scale).rounded()) + let pixelHeight = Int((self.frame.height * scale).rounded()) + guard pixelWidth > 0, pixelHeight > 0 else { return } + + guard let ctx = CGContext( + data: nil, + width: pixelWidth, + height: pixelHeight, + bitsPerComponent: 8, + bytesPerRow: 0, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGBitmapInfo.byteOrder32Little.rawValue | CGImageAlphaInfo.premultipliedFirst.rawValue + ) else { return } + + ctx.scaleBy(x: scale, y: scale) + + let nsCtx = NSGraphicsContext(cgContext: ctx, flipped: self.isFlipped) + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = nsCtx + + self.effectiveAppearance.performAsCurrentDrawingAppearance { + self.draw(self.bounds) + } + + NSGraphicsContext.restoreGraphicsState() + + guard let img = ctx.makeImage() else { return } + + CATransaction.begin() + CATransaction.setDisableActions(true) + layer.contents = img + layer.contentsScale = scale + CATransaction.commit() + } + public func setWidth(_ width: CGFloat) { var newWidth = width if width == 0 || width == 1 { newWidth = self.emptyView() } - + guard self.shadowSize.width != newWidth else { return } self.shadowSize.width = newWidth - + DispatchQueue.main.async { + guard self.hasUsableHost else { return } self.setFrameSize(NSSize(width: newWidth, height: self.frame.size.height)) self.widthHandler?() + self.renderToLayer() } } @@ -214,8 +294,12 @@ open class WidgetWrapper: NSView, widget_p { } public func redraw() { - DispatchQueue.main.async { [weak self] in - self?.display() + if Thread.isMainThread { + self.renderToLayer() + } else { + DispatchQueue.main.async { [weak self] in + self?.renderToLayer() + } } } @@ -279,10 +363,12 @@ public class SWidget { self.originX = item.frame.origin.x self.item.widthHandler = { [weak self] in - self?.sizeCallback?() - if let s = self, let item = s.menuBarItem, let width: CGFloat = self?.item.frame.width, item.length != width { - item.length = width - } + guard let s = self else { return } + s.sizeCallback?() + guard let item = s.menuBarItem, + item.hasValidBackingWindow, + item.length != s.item.frame.width else { return } + item.length = s.item.frame.width } self.item.identifier = NSUserInterfaceItemIdentifier(self.type.rawValue) } @@ -332,23 +418,36 @@ public class SWidget { if self.item.frame.origin.x != self.originX { self.item.setFrameOrigin(NSPoint(x: self.originX, y: self.item.frame.origin.y)) } - self.menuBarItem?.button?.addSubview(self.item) - self.menuBarItem?.button?.image = NSImage() - self.menuBarItem?.button?.toolTip = "\(localizedString(self.module)): \(self.type.name())" - - if let item = self.menuBarItem, !item.isVisible { - self.menuBarItem?.isVisible = true - } - - self.menuBarItem?.button?.target = self - self.menuBarItem?.button?.action = #selector(self.togglePopup) - self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + self.attachToMenuBar(retries: 30) }) } else if let item = self.menuBarItem { NSStatusBar.system.removeStatusItem(item) self.menuBarItem = nil } } + + private func attachToMenuBar(retries: Int) { + guard let item = self.menuBarItem, let button = item.button else { return } + if !item.hasValidBackingWindow { + guard retries > 0 else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in + self?.attachToMenuBar(retries: retries - 1) + } + return + } + if self.item.superview !== button { + self.item.removeFromSuperview() + button.addSubview(self.item) + } + button.image = NSImage() + button.toolTip = "\(localizedString(self.module)): \(self.type.name())" + if !item.isVisible { + item.isVisible = true + } + button.target = self + button.action = #selector(self.togglePopup) + button.sendAction(on: [.leftMouseDown, .rightMouseDown]) + } @objc private func togglePopup() { if let item = self.menuBarItem, let window = item.button?.window { @@ -472,16 +571,7 @@ public class MenuBar { DispatchQueue.main.async(execute: { self.menuBarItem?.autosaveName = self.moduleName }) - self.menuBarItem?.isVisible = true - - self.menuBarItem?.button?.addSubview(self.view) - self.menuBarItem?.button?.image = NSImage() - self.menuBarItem?.button?.toolTip = "\(localizedString(self.moduleName))" - self.menuBarItem?.button?.target = self - self.menuBarItem?.button?.action = #selector(self.togglePopup) - self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) - - self.recalculateWidth() + self.configureMenuBarButton(retries: 30) } else if let item = self.menuBarItem { saveNSStatusItemPosition(id: self.moduleName) NSStatusBar.system.removeStatusItem(item) @@ -489,14 +579,39 @@ public class MenuBar { } }) } + + private func configureMenuBarButton(retries: Int) { + guard let item = self.menuBarItem, let button = item.button else { return } + if !item.hasValidBackingWindow { + guard retries > 0 else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in + self?.configureMenuBarButton(retries: retries - 1) + } + return + } + if !item.isVisible { + item.isVisible = true + } + if self.view.superview !== button { + self.view.removeFromSuperview() + button.addSubview(self.view) + } + button.image = NSImage() + button.toolTip = "\(localizedString(self.moduleName))" + button.target = self + button.action = #selector(self.togglePopup) + button.sendAction(on: [.leftMouseDown, .rightMouseDown]) + self.recalculateWidth() + } private func recalculateWidth() { guard self.oneView, self.active else { return } - + guard let item = self.menuBarItem, item.hasValidBackingWindow else { return } + let w = self.activeWidgets.map({ $0.item.frame.width }).reduce(0, +) + (CGFloat(self.activeWidgets.count - 1) * Constants.Widget.spacing) + Constants.Widget.spacing * 2 - self.menuBarItem?.length = w + item.length = w self.view.setFrameOrigin(NSPoint(x: 0, y: 0)) self.view.setFrameSize(NSSize(width: w, height: Constants.Widget.height)) @@ -558,7 +673,10 @@ public class MenuBarView: NSView { } public func addWidget(_ view: NSView) { - self.addSubview(view) + if view.superview !== self { + view.removeFromSuperview() + self.addSubview(view) + } } public func removeWidget(type: widget_t) { diff --git a/Kit/plugins/Charts.swift b/Kit/plugins/Charts.swift index e24813cbc00..d448b3eb66e 100644 --- a/Kit/plugins/Charts.swift +++ b/Kit/plugins/Charts.swift @@ -112,31 +112,89 @@ private func drawToolTip(_ frame: NSRect, _ point: CGPoint, _ size: CGSize, valu public class ChartView: NSView { public var id: String = UUID().uuidString fileprivate let stateQueue: DispatchQueue - + fileprivate init(frame: NSRect, queueLabel: String) { self.stateQueue = DispatchQueue(label: queueLabel, attributes: .concurrent) super.init(frame: frame) + self.wantsLayer = true + self.layer?.contentsGravity = .resize } - + required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") } - + + public override func viewDidMoveToWindow() { + super.viewDidMoveToWindow() + if self.window != nil { + self.renderToLayer() + } + } + + public override func viewDidChangeEffectiveAppearance() { + super.viewDidChangeEffectiveAppearance() + self.renderToLayer() + } + fileprivate func read(_ block: () -> T) -> T { self.stateQueue.sync(execute: block) } - + fileprivate func write(_ block: @escaping () -> Void) { self.stateQueue.async(flags: .barrier, execute: block) } - + + public func renderToLayer() { + if !Thread.isMainThread { + DispatchQueue.main.async { [weak self] in self?.renderToLayer() } + return + } + guard let layer = self.layer else { return } + guard self.frame.width > 0, self.frame.height > 0 else { return } + + let scale = self.window?.backingScaleFactor ?? NSScreen.main?.backingScaleFactor ?? 2.0 + let pixelWidth = Int((self.frame.width * scale).rounded()) + let pixelHeight = Int((self.frame.height * scale).rounded()) + guard pixelWidth > 0, pixelHeight > 0 else { return } + + guard let ctx = CGContext( + data: nil, + width: pixelWidth, + height: pixelHeight, + bitsPerComponent: 8, + bytesPerRow: 0, + space: CGColorSpaceCreateDeviceRGB(), + bitmapInfo: CGImageAlphaInfo.premultipliedLast.rawValue + ) else { return } + + ctx.scaleBy(x: scale, y: scale) + + let nsCtx = NSGraphicsContext(cgContext: ctx, flipped: self.isFlipped) + NSGraphicsContext.saveGraphicsState() + NSGraphicsContext.current = nsCtx + + self.effectiveAppearance.performAsCurrentDrawingAppearance { + self.draw(self.bounds) + } + + NSGraphicsContext.restoreGraphicsState() + + guard let img = ctx.makeImage() else { return } + + CATransaction.begin() + CATransaction.setDisableActions(true) + layer.contents = img + layer.contentsScale = scale + CATransaction.commit() + } + fileprivate func displayIfVisible() { if Thread.isMainThread { - if self.window?.isVisible ?? false { self.display() } + if self.window?.isVisible ?? false { self.renderToLayer() } } else { DispatchQueue.main.async { [weak self] in guard let self else { return } - if self.window?.isVisible ?? false { self.display() } + if self.window?.isVisible ?? false { self.renderToLayer() } } } } @@ -616,25 +674,25 @@ public class LineChartView: ChartView { public override func mouseEntered(with event: NSEvent) { guard self.tooltipEnabledSnapshot else { return } self.cursor = convert(event.locationInWindow, from: nil) - self.needsDisplay = true + self.renderToLayer() } public override func mouseMoved(with event: NSEvent) { guard self.tooltipEnabledSnapshot else { return } self.cursor = convert(event.locationInWindow, from: nil) - self.needsDisplay = true + self.renderToLayer() } public override func mouseDragged(with event: NSEvent) { guard self.tooltipEnabledSnapshot else { return } self.cursor = convert(event.locationInWindow, from: nil) - self.needsDisplay = true + self.renderToLayer() } public override func mouseExited(with event: NSEvent) { guard self.tooltipEnabledSnapshot else { return } self.cursor = nil - self.needsDisplay = true + self.renderToLayer() } public override func mouseDown(with: NSEvent) { @@ -1155,19 +1213,19 @@ public class ColumnChartView: ChartView { public override func mouseEntered(with event: NSEvent) { self.cursor = convert(event.locationInWindow, from: nil) - self.display() + self.renderToLayer() } public override func mouseMoved(with event: NSEvent) { self.cursor = convert(event.locationInWindow, from: nil) - self.display() + self.renderToLayer() } public override func mouseDragged(with event: NSEvent) { self.cursor = convert(event.locationInWindow, from: nil) - self.display() + self.renderToLayer() } public override func mouseExited(with event: NSEvent) { self.cursor = nil - self.display() + self.renderToLayer() } public override func updateTrackingAreas() { @@ -1275,17 +1333,17 @@ public class GridChartView: ChartView { public override func mouseEntered(with event: NSEvent) { self.cursor = convert(event.locationInWindow, from: nil) - self.needsDisplay = true + self.renderToLayer() } public override func mouseMoved(with event: NSEvent) { self.cursor = convert(event.locationInWindow, from: nil) - self.needsDisplay = true + self.renderToLayer() } public override func mouseExited(with event: NSEvent) { self.cursor = nil - self.needsDisplay = true + self.renderToLayer() } public override func updateTrackingAreas() { diff --git a/Modules/Net/preview.swift b/Modules/Net/preview.swift index b584f2f1e98..5fa3a25f898 100644 --- a/Modules/Net/preview.swift +++ b/Modules/Net/preview.swift @@ -139,7 +139,7 @@ internal class Preview: PreviewWrapper { view.spacing = Constants.Settings.margin*2 view.heightAnchor.constraint(equalToConstant: 140).isActive = true - let chart = NetworkChartView(num: 600) + let chart = NetworkChartView(frame: NSRect(x: 0, y: 0, width: 0, height: 140), num: 600) self.chart = chart chart.setColors(in: self.downloadColor, out: self.uploadColor) chart.setLegend(x: true, y: false) diff --git a/Modules/Net/readers.swift b/Modules/Net/readers.swift index 3bf47652ea0..081d1d14005 100644 --- a/Modules/Net/readers.swift +++ b/Modules/Net/readers.swift @@ -30,7 +30,9 @@ extension CWPHYMode: @retroactive CustomStringConvertible { case .mode11g: return "802.11g" case .mode11n: return "802.11n" case .mode11ax: return "802.11ax" + #if compiler(<6.2) case .mode11be: return "802.11be" + #endif case .modeNone: return "none" @unknown default: return "unknown" } diff --git a/Stats/Views/CombinedView.swift b/Stats/Views/CombinedView.swift index 36c7eba24cd..550f435c0e9 100644 --- a/Stats/Views/CombinedView.swift +++ b/Stats/Views/CombinedView.swift @@ -73,10 +73,25 @@ internal class CombinedView: NSObject, NSGestureRecognizerDelegate { DispatchQueue.main.async(execute: { self.menuBarItem?.autosaveName = "CombinedModules" }) - self.menuBarItem?.button?.addSubview(self.view) - self.menuBarItem?.button?.image = NSImage() - self.menuBarItem?.button?.toolTip = localizedString("Combined modules") - + self.configureMenuBarButton(retries: 30) + } + + private func configureMenuBarButton(retries: Int) { + guard let item = self.menuBarItem, let button = item.button else { return } + if !item.hasValidBackingWindow { + guard retries > 0 else { return } + DispatchQueue.main.asyncAfter(deadline: .now() + 0.05) { [weak self] in + self?.configureMenuBarButton(retries: retries - 1) + } + return + } + if self.view.superview !== button { + self.view.removeFromSuperview() + button.addSubview(self.view) + } + button.image = NSImage() + button.toolTip = localizedString("Combined modules") + if !self.combinedModulesPopup { self.activeModules.forEach { (m: Module) in m.menuBar.widgets.forEach { w in @@ -93,11 +108,11 @@ internal class CombinedView: NSObject, NSGestureRecognizerDelegate { } } } else { - self.menuBarItem?.button?.target = self - self.menuBarItem?.button?.action = #selector(self.togglePopup) - self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) + button.target = self + button.action = #selector(self.togglePopup) + button.sendAction(on: [.leftMouseDown, .rightMouseDown]) } - + DispatchQueue.main.async(execute: { self.recalculate() }) @@ -136,7 +151,9 @@ internal class CombinedView: NSObject, NSGestureRecognizerDelegate { } } self.view.setFrameSize(NSSize(width: w, height: self.view.frame.height)) - self.menuBarItem?.length = w + if let item = self.menuBarItem, item.hasValidBackingWindow, item.length != w { + item.length = w + } } // call when popup appear/disappear