From 2a6c7b1dcdb06a62bac618b0b21178ca4d1ffc8d Mon Sep 17 00:00:00 2001 From: Serhiy Mytrovtsiy Date: Wed, 22 Feb 2023 20:17:39 +0100 Subject: [PATCH] feat: initialized popup view for the combined modules (#1084) --- Kit/constants.swift | 1 + Kit/helpers.swift | 23 ++++++--- Kit/module/module.swift | 4 +- Kit/module/popup.swift | 8 +-- Kit/module/portal.swift | 16 ++++++ Stats/Views/CombinedView.swift | 89 ++++++++++++++++++++++++++++++++-- Stats/Views/Settings.swift | 3 +- 7 files changed, 127 insertions(+), 17 deletions(-) create mode 100644 Kit/module/portal.swift diff --git a/Kit/constants.swift b/Kit/constants.swift index 91cb1d86..b819f12c 100644 --- a/Kit/constants.swift +++ b/Kit/constants.swift @@ -18,6 +18,7 @@ public struct Popup_c_s { public let spacing: CGFloat = 2 public let headerHeight: CGFloat = 42 public let separatorHeight: CGFloat = 30 + public let portalHeight: CGFloat = 100 } public struct Settings_c_s { diff --git a/Kit/helpers.swift b/Kit/helpers.swift index dc9a40ef..df8e68f3 100644 --- a/Kit/helpers.swift +++ b/Kit/helpers.swift @@ -230,7 +230,7 @@ public struct DiskSize { } public class LabelField: NSTextField { - public init(frame: NSRect, _ label: String = "") { + public init(frame: NSRect = NSRect(), _ label: String = "") { super.init(frame: frame) self.isEditable = false @@ -670,7 +670,7 @@ public class ColorView: NSView { private var color: NSColor private var state: Bool - public init(frame: NSRect, color: NSColor, state: Bool = false, radius: CGFloat = 2) { + public init(frame: NSRect = NSRect.zero, color: NSColor, state: Bool = false, radius: CGFloat = 2) { self.color = color self.state = state @@ -759,17 +759,24 @@ public class ProcessView: NSStackView { private var pid: Int? = nil private var lock: Bool = false - private var imageView: NSImageView = NSImageView(frame: NSRect(x: 5, y: 5, width: 12, height: 12)) - private var killView: NSButton = NSButton(frame: NSRect(x: 5, y: 5, width: 12, height: 12)) + private var imageView: NSImageView = NSImageView() + private var killView: NSButton = NSButton() private var labelView: LabelField = { - let view = LabelField(frame: NSRect(x: 0, y: 0, width: 0, height: 0)) + let view = LabelField() view.cell?.truncatesLastVisibleLine = true return view }() private var valueView: ValueField = ValueField(frame: NSRect(x: 0, y: 0, width: 0, height: 0)) - public init() { - super.init(frame: NSRect(x: 0, y: 0, width: 264, height: 22)) + public init(size: CGSize = CGSize(width: 264, height: 22)) { + var rect = NSRect(x: 5, y: 5, width: 12, height: 12) + if size.height != 22 { + rect = NSRect(x: 3, y: 3, width: 12, height: 12) + } + self.imageView = NSImageView(frame: rect) + self.killView = NSButton(frame: rect) + + super.init(frame: NSRect(x: 0, y: 0, width: size.width, height: size.height)) self.wantsLayer = true self.orientation = .horizontal @@ -778,7 +785,7 @@ public class ProcessView: NSStackView { self.layer?.cornerRadius = 3 let imageBox: NSView = { - let view = NSView(frame: NSRect(x: 0, y: 0, width: 0, height: 0)) + let view = NSView() self.killView.bezelStyle = .regularSquare self.killView.translatesAutoresizingMaskIntoConstraints = false diff --git a/Kit/module/module.swift b/Kit/module/module.swift index 1d4a4656..3d692a93 100644 --- a/Kit/module/module.swift +++ b/Kit/module/module.swift @@ -78,6 +78,7 @@ open class Module: Module_p { public var menuBar: MenuBar public var settings: Settings_p? = nil + public let portal: Portal_p? public var name: String { config.name @@ -107,7 +108,8 @@ open class Module: Module_p { } } - public init(popup: Popup_p? = nil, settings: Settings_v? = nil) { + public init(popup: Popup_p? = nil, settings: Settings_v? = nil, portal: Portal_p? = nil) { + self.portal = portal self.config = module_c(in: Bundle(for: type(of: self)).path(forResource: "config", ofType: "plist")!) self.log = NextLog.shared.copy(category: self.config.name) diff --git a/Kit/module/popup.swift b/Kit/module/popup.swift index 650bc1b7..eaa02fea 100644 --- a/Kit/module/popup.swift +++ b/Kit/module/popup.swift @@ -16,12 +16,12 @@ public protocol Popup_p: NSView { func settings() -> NSView? } -internal class PopupWindow: NSWindow, NSWindowDelegate { +public class PopupWindow: NSWindow, NSWindowDelegate { private let viewController: PopupViewController = PopupViewController() internal var locked: Bool = false internal var openedBy: widget_t? = nil - init(title: String, view: Popup_p?, visibilityCallback: @escaping (_ state: Bool) -> Void) { + public init(title: String, view: Popup_p?, visibilityCallback: @escaping (_ state: Bool) -> Void) { self.viewController.setup(title: title, view: view) super.init( @@ -51,12 +51,12 @@ internal class PopupWindow: NSWindow, NSWindowDelegate { self.delegate = self } - func windowWillMove(_ notification: Notification) { + public func windowWillMove(_ notification: Notification) { self.viewController.setCloseButton(true) self.locked = true } - func windowDidResignKey(_ notification: Notification) { + public func windowDidResignKey(_ notification: Notification) { if self.locked { return } diff --git a/Kit/module/portal.swift b/Kit/module/portal.swift new file mode 100644 index 00000000..4cebc5f7 --- /dev/null +++ b/Kit/module/portal.swift @@ -0,0 +1,16 @@ +// +// portal.swift +// Kit +// +// Created by Serhiy Mytrovtsiy on 17/02/2023 +// Using Swift 5.0 +// Running on macOS 13.2 +// +// Copyright © 2023 Serhiy Mytrovtsiy. All rights reserved. +// + +import Cocoa + +public protocol Portal_p: NSView { + var name: String { get set } +} diff --git a/Stats/Views/CombinedView.swift b/Stats/Views/CombinedView.swift index 0ebbda03..a4da59c0 100644 --- a/Stats/Views/CombinedView.swift +++ b/Stats/Views/CombinedView.swift @@ -15,6 +15,7 @@ import Kit class CombinedView { private var menuBarItem: NSStatusItem? = nil private var view: NSView = NSView(frame: NSRect(x: 0, y: 0, width: 100, height: Constants.Widget.height)) + private var popup: PopupWindow? = nil private var status: Bool { Store.shared.bool(key: "CombinedModules", defaultValue: false) @@ -34,6 +35,8 @@ class CombinedView { } } + self.popup = PopupWindow(title: "Combined modules", view: Popup()) { _ in } + if self.status { self.enable() } @@ -52,7 +55,7 @@ class CombinedView { self.menuBarItem?.button?.addSubview(self.view) self.menuBarItem?.button?.target = self - self.menuBarItem?.button?.action = #selector(self.openSettings) + self.menuBarItem?.button?.action = #selector(self.togglePopup) self.menuBarItem?.button?.sendAction(on: [.leftMouseDown, .rightMouseDown]) DispatchQueue.main.async(execute: { @@ -82,8 +85,33 @@ class CombinedView { self.menuBarItem?.length = w } - @objc private func openSettings() { - NotificationCenter.default.post(name: .toggleSettings, object: nil, userInfo: ["module": "Dashboard"]) + // call when popup appear/disappear + private func visibilityCallback(_ state: Bool) {} + + @objc private func togglePopup(_ sender: Any) { + guard let popup = self.popup, let item = self.menuBarItem, let window = item.button?.window else { return } + let openedWindows = NSApplication.shared.windows.filter{ $0 is NSPanel } + openedWindows.forEach{ $0.setIsVisible(false) } + + if popup.occlusionState.rawValue == 8192 { + NSApplication.shared.activate(ignoringOtherApps: true) + + popup.contentView?.invalidateIntrinsicContentSize() + + let windowCenter = popup.contentView!.intrinsicContentSize.width / 2 + var x = window.frame.origin.x - windowCenter + window.frame.width/2 + let y = window.frame.origin.y - popup.contentView!.intrinsicContentSize.height - 3 + + let maxWidth = NSScreen.screens.map{ $0.frame.width }.reduce(0, +) + if x + popup.contentView!.intrinsicContentSize.width > maxWidth { + x = maxWidth - popup.contentView!.intrinsicContentSize.width - 3 + } + + popup.setFrameOrigin(NSPoint(x: x, y: y)) + popup.setIsVisible(true) + } else { + popup.setIsVisible(false) + } } @objc private func listenForOneView(_ notification: Notification) { @@ -100,3 +128,58 @@ class CombinedView { self.recalculate() } } + +private class Popup: NSStackView, Popup_p { + public var sizeCallback: ((NSSize) -> Void)? = nil + + init() { + super.init(frame: NSRect(x: 0, y: 0, width: Constants.Popup.width, height: 0)) + + self.orientation = .vertical + self.distribution = .fill + self.alignment = .width + self.spacing = Constants.Popup.spacing + + self.reinit() + + NotificationCenter.default.addObserver(self, selector: #selector(reinit), name: .toggleModule, object: nil) + } + + required init?(coder: NSCoder) { + fatalError("init(coder:) has not been implemented") + } + + deinit { + NotificationCenter.default.removeObserver(self, name: .toggleOneView, object: nil) + } + + public func settings() -> NSView? { return nil } + + @objc private func reinit() { + self.subviews.forEach({ $0.removeFromSuperview() }) + + let availableModules = modules.filter({ $0.enabled && $0.portal != nil }) + let pairs = stride(from: 0, to: availableModules.endIndex, by: 2).map { + (availableModules[$0], $0 < availableModules.index(before: availableModules.endIndex) ? availableModules[$0.advanced(by: 1)] : nil) + } + pairs.forEach { (m1: Module, m2: Module?) in + let row = NSStackView() + row.orientation = .horizontal + row.distribution = .fillEqually + row.spacing = Constants.Popup.spacing + + if let p = m1.portal { + row.addArrangedSubview(p) + } + if let p = m2?.portal { + row.addArrangedSubview(p) + } + + self.addArrangedSubview(row) + } + + let h = CGFloat(pairs.count) * Constants.Popup.portalHeight + (CGFloat(pairs.count)*Constants.Popup.spacing) + self.setFrameSize(NSSize(width: self.frame.width, height: h)) + self.sizeCallback?(self.frame.size) + } +} diff --git a/Stats/Views/Settings.swift b/Stats/Views/Settings.swift index 5b4b36d8..a8c48291 100644 --- a/Stats/Views/Settings.swift +++ b/Stats/Views/Settings.swift @@ -166,7 +166,8 @@ class SettingsWindow: NSWindow, NSWindowDelegate, NSToolbarDelegate { self.orderFrontRegardless() } - if let name = notification.userInfo?["module"] as? String { + if var name = notification.userInfo?["module"] as? String { + if name == "Combined modules" { name = "Dashboard" } self.sidebarView.openMenu(name) } }