mirror of
https://github.com/dredx/prole.git
synced 2026-09-27 17:04:30 +00:00
refactor(app, scripts): remove deprecated init-port-forwards.sh; enhance UI and db management
- Deleted `init-port-forwards.sh`, removing deprecated placeholder script. - Introduced `dbStatus` command in `PFScriptBridge.swift` for querying database status with `kubecolor`. - Improved `StatusView` UI: refined button interactivity and font standardization. - Added `DBStatusWindowController` to `AppDelegate` for better database status visibility and control. - Updated `OverlayWindow`: adjusted window levels and interaction behavior for smoother UX.
This commit is contained in:
parent
325d180e9e
commit
6fa949cabc
@ -1,17 +0,0 @@
|
|||||||
#!/usr/bin/env bash
|
|
||||||
set -euo pipefail
|
|
||||||
|
|
||||||
# DEPRECATED: this script has been renamed to etc/init_port_forwards.sh
|
|
||||||
# Keep as a thin wrapper for backward compatibility.
|
|
||||||
|
|
||||||
SCRIPT_DIR=$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)
|
|
||||||
NEW_SCRIPT="$SCRIPT_DIR/init_port_forwards.sh"
|
|
||||||
|
|
||||||
echo "[init-port-forwards.sh] WARNING: This script is deprecated. Please use etc/init_port_forwards.sh instead." >&2
|
|
||||||
|
|
||||||
if [ -x "$NEW_SCRIPT" ]; then
|
|
||||||
exec "$NEW_SCRIPT" "$@"
|
|
||||||
else
|
|
||||||
echo "[init-port-forwards.sh] ERROR: $NEW_SCRIPT not found or not executable." >&2
|
|
||||||
exit 127
|
|
||||||
fi
|
|
||||||
@ -14,6 +14,7 @@ import AppKit
|
|||||||
final class AppDelegate: NSObject, NSApplicationDelegate {
|
final class AppDelegate: NSObject, NSApplicationDelegate {
|
||||||
private var overlayWindowController: OverlayWindowController!
|
private var overlayWindowController: OverlayWindowController!
|
||||||
private var mainWindowController: MainWindowController!
|
private var mainWindowController: MainWindowController!
|
||||||
|
private var dbStatusWindowController: DBStatusWindowController!
|
||||||
private var workstationWindowController: WorkstationWindowController!
|
private var workstationWindowController: WorkstationWindowController!
|
||||||
private var ircWindowController: IRCWindowController!
|
private var ircWindowController: IRCWindowController!
|
||||||
private var hotKeyManager: HotKeyManager!
|
private var hotKeyManager: HotKeyManager!
|
||||||
@ -64,6 +65,12 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
), pfManager: nil)
|
), pfManager: nil)
|
||||||
dlog("MainWindowController created")
|
dlog("MainWindowController created")
|
||||||
|
|
||||||
|
// Database status window — positioned slightly down and left of main window
|
||||||
|
dbStatusWindowController = DBStatusWindowController(actions: .init(
|
||||||
|
onMinimizeToStatusBar: { [weak self] in self?.switchToStatusBarMode() }
|
||||||
|
))
|
||||||
|
dlog("DBStatusWindowController created")
|
||||||
|
|
||||||
// Workstation window (VNC viewer) — shown to the right of Prole Status when not in status bar mode
|
// Workstation window (VNC viewer) — shown to the right of Prole Status when not in status bar mode
|
||||||
workstationWindowController = WorkstationWindowController()
|
workstationWindowController = WorkstationWindowController()
|
||||||
dlog("WorkstationWindowController created")
|
dlog("WorkstationWindowController created")
|
||||||
@ -152,6 +159,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
private func switchToStatusBarMode() {
|
private func switchToStatusBarMode() {
|
||||||
mode = .statusBar
|
mode = .statusBar
|
||||||
mainWindowController.hide()
|
mainWindowController.hide()
|
||||||
|
dbStatusWindowController.hide()
|
||||||
workstationWindowController.hide()
|
workstationWindowController.hide()
|
||||||
ircWindowController.hide()
|
ircWindowController.hide()
|
||||||
overlayWindowController.showOverlayNow()
|
overlayWindowController.showOverlayNow()
|
||||||
@ -167,6 +175,9 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
mode = .appWindow
|
mode = .appWindow
|
||||||
overlayWindowController.hideOverlay()
|
overlayWindowController.hideOverlay()
|
||||||
mainWindowController.show()
|
mainWindowController.show()
|
||||||
|
// Position database status window slightly down and to the left of the main window
|
||||||
|
if let ref = mainWindowController.window { dbStatusWindowController.position(relativeTo: ref) }
|
||||||
|
dbStatusWindowController.show()
|
||||||
// Position workstation window to the right of the main window and show it
|
// Position workstation window to the right of the main window and show it
|
||||||
if let ref = mainWindowController.window { workstationWindowController.position(rightOf: ref) }
|
if let ref = mainWindowController.window { workstationWindowController.position(rightOf: ref) }
|
||||||
workstationWindowController.show()
|
workstationWindowController.show()
|
||||||
@ -194,14 +205,19 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
private func toggleAppWindowVisibility() {
|
private func toggleAppWindowVisibility() {
|
||||||
if mainWindowController.isVisible {
|
if mainWindowController.isVisible {
|
||||||
mainWindowController.hide()
|
mainWindowController.hide()
|
||||||
|
dbStatusWindowController.hide()
|
||||||
workstationWindowController.hide()
|
workstationWindowController.hide()
|
||||||
ircWindowController.hide()
|
ircWindowController.hide()
|
||||||
if mode == .appWindow { mode = .statusBar }
|
if mode == .appWindow { mode = .statusBar }
|
||||||
} else {
|
} else {
|
||||||
mainWindowController.show()
|
mainWindowController.show()
|
||||||
if let ref = mainWindowController.window { workstationWindowController.position(rightOf: ref) }
|
if let ref = mainWindowController.window {
|
||||||
|
dbStatusWindowController.position(relativeTo: ref)
|
||||||
|
workstationWindowController.position(rightOf: ref)
|
||||||
|
ircWindowController.position(below: ref)
|
||||||
|
}
|
||||||
|
dbStatusWindowController.show()
|
||||||
workstationWindowController.show()
|
workstationWindowController.show()
|
||||||
if let ref = mainWindowController.window { ircWindowController.position(below: ref) }
|
|
||||||
ircWindowController.show()
|
ircWindowController.show()
|
||||||
mode = .appWindow
|
mode = .appWindow
|
||||||
overlayWindowController.hideOverlay()
|
overlayWindowController.hideOverlay()
|
||||||
@ -297,6 +313,7 @@ final class AppDelegate: NSObject, NSApplicationDelegate {
|
|||||||
|
|
||||||
// Invoked by overlay maximize button
|
// Invoked by overlay maximize button
|
||||||
@objc private func showMainWindowRequested() {
|
@objc private func showMainWindowRequested() {
|
||||||
|
dlog("AppDelegate: showMainWindowRequested notification received")
|
||||||
switchToAppWindowMode()
|
switchToAppWindowMode()
|
||||||
// Maximize (zoom) the window to ensure it's fully visible/readable
|
// Maximize (zoom) the window to ensure it's fully visible/readable
|
||||||
mainWindowController.window?.zoom(nil)
|
mainWindowController.window?.zoom(nil)
|
||||||
|
|||||||
@ -1,12 +1,7 @@
|
|||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
enum Debug {
|
enum Debug {
|
||||||
static let isEnabled: Bool = {
|
static let isEnabled: Bool = true
|
||||||
if let v = ProcessInfo.processInfo.environment["PROLESTATUS_DEBUG"] {
|
|
||||||
return v == "1" || v.lowercased() == "true" || v.lowercased() == "yes"
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}()
|
|
||||||
|
|
||||||
static func log(_ message: @autoclosure () -> String) {
|
static func log(_ message: @autoclosure () -> String) {
|
||||||
guard isEnabled else { return }
|
guard isEnabled else { return }
|
||||||
|
|||||||
@ -9,8 +9,8 @@ final class OverlayWindowController: NSWindowController {
|
|||||||
let window = OverlayWindow(contentRect: .zero, styleMask: style, backing: .buffered, defer: false)
|
let window = OverlayWindow(contentRect: .zero, styleMask: style, backing: .buffered, defer: false)
|
||||||
super.init(window: window)
|
super.init(window: window)
|
||||||
window.isReleasedWhenClosed = false
|
window.isReleasedWhenClosed = false
|
||||||
window.level = .floating // below status bar level, above normal windows
|
window.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1)
|
||||||
window.collectionBehavior = [.canJoinAllSpaces, .stationary]
|
window.collectionBehavior = [.canJoinAllSpaces, .stationary, .ignoresCycle]
|
||||||
window.hasShadow = false
|
window.hasShadow = false
|
||||||
window.backgroundColor = .clear
|
window.backgroundColor = .clear
|
||||||
window.isOpaque = false
|
window.isOpaque = false
|
||||||
@ -53,8 +53,7 @@ final class OverlayWindowController: NSWindowController {
|
|||||||
let rect = NSRect(x: frame.minX, y: y, width: frame.width, height: overlayHeight)
|
let rect = NSRect(x: frame.minX, y: y, width: frame.width, height: overlayHeight)
|
||||||
window?.setFrame(rect, display: true)
|
window?.setFrame(rect, display: true)
|
||||||
window?.orderFrontRegardless()
|
window?.orderFrontRegardless()
|
||||||
// Allow mouse for our inline controls (refresh/maximize). The view will pass-through elsewhere.
|
window?.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1)
|
||||||
window?.ignoresMouseEvents = false
|
|
||||||
dlog("Overlay window forced visible at top of visible frame")
|
dlog("Overlay window forced visible at top of visible frame")
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -86,19 +85,12 @@ final class OverlayWindowController: NSWindowController {
|
|||||||
dlog("Computed overlay frame: x=\(rect.origin.x), y=\(rect.origin.y), w=\(rect.size.width), h=\(rect.size.height) | menuBarHeight=\(menuBarHeight), statusBarThickness=\(statusThickness), overlayHeight=\(overlayHeight)")
|
dlog("Computed overlay frame: x=\(rect.origin.x), y=\(rect.origin.y), w=\(rect.size.width), h=\(rect.size.height) | menuBarHeight=\(menuBarHeight), statusBarThickness=\(statusThickness), overlayHeight=\(overlayHeight)")
|
||||||
window?.setFrame(rect, display: true)
|
window?.setFrame(rect, display: true)
|
||||||
window?.orderFrontRegardless()
|
window?.orderFrontRegardless()
|
||||||
// Enable mouse so the inline buttons work; StatusView forwards other clicks through.
|
window?.level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1)
|
||||||
window?.ignoresMouseEvents = false
|
|
||||||
dlog("Overlay window ordered front")
|
dlog("Overlay window ordered front")
|
||||||
}
|
}
|
||||||
|
|
||||||
func showContextMenu() {
|
func showContextMenu() {
|
||||||
guard let window = window, window.isVisible else { return }
|
guard let window = window, window.isVisible else { return }
|
||||||
// Temporarily enable mouse interactions so the context menu can be used
|
|
||||||
let wasIgnoring = window.ignoresMouseEvents
|
|
||||||
if wasIgnoring {
|
|
||||||
dlog("Enabling mouse events for context menu interaction")
|
|
||||||
window.ignoresMouseEvents = false
|
|
||||||
}
|
|
||||||
let menu = NSMenu(title: "Prole Tools")
|
let menu = NSMenu(title: "Prole Tools")
|
||||||
menu.addItem(withTitle: statusView.aggregateStatusSummary(), action: nil, keyEquivalent: "")
|
menu.addItem(withTitle: statusView.aggregateStatusSummary(), action: nil, keyEquivalent: "")
|
||||||
menu.addItem(.separator())
|
menu.addItem(.separator())
|
||||||
@ -109,11 +101,6 @@ final class OverlayWindowController: NSWindowController {
|
|||||||
|
|
||||||
let point = NSPoint(x: window.frame.midX, y: window.frame.minY)
|
let point = NSPoint(x: window.frame.midX, y: window.frame.minY)
|
||||||
menu.popUp(positioning: nil, at: point, in: nil)
|
menu.popUp(positioning: nil, at: point, in: nil)
|
||||||
// Restore click-through after the menu is dismissed
|
|
||||||
if wasIgnoring {
|
|
||||||
dlog("Restoring click-through after context menu")
|
|
||||||
window.ignoresMouseEvents = true
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
@objc private func refreshNow() {
|
@objc private func refreshNow() {
|
||||||
@ -131,17 +118,49 @@ final class OverlayWindowController: NSWindowController {
|
|||||||
}
|
}
|
||||||
|
|
||||||
final class OverlayWindow: NSWindow {
|
final class OverlayWindow: NSWindow {
|
||||||
override var canBecomeKey: Bool { false }
|
override var canBecomeKey: Bool { return false }
|
||||||
override var canBecomeMain: Bool { false }
|
override var canBecomeMain: Bool { return false }
|
||||||
|
|
||||||
|
// Allow interaction with the window even if it's not key
|
||||||
|
override var acceptsMouseMovedEvents: Bool {
|
||||||
|
get { return true }
|
||||||
|
set { }
|
||||||
|
}
|
||||||
|
|
||||||
|
override func mouseDown(with event: NSEvent) {
|
||||||
|
dlog("OverlayWindow: mouseDown at \(event.locationInWindow)")
|
||||||
|
super.mouseDown(with: event)
|
||||||
|
}
|
||||||
|
|
||||||
|
override func sendEvent(_ event: NSEvent) {
|
||||||
|
if event.type == .leftMouseDown {
|
||||||
|
dlog("OverlayWindow: sendEvent .leftMouseDown at \(event.locationInWindow)")
|
||||||
|
// Fallback: Manually check if the click is in the maximize button area
|
||||||
|
if let contentView = self.contentView,
|
||||||
|
let statusView = contentView.subviews.first(where: { $0 is StatusView }) as? StatusView {
|
||||||
|
let pointInStatusView = statusView.convert(event.locationInWindow, from: nil)
|
||||||
|
if let hitView = statusView.hitTest(pointInStatusView), hitView.toolTip == "Show Main Window" {
|
||||||
|
dlog("OverlayWindow: Manual hit detected for maximize button via sendEvent")
|
||||||
|
NotificationCenter.default.post(name: NSNotification.Name("ProleStatus.showMainWindow"), object: nil)
|
||||||
|
return // Intercepted
|
||||||
|
}
|
||||||
|
}
|
||||||
|
}
|
||||||
|
super.sendEvent(event)
|
||||||
|
}
|
||||||
|
|
||||||
override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
|
override init(contentRect: NSRect, styleMask style: NSWindow.StyleMask, backing backingStoreType: NSWindow.BackingStoreType, defer flag: Bool) {
|
||||||
super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
|
super.init(contentRect: contentRect, styleMask: style, backing: backingStoreType, defer: flag)
|
||||||
isOpaque = false
|
isOpaque = false
|
||||||
backgroundColor = .clear
|
backgroundColor = .clear
|
||||||
// Default to fully click-through. We temporarily disable this only while showing the context menu.
|
// Default to NOT click-through so we can intercept clicks on the maximize button.
|
||||||
ignoresMouseEvents = true
|
// TransparentContainerView.hitTest handles passing through clicks for non-button areas.
|
||||||
|
ignoresMouseEvents = false
|
||||||
acceptsMouseMovedEvents = true
|
acceptsMouseMovedEvents = true
|
||||||
dlog("OverlayWindow initialized (borderless, transparent, accepts mouse moved events)")
|
isMovableByWindowBackground = false
|
||||||
|
// Ensure the window level is high and it doesn't ignore mouse events
|
||||||
|
level = NSWindow.Level(rawValue: Int(CGShieldingWindowLevel()) + 1)
|
||||||
|
dlog("OverlayWindow initialized (borderless, transparent, level: \(level.rawValue))")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -149,13 +168,8 @@ final class OverlayWindow: NSWindow {
|
|||||||
// while still allowing mouse-moved events for subviews that add tracking areas.
|
// while still allowing mouse-moved events for subviews that add tracking areas.
|
||||||
final class TransparentContainerView: NSView {
|
final class TransparentContainerView: NSView {
|
||||||
override func hitTest(_ point: NSPoint) -> NSView? {
|
override func hitTest(_ point: NSPoint) -> NSView? {
|
||||||
// Allow interaction only with interactive subviews (e.g., buttons inside StatusView).
|
let view = super.hitTest(point)
|
||||||
// Otherwise, pass clicks through.
|
dlog("TransparentContainerView: hitTest at \(point) -> \(view?.description ?? "nil")")
|
||||||
if let hit = super.hitTest(point) {
|
return view
|
||||||
if hit is NSButton { return hit }
|
|
||||||
// If a subview wants events but isn't a button, also allow it
|
|
||||||
if hit.acceptsFirstResponder { return hit }
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|||||||
@ -15,6 +15,39 @@ enum PFScriptBridge {
|
|||||||
|
|
||||||
static func status() -> (code: Int32, out: String, err: String) { runScript(arg: "status") }
|
static func status() -> (code: Int32, out: String, err: String) { runScript(arg: "status") }
|
||||||
|
|
||||||
|
static func dbStatus() -> (code: Int32, out: String, err: String) {
|
||||||
|
runRawCommand(command: "kubecolor cnpg status prole-db", description: "kubecolor cnpg status prole-db")
|
||||||
|
}
|
||||||
|
|
||||||
|
private static func runRawCommand(command: String, description: String) -> (code: Int32, out: String, err: String) {
|
||||||
|
lastInvocation = description
|
||||||
|
let p = Process()
|
||||||
|
p.executableURL = URL(fileURLWithPath: "/bin/bash")
|
||||||
|
p.arguments = ["-lc", command]
|
||||||
|
// Use home as default CWD
|
||||||
|
let env = ProcessInfo.processInfo.environment
|
||||||
|
let home = env["HOME"] ?? NSHomeDirectory()
|
||||||
|
let proleHomeEnv = env["PROLE_HOME"]
|
||||||
|
p.currentDirectoryURL = URL(fileURLWithPath: proleHomeEnv ?? home)
|
||||||
|
|
||||||
|
Logger.shared.info("invoking raw: \(description)")
|
||||||
|
|
||||||
|
let outPipe = Pipe(); let errPipe = Pipe()
|
||||||
|
p.standardOutput = outPipe
|
||||||
|
p.standardError = errPipe
|
||||||
|
do { try p.run() } catch {
|
||||||
|
let errStr = String(describing: error)
|
||||||
|
Logger.shared.error("spawn error: \(errStr)")
|
||||||
|
return (-1, "", errStr)
|
||||||
|
}
|
||||||
|
p.waitUntilExit()
|
||||||
|
let out = String(data: outPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
|
||||||
|
let err = String(data: errPipe.fileHandleForReading.readDataToEndOfFile(), encoding: .utf8) ?? ""
|
||||||
|
let code = p.terminationStatus
|
||||||
|
|
||||||
|
return (code, out, err)
|
||||||
|
}
|
||||||
|
|
||||||
private static func runScript(arg: String) -> (code: Int32, out: String, err: String) {
|
private static func runScript(arg: String) -> (code: Int32, out: String, err: String) {
|
||||||
let fm = FileManager.default
|
let fm = FileManager.default
|
||||||
let env = ProcessInfo.processInfo.environment
|
let env = ProcessInfo.processInfo.environment
|
||||||
|
|||||||
@ -8,7 +8,7 @@ final class StatusView: NSView {
|
|||||||
let tf = NSTextField(labelWithString: "0000-00-00 00:00:00 +00:00")
|
let tf = NSTextField(labelWithString: "0000-00-00 00:00:00 +00:00")
|
||||||
tf.textColor = .secondaryLabelColor
|
tf.textColor = .secondaryLabelColor
|
||||||
tf.alignment = .left
|
tf.alignment = .left
|
||||||
tf.font = NSFont.monospacedDigitSystemFont(ofSize: NSFont.systemFontSize(for: .small), weight: .regular)
|
tf.font = NSFont.monospacedDigitSystemFont(ofSize: 10, weight: .regular)
|
||||||
tf.setContentHuggingPriority(.required, for: .horizontal)
|
tf.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
tf.setContentCompressionResistancePriority(.required, for: .horizontal)
|
tf.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
return tf
|
return tf
|
||||||
@ -17,7 +17,10 @@ final class StatusView: NSView {
|
|||||||
private let maximizeButton: NSButton = {
|
private let maximizeButton: NSButton = {
|
||||||
let b = NSButton(title: "□", target: nil, action: nil)
|
let b = NSButton(title: "□", target: nil, action: nil)
|
||||||
b.bezelStyle = .texturedRounded
|
b.bezelStyle = .texturedRounded
|
||||||
|
b.setButtonType(.momentaryPushIn)
|
||||||
b.toolTip = "Show Main Window"
|
b.toolTip = "Show Main Window"
|
||||||
|
b.isEnabled = true
|
||||||
|
b.refusesFirstResponder = true // Don't take focus away from other apps
|
||||||
b.setContentHuggingPriority(.required, for: .horizontal)
|
b.setContentHuggingPriority(.required, for: .horizontal)
|
||||||
b.setContentCompressionResistancePriority(.required, for: .horizontal)
|
b.setContentCompressionResistancePriority(.required, for: .horizontal)
|
||||||
return b
|
return b
|
||||||
@ -68,18 +71,26 @@ final class StatusView: NSView {
|
|||||||
|
|
||||||
// Configure marquee width to be ~64 monospace characters
|
// Configure marquee width to be ~64 monospace characters
|
||||||
configureMarqueeWidth()
|
configureMarqueeWidth()
|
||||||
|
|
||||||
|
// Ensure button responds to mouse down immediately for better responsiveness in overlays
|
||||||
|
maximizeButton.sendAction(on: [.leftMouseDown, .leftMouseUp])
|
||||||
}
|
}
|
||||||
|
|
||||||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||||||
|
|
||||||
override func hitTest(_ point: NSPoint) -> NSView? {
|
override func hitTest(_ point: NSPoint) -> NSView? {
|
||||||
// Allow interaction with our inline controls and status lights (for tooltips),
|
// Allow interaction ONLY with the maximize button.
|
||||||
// pass other clicks through to avoid blocking the menu bar.
|
// Other subviews (like traffic lights) are no longer returned here to ensure they are click-through.
|
||||||
if let v = super.hitTest(point) {
|
// Only the small area of the box click should be active.
|
||||||
if v is NSButton { return v }
|
|
||||||
if v is TrafficLight { return v }
|
// Convert point to maximizeButton's coordinate system
|
||||||
if let tl = v.toolTip, !tl.isEmpty { return v }
|
let pointInButton = convert(point, to: maximizeButton)
|
||||||
|
let hit = maximizeButton.bounds.contains(pointInButton)
|
||||||
|
dlog("StatusView: hitTest at \(point) (inButton: \(pointInButton)) -> \(hit ? "maximizeButton" : "nil") | buttonFrame=\(maximizeButton.frame) window=\(String(describing: window))")
|
||||||
|
if hit {
|
||||||
|
return maximizeButton
|
||||||
}
|
}
|
||||||
|
|
||||||
return nil
|
return nil
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -144,6 +155,7 @@ final class StatusView: NSView {
|
|||||||
|
|
||||||
// MARK: - Button actions
|
// MARK: - Button actions
|
||||||
@objc private func didTapMaximize() {
|
@objc private func didTapMaximize() {
|
||||||
|
dlog("StatusView: didTapMaximize button clicked")
|
||||||
// Explicitly request the main window to show (don’t toggle modes implicitly)
|
// Explicitly request the main window to show (don’t toggle modes implicitly)
|
||||||
NotificationCenter.default.post(name: .showMainWindow, object: nil)
|
NotificationCenter.default.post(name: .showMainWindow, object: nil)
|
||||||
}
|
}
|
||||||
@ -172,7 +184,7 @@ final class MarqueeView: NSView {
|
|||||||
|
|
||||||
// Configure labels
|
// Configure labels
|
||||||
for lbl in [label1, label2] {
|
for lbl in [label1, label2] {
|
||||||
lbl.font = NSFont.monospacedSystemFont(ofSize: NSFont.systemFontSize(for: .small), weight: .regular)
|
lbl.font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
|
||||||
lbl.textColor = .secondaryLabelColor
|
lbl.textColor = .secondaryLabelColor
|
||||||
lbl.alignment = .left
|
lbl.alignment = .left
|
||||||
lbl.backgroundColor = .clear
|
lbl.backgroundColor = .clear
|
||||||
@ -244,7 +256,7 @@ final class MarqueeView: NSView {
|
|||||||
}
|
}
|
||||||
|
|
||||||
private func intrinsicLineHeight() -> CGFloat {
|
private func intrinsicLineHeight() -> CGFloat {
|
||||||
let f = label1.font ?? NSFont.monospacedSystemFont(ofSize: NSFont.systemFontSize(for: .small), weight: .regular)
|
let f = label1.font ?? NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
|
||||||
return ceil(f.ascender - f.descender)
|
return ceil(f.ascender - f.descender)
|
||||||
}
|
}
|
||||||
|
|
||||||
@ -295,7 +307,7 @@ private func widthForMonospaceCharacters(_ count: Int, font: NSFont) -> CGFloat
|
|||||||
private extension StatusView {
|
private extension StatusView {
|
||||||
func configureMarqueeWidth() {
|
func configureMarqueeWidth() {
|
||||||
// Match font with timestamp for visual cohesion
|
// Match font with timestamp for visual cohesion
|
||||||
let font = NSFont.monospacedSystemFont(ofSize: NSFont.systemFontSize(for: .small), weight: .regular)
|
let font = NSFont.monospacedSystemFont(ofSize: 10, weight: .regular)
|
||||||
marquee.heightAnchor.constraint(equalToConstant: ceil(font.capHeight * 1.8)).isActive = true
|
marquee.heightAnchor.constraint(equalToConstant: ceil(font.capHeight * 1.8)).isActive = true
|
||||||
let width = widthForMonospaceCharacters(64, font: font)
|
let width = widthForMonospaceCharacters(64, font: font)
|
||||||
let wConstraint = marquee.widthAnchor.constraint(equalToConstant: width)
|
let wConstraint = marquee.widthAnchor.constraint(equalToConstant: width)
|
||||||
|
|||||||
Loading…
Reference in New Issue
Block a user