Initial MacTaskManager release

This commit is contained in:
2026-08-14 13:07:44 -04:00
commit 9795096656
47 changed files with 5173 additions and 0 deletions
+196
View File
@@ -0,0 +1,196 @@
import AppKit
import SwiftUI
struct ContentView: View {
@Environment(SystemMonitor.self) private var monitor
@State private var selection: TaskSection?
@State private var searchText = ""
@State private var showingAbout = false
@State private var showingNewTask = false
@State private var detailSelection: Int32?
@State private var columnVisibility: NavigationSplitViewVisibility = .all
@AppStorage("sidebarCompact") private var sidebarCompact = false
init() {
let stored = UserDefaults.standard.string(forKey: "defaultStartPage") ?? TaskSection.processes.rawValue
_selection = State(initialValue: TaskSection(rawValue: stored) ?? .processes)
}
var body: some View {
NavigationSplitView(columnVisibility: $columnVisibility) {
Sidebar(selection: $selection, isCompact: sidebarCompact)
.navigationSplitViewColumnWidth(
min: sidebarCompact ? 68 : 190,
ideal: sidebarCompact ? 68 : 210,
max: sidebarCompact ? 68 : 250
)
} detail: {
Group {
switch selection ?? .processes {
case .processes:
ProcessesView(searchText: searchText) { process in
detailSelection = process.pid
selection = .details
}
case .performance:
PerformanceView()
case .history:
AppHistoryView(searchText: searchText) { process in
detailSelection = process.pid
selection = .details
}
case .startup:
StartupAppsView()
case .users:
UsersView { process in
detailSelection = process.pid
selection = .details
}
case .details:
DetailsView(searchText: searchText, selection: $detailSelection)
case .services:
ServicesView(searchText: searchText) { process in
detailSelection = process.pid
selection = .details
}
case .settings:
SettingsView()
}
}
.searchable(text: $searchText, placement: .toolbar, prompt: "Type a name, PID, or user")
.toolbar {
TaskToolbarContent(showingNewTask: $showingNewTask, showingAbout: $showingAbout)
}
}
.navigationTitle("Task Manager")
.animation(.snappy(duration: 0.22), value: sidebarCompact)
.onChange(of: columnVisibility) { _, visibility in
guard visibility == .detailOnly else { return }
columnVisibility = .all
DispatchQueue.main.asyncAfter(deadline: .now() + 0.08) {
withAnimation(.snappy(duration: 0.22)) { sidebarCompact.toggle() }
}
}
.sheet(isPresented: $showingNewTask) {
NewTaskView(isPresented: $showingNewTask)
}
.alert("Task Manager", isPresented: Binding(
get: { monitor.errorMessage != nil },
set: { if !$0 { monitor.errorMessage = nil } }
)) {
Button("OK", role: .cancel) { monitor.errorMessage = nil }
} message: {
Text(monitor.errorMessage ?? "")
}
.alert("Task Manager for macOS", isPresented: $showingAbout) {
Button("OK", role: .cancel) { }
} message: {
Text("A native SwiftUI system monitor inspired by Windows Task Manager.")
}
.onAppear {
DispatchQueue.main.async {
NSApp.keyWindow?.level = UserDefaults.standard.bool(forKey: "alwaysOnTop") ? .floating : .normal
}
}
}
}
private struct TaskToolbarContent: ToolbarContent {
@Environment(SystemMonitor.self) private var monitor
@Binding var showingNewTask: Bool
@Binding var showingAbout: Bool
var body: some ToolbarContent {
@Bindable var monitor = monitor
ToolbarItemGroup(placement: .primaryAction) {
Button {
showingNewTask = true
} label: {
Label("Run new task", systemImage: "plus.square")
}
.help("Launch an application or executable")
Button {
monitor.refresh()
} label: {
Label("Refresh", systemImage: "arrow.clockwise")
}
.help("Refresh now (⌘R)")
Menu {
UpdateSpeedPicker(selection: $monitor.updateSpeed)
Divider()
Button("About Task Manager") { showingAbout = true }
} label: {
Image(systemName: "ellipsis")
}
.menuStyle(.borderlessButton)
}
}
}
private struct Sidebar: View {
@Binding var selection: TaskSection?
let isCompact: Bool
var body: some View {
VStack(spacing: 0) {
ScrollView {
VStack(spacing: 4) {
ForEach(TaskSection.allCases.filter { $0 != .settings }) { section in
Button {
selection = section
} label: {
sidebarLabel(section.rawValue, icon: section.icon)
.padding(.horizontal, isCompact ? 0 : 8)
.frame(height: 40)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.frame(maxWidth: .infinity)
.background(selection == section ? Color.accentColor.opacity(0.18) : .clear)
.clipShape(RoundedRectangle(cornerRadius: 7))
.help(section.rawValue)
.accessibilityLabel(section.rawValue)
.accessibilityAddTraits(selection == section ? .isSelected : [])
}
}
.padding(.horizontal, isCompact ? 8 : 10)
.padding(.vertical, 8)
}
Divider().padding(.horizontal, isCompact ? 12 : 16)
Button {
selection = .settings
} label: {
sidebarLabel("Settings", icon: "gearshape")
.padding(.horizontal, isCompact ? 0 : 8)
.frame(height: 40)
.contentShape(Rectangle())
}
.buttonStyle(.plain)
.font(.callout)
.frame(maxWidth: .infinity)
.background(selection == .settings ? Color.accentColor.opacity(0.18) : .clear)
.clipShape(RoundedRectangle(cornerRadius: 7))
.padding(.horizontal, isCompact ? 8 : 10)
.padding(.vertical, 8)
.foregroundStyle(selection == .settings ? Color.primary : Color.secondary)
.help("Settings")
.accessibilityLabel("Settings")
}
}
private func sidebarLabel(_ title: String, icon: String) -> some View {
HStack(spacing: 10) {
Image(systemName: icon)
.frame(width: 22, height: 20)
if !isCompact {
Text(title).lineLimit(1)
Spacer(minLength: 0)
}
}
.frame(maxWidth: .infinity, alignment: isCompact ? .center : .leading)
}
}