165 lines
5.4 KiB
Swift
165 lines
5.4 KiB
Swift
import MastermindPOCCore
|
|
import SwiftUI
|
|
|
|
final class OverlayViewModel: ObservableObject {
|
|
@Published var statusTitle = "Mastermind: Idle"
|
|
@Published var statusDetail = "Native macOS companion proof"
|
|
@Published var clickThroughEnabled = false
|
|
@Published var backgroundOpacity = OverlaySettings.default.backgroundOpacity
|
|
@Published var screenFrames = 0
|
|
@Published var microphoneFrames = 0
|
|
@Published var microphoneBytes = 0
|
|
@Published var systemFrames = 0
|
|
@Published var systemBytes = 0
|
|
|
|
func apply(status: TrustStatus) {
|
|
statusTitle = status.menuBarTitle
|
|
|
|
switch status {
|
|
case .idle:
|
|
statusDetail = "Ready. Capture is off."
|
|
case .listening:
|
|
statusDetail = "Microphone capture is active."
|
|
case .screenContext:
|
|
statusDetail = "Screen context proof is active."
|
|
case .systemAudio:
|
|
statusDetail = "System audio proof is active."
|
|
case .agentWorking:
|
|
statusDetail = "Agent work placeholder."
|
|
case .paused:
|
|
statusDetail = "All capture paused."
|
|
case .permissionNeeded(let message):
|
|
statusDetail = message
|
|
case .error(let message):
|
|
statusDetail = message
|
|
}
|
|
}
|
|
}
|
|
|
|
struct OverlayView: View {
|
|
@ObservedObject var viewModel: OverlayViewModel
|
|
let actions: OverlayActions
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 12) {
|
|
HudTitleBar(viewModel: viewModel, actions: actions)
|
|
|
|
VStack(alignment: .leading, spacing: 4) {
|
|
Text(viewModel.statusTitle)
|
|
.font(.system(.title3, design: .rounded, weight: .semibold))
|
|
.foregroundStyle(.white)
|
|
Text(viewModel.statusDetail)
|
|
.font(.callout)
|
|
.foregroundStyle(.white.opacity(0.78))
|
|
.lineLimit(2)
|
|
}
|
|
|
|
Divider()
|
|
.overlay(.white.opacity(0.2))
|
|
|
|
HStack(spacing: 14) {
|
|
CounterView(label: "Screen", value: viewModel.screenFrames)
|
|
CounterView(label: "Mic", value: viewModel.microphoneFrames)
|
|
CounterView(label: "System", value: viewModel.systemFrames)
|
|
}
|
|
|
|
Text("PCM bytes mic \(viewModel.microphoneBytes) | system \(viewModel.systemBytes)")
|
|
.font(.caption2.monospacedDigit())
|
|
.foregroundStyle(.white.opacity(0.62))
|
|
}
|
|
.padding(18)
|
|
.frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .topLeading)
|
|
.background(
|
|
RoundedRectangle(cornerRadius: 18, style: .continuous)
|
|
.fill(.black.opacity(viewModel.backgroundOpacity))
|
|
.stroke(.white.opacity(0.16), lineWidth: 1)
|
|
)
|
|
}
|
|
}
|
|
|
|
private struct HudTitleBar: View {
|
|
@ObservedObject var viewModel: OverlayViewModel
|
|
let actions: OverlayActions
|
|
|
|
var body: some View {
|
|
HStack(spacing: 10) {
|
|
ZStack(alignment: .leading) {
|
|
WindowDragRegion()
|
|
HStack(spacing: 8) {
|
|
Image(systemName: "sparkles")
|
|
.font(.caption.weight(.semibold))
|
|
Text("Mastermind")
|
|
.font(.headline)
|
|
Text(viewModel.clickThroughEnabled ? "Click-through" : "Interactive")
|
|
.font(.caption)
|
|
.foregroundStyle(.white.opacity(0.66))
|
|
}
|
|
.foregroundStyle(.white)
|
|
.allowsHitTesting(false)
|
|
}
|
|
.frame(height: 28)
|
|
|
|
HStack(spacing: 6) {
|
|
HudIconButton(systemName: "gearshape", help: "Settings", action: actions.openSettings)
|
|
HudIconButton(systemName: "eye.slash", help: "Hide assistant", action: actions.hideOverlay)
|
|
HudIconButton(systemName: "xmark", help: "Quit Mastermind POC", role: .destructive, action: actions.quitApp)
|
|
}
|
|
}
|
|
}
|
|
}
|
|
|
|
private struct HudIconButton: View {
|
|
let systemName: String
|
|
let help: String
|
|
var role: ButtonRole?
|
|
let action: () -> Void
|
|
|
|
var body: some View {
|
|
Button(role: role, action: action) {
|
|
Image(systemName: systemName)
|
|
.font(.caption.weight(.semibold))
|
|
.frame(width: 24, height: 24)
|
|
.contentShape(Rectangle())
|
|
}
|
|
.buttonStyle(.plain)
|
|
.foregroundStyle(.white.opacity(0.82))
|
|
.background(.white.opacity(0.08), in: RoundedRectangle(cornerRadius: 6, style: .continuous))
|
|
.help(help)
|
|
}
|
|
}
|
|
|
|
private struct WindowDragRegion: NSViewRepresentable {
|
|
func makeNSView(context: Context) -> DragHandleView {
|
|
DragHandleView()
|
|
}
|
|
|
|
func updateNSView(_ nsView: DragHandleView, context: Context) {}
|
|
}
|
|
|
|
private final class DragHandleView: NSView {
|
|
override var mouseDownCanMoveWindow: Bool {
|
|
true
|
|
}
|
|
|
|
override func mouseDown(with event: NSEvent) {
|
|
window?.performDrag(with: event)
|
|
}
|
|
}
|
|
|
|
private struct CounterView: View {
|
|
let label: String
|
|
let value: Int
|
|
|
|
var body: some View {
|
|
VStack(alignment: .leading, spacing: 3) {
|
|
Text(label)
|
|
.font(.caption2)
|
|
.foregroundStyle(.white.opacity(0.58))
|
|
Text("\(value)")
|
|
.font(.caption.monospacedDigit().weight(.semibold))
|
|
.foregroundStyle(.white)
|
|
}
|
|
.frame(maxWidth: .infinity, alignment: .leading)
|
|
}
|
|
}
|