Initial MacTaskManager release
This commit is contained in:
@@ -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)
|
||||
}
|
||||
}
|
||||
Reference in New Issue
Block a user