mirror of
https://github.com/dredx/prole.git
synced 2026-09-23 11:03:59 +00:00
- Default to Application Window mode with full app menu; status bar overlay remains available - Restore clickable startup Splash Tip (5s) with animated GIF and live counter; use light theme (Aqua) - Status bar item: monospaced "P" icon; left click shows overlay (status mode) or main window (app mode) - Overlay: add Maximize button (□) to toggle back to main window; keep click‑through elsewhere - Global hotkey Cmd+Opt+Shift+P toggles modes; Prole menu item mirrors the same toggle and updates title dynamically - Application menu (Prole): Show Status Bar / Show Main Window (contextual), Refresh Now, Quit - Application Window layout: non‑scrolling, vertical 1‑line rows (svc, k3s aggregate, local) with top‑right timestamp; bottom‑left controls (⟳ Refresh, _ Minimize) - Parameterize endpoints via `prole.properties` (bundled + user override). Replace legacy raspberry with retropie defaults - ServiceChecker + UI read endpoints from Config loader; tooltips/labels reflect configured hosts/ports - Build script: generate `Info.plist` with `LSUIElement=false`; bundle resources (`prole-type.gif`, `prole.properties`); ad‑hoc codesign. Universal build supported - Documentation: rewrite README with technical build/run/config details and operational posture Files: - proleStatus/Sources/: AppDelegate.swift, OverlayWindow.swift, StatusView.swift, StatusItemController.swift, SplashTipWindowController.swift, MainWindowController.swift, AppStatusView.swift, ServiceChecker.swift, Config.swift - proleStatus/build.sh - proleStatus/prole.properties - proleStatus/README.md Notes: - Build verified via `./build.sh build` (arm64). App starts in App Window mode with light theme; menu + hotkey + overlay toggle operate as intended
94 lines
3.8 KiB
Swift
94 lines
3.8 KiB
Swift
import AppKit
|
||
|
||
// MainWindowController builds the regular window you can move/resize.
|
||
// It embeds an AppStatusView (3 simple rows, no scrolling) and a tiny
|
||
// control bar in the bottom‑left with:
|
||
// - ⟳ Refresh (forces a status check)
|
||
// - _ Minimize to Status Bar (switches to overlay mode)
|
||
final class MainWindowController: NSWindowController {
|
||
struct Actions {
|
||
let onRefresh: () -> Void
|
||
let onMinimizeToStatusBar: () -> Void
|
||
}
|
||
|
||
private let statusView = AppStatusView()
|
||
private let actions: Actions
|
||
|
||
init(serviceChecker: ServiceChecker, actions: Actions) {
|
||
// Initialize stored properties before calling super.init
|
||
self.actions = actions
|
||
let style: NSWindow.StyleMask = [.titled, .closable, .miniaturizable, .resizable]
|
||
let initialRect = NSRect(x: 0, y: 0, width: 720, height: 420)
|
||
let window = NSWindow(contentRect: initialRect, styleMask: style, backing: .buffered, defer: false)
|
||
super.init(window: window)
|
||
|
||
window.isReleasedWhenClosed = false
|
||
window.title = "ProleStatus"
|
||
window.center()
|
||
window.level = .normal
|
||
window.collectionBehavior = [.canJoinAllSpaces]
|
||
window.appearance = NSAppearance(named: .aqua)
|
||
|
||
// A plain NSView acts as the content container
|
||
let content = NSView()
|
||
content.translatesAutoresizingMaskIntoConstraints = false
|
||
window.contentView = content
|
||
|
||
statusView.translatesAutoresizingMaskIntoConstraints = false
|
||
content.addSubview(statusView)
|
||
|
||
// Bottom-left control bar with Refresh and Minimize buttons
|
||
let controls = NSStackView()
|
||
controls.orientation = .horizontal
|
||
controls.spacing = 8
|
||
controls.alignment = .centerY
|
||
controls.translatesAutoresizingMaskIntoConstraints = false
|
||
content.addSubview(controls)
|
||
|
||
let refreshButton = NSButton(title: "⟳", target: nil, action: nil)
|
||
refreshButton.bezelStyle = .texturedRounded
|
||
refreshButton.toolTip = "Refresh Now"
|
||
refreshButton.target = self
|
||
refreshButton.action = #selector(didTapRefresh)
|
||
|
||
let minimizeButton = NSButton(title: "_", target: nil, action: nil)
|
||
minimizeButton.bezelStyle = .texturedRounded
|
||
minimizeButton.toolTip = "Minimize to Status Bar"
|
||
minimizeButton.target = self
|
||
minimizeButton.action = #selector(didTapMinimize)
|
||
|
||
controls.addArrangedSubview(refreshButton)
|
||
controls.addArrangedSubview(minimizeButton)
|
||
NSLayoutConstraint.activate([
|
||
content.widthAnchor.constraint(equalToConstant: initialRect.width),
|
||
content.heightAnchor.constraint(equalToConstant: initialRect.height),
|
||
// AppStatusView pinned to top/leading/trailing so timestamp can sit top-right
|
||
statusView.leadingAnchor.constraint(equalTo: content.leadingAnchor),
|
||
statusView.trailingAnchor.constraint(equalTo: content.trailingAnchor),
|
||
statusView.topAnchor.constraint(equalTo: content.topAnchor),
|
||
controls.leadingAnchor.constraint(equalTo: content.leadingAnchor, constant: 8),
|
||
controls.bottomAnchor.constraint(equalTo: content.bottomAnchor, constant: -8)
|
||
])
|
||
|
||
statusView.bindTo(serviceChecker: serviceChecker)
|
||
}
|
||
|
||
required init?(coder: NSCoder) { fatalError("init(coder:) has not been implemented") }
|
||
|
||
func show() {
|
||
guard let window = window else { return }
|
||
window.makeKeyAndOrderFront(nil)
|
||
NSApp.activate(ignoringOtherApps: false)
|
||
}
|
||
|
||
func hide() {
|
||
window?.orderOut(nil)
|
||
}
|
||
|
||
var isVisible: Bool { window?.isVisible ?? false }
|
||
|
||
// MARK: - Actions
|
||
@objc private func didTapRefresh() { actions.onRefresh() }
|
||
@objc private func didTapMinimize() { actions.onMinimizeToStatusBar() }
|
||
}
|