Files
Mastermind/native/MastermindPOC/Sources/MastermindPOC/OverlayView.swift
T

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)
}
}