From 54f417cb5b1e69bcc40212b066c6dc770decc294 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Tue, 30 Jul 2024 10:06:02 -0500 Subject: [PATCH 01/15] Made a few minor adjustments to the activity bar UI. --- .../Features/ActivityViewer/ActivityViewer.swift | 6 ++++-- .../Notificaitons/CustomLoadingRingView.swift | 4 ++-- .../Notificaitons/TaskNotificationView.swift | 7 ++++--- .../ActivityViewer/Tasks/SchemeDropDownView.swift | 13 +++++++------ .../ActivityViewer/Tasks/TaskDropDownView.swift | 7 ++++--- .../CodeEditWindowController+Toolbar.swift | 2 +- CodeEdit/Features/Tasks/CEActiveTask.swift | 6 +++--- 7 files changed, 25 insertions(+), 20 deletions(-) diff --git a/CodeEdit/Features/ActivityViewer/ActivityViewer.swift b/CodeEdit/Features/ActivityViewer/ActivityViewer.swift index e96d73bad..1314f565e 100644 --- a/CodeEdit/Features/ActivityViewer/ActivityViewer.swift +++ b/CodeEdit/Features/ActivityViewer/ActivityViewer.swift @@ -46,11 +46,13 @@ struct ActivityViewer: View { .fixedSize() } .fixedSize(horizontal: false, vertical: false) - .padding(.horizontal, 10) + .padding(.horizontal, 5) + .padding(.vertical, 1.5) + .frame(height: 22) .background { if colorScheme == .dark { RoundedRectangle(cornerRadius: 5) - .opacity(0.10) + .opacity(0.1) } else { RoundedRectangle(cornerRadius: 5) .opacity(0.1) diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/CustomLoadingRingView.swift b/CodeEdit/Features/ActivityViewer/Notificaitons/CustomLoadingRingView.swift index a7d04f087..81e8bd5ae 100644 --- a/CodeEdit/Features/ActivityViewer/Notificaitons/CustomLoadingRingView.swift +++ b/CodeEdit/Features/ActivityViewer/Notificaitons/CustomLoadingRingView.swift @@ -22,12 +22,12 @@ struct CustomLoadingRingView: View { if let progress = progress { Circle() .trim(from: 0, to: progress) - .stroke(Color.blue.gradient, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)) + .stroke(Color.accentColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)) .animation(.easeInOut, value: progress) } else { Circle() .trim(from: 0, to: 0.5) - .stroke(Color.blue.gradient, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)) + .stroke(Color.accentColor, style: StrokeStyle(lineWidth: lineWidth, lineCap: .round)) .rotationEffect( previousValue ? .degrees(isAnimating ? 0 : -360) diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift index 4c1138313..f486791f6 100644 --- a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift +++ b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift @@ -23,8 +23,8 @@ struct TaskNotificationView: View { progress: notification.percentage, currentTaskCount: taskNotificationHandler.notifications.count ) - .padding(.leading, 5) - .frame(height: 15) + .padding(.horizontal, -1) + .frame(height: 14) } else { if taskNotificationHandler.notifications.count > 1 { Text("\(taskNotificationHandler.notifications.count)") @@ -33,7 +33,7 @@ struct TaskNotificationView: View { .background( Circle() .foregroundStyle(.gray) - .opacity(0.3) + .opacity(0.2) ) .padding(-5) } @@ -52,6 +52,7 @@ struct TaskNotificationView: View { self.hovered = isHovered } .padding(-3) + .padding(.trailing, 3) .popover(isPresented: $isPresented) { TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler) }.onTapGesture { diff --git a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift index 01143ec37..428153c1b 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift @@ -18,7 +18,7 @@ struct SchemeDropDownView: View { var workspaceFileManager: CEWorkspaceFileManager? var body: some View { - HStack(spacing: 5) { + HStack(spacing: 6) { Image(systemName: "folder.badge.gearshape") .imageScale(.medium) @@ -31,8 +31,9 @@ struct SchemeDropDownView: View { }.font(.subheadline) } .font(.caption) - .padding(.trailing, 9) - .padding(5) + .padding(.trailing, 11.5) + .padding(.horizontal, 2.5) + .padding(.vertical, 2.5) .background { Color(nsColor: colorScheme == .dark ? .white : .black) .opacity(isHoveringScheme || isSchemePopOverPresented ? 0.05 : 0) @@ -41,10 +42,10 @@ struct SchemeDropDownView: View { Spacer() if isHoveringScheme || isSchemePopOverPresented { chevronDown - .padding(.trailing, 3) + .padding(.trailing, 2) } else { chevron - .padding(.trailing, 3) + .padding(.trailing, 4) } } } @@ -90,7 +91,7 @@ struct SchemeDropDownView: View { VStack(spacing: 1) { Image(systemName: "chevron.down") } - .font(.system(size: 8, weight: .bold, design: .default)) + .font(.system(size: 8, weight: .semibold, design: .default)) .padding(.top, 0.5) } } diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift index 29175f183..86275b5c5 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift @@ -29,8 +29,9 @@ struct TaskDropDownView: View { .font(.subheadline) } } - .padding(.trailing, 9) - .padding(5) + .padding(.trailing, 11.5) + .padding(.horizontal, 2.5) + .padding(.vertical, 2.5) .background(backgroundColor) .onHover { hovering in self.isHoveringTasks = hovering @@ -62,7 +63,7 @@ struct TaskDropDownView: View { Image(systemName: "chevron.down") .font(.system(size: 8, weight: .bold, design: .default)) .padding(.top, 0.5) - .padding(.trailing, 3) + .padding(.trailing, 2) } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index d1d92b471..e5e0ce10a 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -121,7 +121,7 @@ extension CodeEditWindowController { toolbarItem.image = NSImage( systemSymbolName: "stop.fill", accessibilityDescription: nil - )?.withSymbolConfiguration(.init(scale: .large)) + )?.withSymbolConfiguration(.init(pointSize: 15, weight: .regular)) return toolbarItem case .startTaskSidebarItem: diff --git a/CodeEdit/Features/Tasks/CEActiveTask.swift b/CodeEdit/Features/Tasks/CEActiveTask.swift index fa1358804..1d1273369 100644 --- a/CodeEdit/Features/Tasks/CEActiveTask.swift +++ b/CodeEdit/Features/Tasks/CEActiveTask.swift @@ -76,7 +76,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { await updateOutput("\nFinished running \(task.name).\n\n") await updateTaskStatus(to: .finished) updateTaskNotification( - title: "Finished Running: \(task.name)", + title: "Finished Running \(task.name)", message: "", isLoading: false ) @@ -92,7 +92,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { await updateOutput("\nFailed to run \(task.name).\n\n") await updateTaskStatus(to: .failed) updateTaskNotification( - title: "Failed Running: \(task.name)", + title: "Failed Running \(task.name)", message: "", isLoading: false ) @@ -141,7 +141,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { let userInfo: [String: Any] = [ "id": self.task.id.uuidString, "action": "createWithPriority", - "title": "Running: \(self.task.name)", + "title": "Running \(self.task.name)", "message": "Running your task: \(self.task.name).", "isLoading": true ] From c154ddaa77a11a815731222f82b59ff531b26b2b Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Wed, 31 Jul 2024 00:53:00 -0500 Subject: [PATCH 02/15] Cleaned up activity viewer menus --- .../Tasks/DropdownMenuItemStyleModifier.swift | 20 +++++- .../Tasks/OptionMenuItemView.swift | 4 +- .../Tasks/SchemeDropDownView.swift | 12 ++-- .../Tasks/TaskDropDownView.swift | 61 +++++++++++-------- .../Tasks/WorkspaceMenuItemView.swift | 9 +-- .../CodeEditWindowController+Toolbar.swift | 28 +++++++++ 6 files changed, 96 insertions(+), 38 deletions(-) diff --git a/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift b/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift index e71f4e197..0285955a1 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift @@ -12,10 +12,28 @@ struct DropdownMenuItemStyleModifier: ViewModifier { func body(content: Content) -> some View { content - .background(isHovering ? Color(NSColor.systemBlue) : .clear) + .background(isHovering ? AnyView(HighlightedBackground()) : AnyView(Color.clear)) .foregroundColor(isHovering ? Color(NSColor.white) : .primary) .onHover(perform: { hovering in self.isHovering = hovering }) } } +struct SelectionVisualEffectView: NSViewRepresentable { + func makeNSView(context: Context) -> NSVisualEffectView { + let view = NSVisualEffectView() + view.material = .selection + view.blendingMode = .withinWindow + view.isEmphasized = true + return view + } + + func updateNSView(_ nsView: NSVisualEffectView, context: Context) {} +} + +struct HighlightedBackground: View { + var body: some View { + SelectionVisualEffectView() + .frame(maxWidth: .infinity, maxHeight: .infinity) + } +} diff --git a/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift b/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift index 9696362ad..a00c8ecdb 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/OptionMenuItemView.swift @@ -15,7 +15,7 @@ struct OptionMenuItemView: View { Text(label) Spacer() } - .padding(.vertical, 5) + .padding(.vertical, 4) .padding(.horizontal, 28) .modifier(DropdownMenuItemStyleModifier()) .clipShape(RoundedRectangle(cornerRadius: 5)) @@ -26,7 +26,7 @@ struct OptionMenuItemView: View { } #Preview { - OptionMenuItemView(label: "Tst") { + OptionMenuItemView(label: "Test") { print("test") } } diff --git a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift index 428153c1b..88b54c675 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift @@ -28,9 +28,9 @@ struct SchemeDropDownView: View { } else { Text(workspaceSettingsManager.settings.project.projectName) } - }.font(.subheadline) + } } - .font(.caption) + .font(.subheadline) .padding(.trailing, 11.5) .padding(.horizontal, 2.5) .padding(.vertical, 2.5) @@ -62,9 +62,9 @@ struct SchemeDropDownView: View { Divider() .padding(.vertical, 5) Group { - OptionMenuItemView(label: "Add Folder..") { + OptionMenuItemView(label: "Add Folder...") { // TODO: Implment Add Folder - print("NOT IMPLMENTED") + print("NOT IMPLEMENTED") } OptionMenuItemView(label: "Workspace Settings...") { NSApp.sendAction( @@ -74,7 +74,9 @@ struct SchemeDropDownView: View { } } .padding(5) - }.onTapGesture { + .font(.subheadline) + } + .onTapGesture { self.isSchemePopOverPresented.toggle() } } diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift index 86275b5c5..64b5d77cc 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift @@ -23,12 +23,13 @@ struct TaskDropDownView: View { TaskView(activeTask: selectedActiveTask, isCompact: true) } else { DefaultTaskView(task: selectedTask) + .fixedSize() } } else { Text("Create Tasks") - .font(.subheadline) } } + .font(.subheadline) .padding(.trailing, 11.5) .padding(.horizontal, 2.5) .padding(.vertical, 2.5) @@ -59,12 +60,10 @@ struct TaskDropDownView: View { } private var chevronIcon: some View { - VStack(spacing: 1) { - Image(systemName: "chevron.down") - .font(.system(size: 8, weight: .bold, design: .default)) - .padding(.top, 0.5) - .padding(.trailing, 2) - } + Image(systemName: "chevron.down") + .font(.system(size: 8, weight: .bold, design: .default)) + .padding(.top, 0.5) + .padding(.trailing, 2) } private var taskPopoverContent: some View { @@ -72,13 +71,14 @@ struct TaskDropDownView: View { if !taskManager.availableTasks.isEmpty { ForEach(taskManager.availableTasks, id: \.id) { task in TasksPopoverView(taskManager: taskManager, task: task) + .frame(maxWidth: .infinity) } Divider() .padding(.vertical, 5) } Group { - OptionMenuItemView(label: "Add Task..") { + OptionMenuItemView(label: "Add Task...") { NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) } OptionMenuItemView(label: "Manage Tasks...") { @@ -86,6 +86,7 @@ struct TaskDropDownView: View { } } } + .font(.subheadline) .padding(5) .frame(width: 215) } @@ -93,17 +94,20 @@ struct TaskDropDownView: View { private struct DefaultTaskView: View { @ObservedObject var task: CETask var body: some View { - HStack(spacing: 3) { + HStack(spacing: 5) { Image(systemName: "gearshape") .imageScale(.medium) - Text(task.name) - .font(.subheadline) - + Spacer(minLength: 0) + } + .padding(.trailing, 7.5) + .overlay(alignment: .trailing) { Circle() .fill(CETaskStatus.notRunning.color) .frame(width: 5, height: 5) + .padding(.trailing, 2.5) } + .font(.subheadline) } } } @@ -122,21 +126,20 @@ struct TaskView: View { var isCompact: Bool var body: some View { - HStack(spacing: isCompact ? 3 : 8) { + HStack(spacing: 5) { Image(systemName: "gearshape") .imageScale(.medium) - Text(activeTask.task.name) - .font(isCompact ? .subheadline : .body) - - if !isCompact { - Spacer() - } - + Spacer() + } + .padding(.trailing, 7.5) + .overlay(alignment: .trailing) { Circle() .fill(activeTask.status.color) .frame(width: 5, height: 5) + .padding(.trailing, 2.5) } + .font(.subheadline) } } @@ -148,12 +151,12 @@ struct TasksPopoverView: View { var task: CETask var body: some View { - HStack { + HStack(spacing: 5) { selectionIndicator popoverContent } - .padding(.vertical, 5) - .padding(.horizontal, 10) + .padding(.vertical, 4) + .padding(.horizontal, 8) .modifier(DropdownMenuItemStyleModifier()) .onTapGesture { taskManager.selectedTaskID = task.id @@ -166,11 +169,12 @@ struct TasksPopoverView: View { Group { if taskManager.selectedTaskID == task.id { Image(systemName: "checkmark") + .fontWeight(.bold) .imageScale(.small) .frame(width: 10) } else { Spacer() - .frame(width: 18) + .frame(width: 10) } } } @@ -178,7 +182,7 @@ struct TasksPopoverView: View { private var popoverContent: some View { Group { if let activeTask = taskManager.activeTasks[task.id] { - TaskView(activeTask: activeTask, isCompact: false) + TaskView(activeTask: activeTask, isCompact: true) } else { defaultTaskView } @@ -186,14 +190,19 @@ struct TasksPopoverView: View { } private var defaultTaskView: some View { - HStack { + HStack(spacing: 5) { Image(systemName: "gearshape") .imageScale(.medium) Text(task.name) Spacer() + } + .padding(.trailing, 7.5) + .overlay(alignment: .trailing) { Circle() .fill(taskManager.taskStatus(taskID: task.id).color) .frame(width: 5, height: 5) + .padding(.trailing, 2.5) } + .font(.subheadline) } } diff --git a/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift b/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift index cb6216e02..853bcdc95 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/WorkspaceMenuItemView.swift @@ -12,22 +12,23 @@ struct WorkspaceMenuItemView: View { var item: CEWorkspaceFile? var body: some View { - HStack { + HStack(spacing: 5) { if workspaceFileManager?.workspaceItem.fileName() == item?.name { Image(systemName: "checkmark") + .fontWeight(.bold) .imageScale(.small) .frame(width: 10) } else { Spacer() - .frame(width: 18) + .frame(width: 10) } Image(systemName: "folder.badge.gearshape") .imageScale(.medium) Text(item?.name ?? "") Spacer() } - .padding(.vertical, 5) - .padding(.horizontal, 10) + .padding(.vertical, 4) + .padding(.horizontal, 8) .modifier(DropdownMenuItemStyleModifier()) .onTapGesture { } .clipShape(RoundedRectangle(cornerRadius: 5)) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index e5e0ce10a..0aa053823 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -123,6 +123,20 @@ extension CodeEditWindowController { accessibilityDescription: nil )?.withSymbolConfiguration(.init(pointSize: 15, weight: .regular)) + let view = NSHostingView( + rootView: Button { + self.terminateActiveTask() + } label: { + Label("Stop", systemImage: "stop.fill") + .labelStyle(.iconOnly) + .font(.system(size: 15, weight: .regular)) + .help("Stop selected task") + .frame(width: 28) + .offset(CGSize(width: 0, height: 1.5)) + } + ) + toolbarItem.view = view + return toolbarItem case .startTaskSidebarItem: let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.startTaskSidebarItem) @@ -137,6 +151,20 @@ extension CodeEditWindowController { accessibilityDescription: nil )?.withSymbolConfiguration(.init(scale: .large)) + let view = NSHostingView( + rootView: Button { + self.runActiveTask() + } label: { + Label("Start", systemImage: "play.fill") + .labelStyle(.iconOnly) + .font(.system(size: 18, weight: .regular)) + .help("Start selected task") + .frame(width: 28) + .offset(CGSize(width: 0, height: 2.5)) + } + ) + toolbarItem.view = view + return toolbarItem case .branchPicker: let toolbarItem = NSToolbarItem(itemIdentifier: .branchPicker) From 446810b9618a733e561bb17018c8091cc6c9fa2f Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Wed, 31 Jul 2024 01:00:13 -0500 Subject: [PATCH 03/15] Gave spacers a minLength --- .../ActivityViewer/Tasks/TaskDropDownView.swift | 12 +++++------- 1 file changed, 5 insertions(+), 7 deletions(-) diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift index 64b5d77cc..26f09372e 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift @@ -20,7 +20,8 @@ struct TaskDropDownView: View { Group { if let selectedTask = taskManager.selectedTask { if let selectedActiveTask = taskManager.activeTasks[selectedTask.id] { - TaskView(activeTask: selectedActiveTask, isCompact: true) + TaskView(activeTask: selectedActiveTask) + .fixedSize() } else { DefaultTaskView(task: selectedTask) .fixedSize() @@ -117,20 +118,17 @@ struct TaskDropDownView: View { // 2. Reference types (like objects) do not notify observers when their internal state changes. /// `TaskView` represents a single active task and observes its state. /// - Parameter activeTask: The active task to be displayed and observed. -/// - Parameter isCompact: Determines the layout style of the view. /// Set to `true` for a compact display, used for the current task in the activity viewer. /// Set to `false`, used in the popover. struct TaskView: View { @ObservedObject var activeTask: CEActiveTask - // This property allows for compact layout adjustment - var isCompact: Bool var body: some View { HStack(spacing: 5) { Image(systemName: "gearshape") .imageScale(.medium) Text(activeTask.task.name) - Spacer() + Spacer(minLength: 0) } .padding(.trailing, 7.5) .overlay(alignment: .trailing) { @@ -182,7 +180,7 @@ struct TasksPopoverView: View { private var popoverContent: some View { Group { if let activeTask = taskManager.activeTasks[task.id] { - TaskView(activeTask: activeTask, isCompact: true) + TaskView(activeTask: activeTask) } else { defaultTaskView } @@ -194,7 +192,7 @@ struct TasksPopoverView: View { Image(systemName: "gearshape") .imageScale(.medium) Text(task.name) - Spacer() + Spacer(minLength: 0) } .padding(.trailing, 7.5) .overlay(alignment: .trailing) { From 2e0a704451fd658859f61cbbd657fb7a53f2b611 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Wed, 31 Jul 2024 14:31:55 -0500 Subject: [PATCH 04/15] Unified TaskView duplication. Refined workspace settings and add and edit task sheet design --- .../Tasks/SchemeDropDownView.swift | 21 +++-- .../Tasks/TaskDropDownView.swift | 94 +++++++------------ .../Views/AddCETaskView.swift | 35 +++---- .../Views/CETaskFormView.swift | 6 +- .../Views/CEWorkspaceSettingsView.swift | 6 +- .../Views/EditCETaskView.swift | 6 +- 6 files changed, 65 insertions(+), 103 deletions(-) diff --git a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift index 88b54c675..29c60a461 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/SchemeDropDownView.swift @@ -17,18 +17,19 @@ struct SchemeDropDownView: View { @ObservedObject var workspaceSettingsManager: CEWorkspaceSettings var workspaceFileManager: CEWorkspaceFileManager? + var workspaceName: String { + workspaceSettingsManager.settings.project.projectName + } + var body: some View { HStack(spacing: 6) { Image(systemName: "folder.badge.gearshape") .imageScale(.medium) - - Group { - if workspaceSettingsManager.settings.project.projectName.isEmpty { - Text(workspaceFileManager?.workspaceItem.fileName() ?? "No Project found") - } else { - Text(workspaceSettingsManager.settings.project.projectName) - } - } + Text( + workspaceName.isEmpty + ? (workspaceFileManager?.workspaceItem.fileName() ?? "No Project found") + : workspaceName + ) } .font(.subheadline) .padding(.trailing, 11.5) @@ -58,7 +59,6 @@ struct SchemeDropDownView: View { workspaceFileManager: workspaceFileManager, item: workspaceFileManager?.workspaceItem ) - Divider() .padding(.vertical, 5) Group { @@ -73,8 +73,9 @@ struct SchemeDropDownView: View { } } } - .padding(5) .font(.subheadline) + .padding(5) + .frame(minWidth: 215) } .onTapGesture { self.isSchemePopOverPresented.toggle() diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift index 26f09372e..98ca65f11 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift @@ -20,10 +20,10 @@ struct TaskDropDownView: View { Group { if let selectedTask = taskManager.selectedTask { if let selectedActiveTask = taskManager.activeTasks[selectedTask.id] { - TaskView(activeTask: selectedActiveTask) + ActiveTaskView(activeTask: selectedActiveTask) .fixedSize() } else { - DefaultTaskView(task: selectedTask) + TaskView(task: selectedTask, status: CETaskStatus.notRunning) .fixedSize() } } else { @@ -71,45 +71,21 @@ struct TaskDropDownView: View { VStack(alignment: .leading, spacing: 0) { if !taskManager.availableTasks.isEmpty { ForEach(taskManager.availableTasks, id: \.id) { task in - TasksPopoverView(taskManager: taskManager, task: task) - .frame(maxWidth: .infinity) + TasksPopoverMenuItem(taskManager: taskManager, task: task) } Divider() .padding(.vertical, 5) } - - Group { - OptionMenuItemView(label: "Add Task...") { - NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) - } - OptionMenuItemView(label: "Manage Tasks...") { - NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) - } + OptionMenuItemView(label: "Add Task...") { + NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) + } + OptionMenuItemView(label: "Manage Tasks...") { + NSApp.sendAction(#selector(CodeEditWindowController.openWorkspaceSettings(_:)), to: nil, from: nil) } } .font(.subheadline) .padding(5) - .frame(width: 215) - } - - private struct DefaultTaskView: View { - @ObservedObject var task: CETask - var body: some View { - HStack(spacing: 5) { - Image(systemName: "gearshape") - .imageScale(.medium) - Text(task.name) - Spacer(minLength: 0) - } - .padding(.trailing, 7.5) - .overlay(alignment: .trailing) { - Circle() - .fill(CETaskStatus.notRunning.color) - .frame(width: 5, height: 5) - .padding(.trailing, 2.5) - } - .font(.subheadline) - } + .frame(minWidth: 215) } } @@ -117,31 +93,44 @@ struct TaskDropDownView: View { // 1. Active tasks are nested inside TaskManager. // 2. Reference types (like objects) do not notify observers when their internal state changes. /// `TaskView` represents a single active task and observes its state. -/// - Parameter activeTask: The active task to be displayed and observed. -/// Set to `true` for a compact display, used for the current task in the activity viewer. -/// Set to `false`, used in the popover. +/// - Parameter task: The task to be displayed and observed. +/// - Parameter status: The status of the task to be displayed. struct TaskView: View { - @ObservedObject var activeTask: CEActiveTask + @ObservedObject var task: CETask + var status: CETaskStatus var body: some View { HStack(spacing: 5) { +// Label(task.name, systemImage: "gearshape") +// .labelStyle(.titleAndIcon) Image(systemName: "gearshape") - .imageScale(.medium) - Text(activeTask.task.name) + Text(task.name) Spacer(minLength: 0) } .padding(.trailing, 7.5) .overlay(alignment: .trailing) { Circle() - .fill(activeTask.status.color) + .fill(status.color) .frame(width: 5, height: 5) .padding(.trailing, 2.5) } - .font(.subheadline) } } -struct TasksPopoverView: View { +// We need to observe each active task individually because: +// 1. Active tasks are nested inside TaskManager. +// 2. Reference types (like objects) do not notify observers when their internal state changes. +/// `ActiveTaskView` represents a single active task and observes its state. +/// - Parameter activeTask: The active task to be displayed and observed. +struct ActiveTaskView: View { + @ObservedObject var activeTask: CEActiveTask + + var body: some View { + TaskView(task: activeTask.task, status: activeTask.status) + } +} + +struct TasksPopoverMenuItem: View { @Environment(\.dismiss) private var dismiss @@ -180,27 +169,10 @@ struct TasksPopoverView: View { private var popoverContent: some View { Group { if let activeTask = taskManager.activeTasks[task.id] { - TaskView(activeTask: activeTask) + ActiveTaskView(activeTask: activeTask) } else { - defaultTaskView + TaskView(task: task, status: taskManager.taskStatus(taskID: task.id)) } } } - - private var defaultTaskView: some View { - HStack(spacing: 5) { - Image(systemName: "gearshape") - .imageScale(.medium) - Text(task.name) - Spacer(minLength: 0) - } - .padding(.trailing, 7.5) - .overlay(alignment: .trailing) { - Circle() - .fill(taskManager.taskStatus(taskID: task.id).color) - .frame(width: 5, height: 5) - .padding(.trailing, 2.5) - } - .font(.subheadline) - } } diff --git a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift index a66ff5522..0f9f21f2a 100644 --- a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift +++ b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift @@ -18,29 +18,22 @@ struct AddCETaskView: View { self._newTask = StateObject(wrappedValue: CETask(target: "My Mac", workingDirectory: workingDirectory)) } var body: some View { - // TODO: Discuss if this is needed - NavigationStack { - VStack { - CETaskFormView( - task: newTask - ).padding(.top) - + VStack(spacing: 0) { + CETaskFormView(task: newTask) + Divider() + HStack { + Button("Cancel") { + dismiss() + } Spacer() - Divider() - HStack { - Button("Cancel") { - dismiss() - } - Spacer() - Button("Save") { - workspaceSettingsManager.settings.tasks.append(newTask) - try? workspaceSettingsManager.savePreferences() - dismiss() - } - .disabled(newTask.isInvalid) + Button("Save") { + workspaceSettingsManager.settings.tasks.append(newTask) + try? workspaceSettingsManager.savePreferences() + dismiss() } - .padding() - }.navigationTitle("Add Task") + .disabled(newTask.isInvalid) + } + .padding() } } diff --git a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CETaskFormView.swift b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CETaskFormView.swift index e22a2b4a3..ea4159450 100644 --- a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CETaskFormView.swift +++ b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CETaskFormView.swift @@ -14,7 +14,7 @@ struct CETaskFormView: View { @StateObject var settingsViewModel = SettingsViewModel() var body: some View { - SettingsForm { + Form { Section { TextField(text: $task.name) { Text("Name") @@ -80,7 +80,9 @@ struct CETaskFormView: View { } header: { Text("Environment Variables") } - }.environmentObject(settingsViewModel) + } + .formStyle(.grouped) + .environmentObject(settingsViewModel) } func removeSelectedEnv() { diff --git a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift index 49cf6a669..939349f35 100644 --- a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift @@ -19,7 +19,7 @@ struct CEWorkspaceSettingsView: View { let window: NSWindow? var body: some View { VStack(spacing: 0) { - SettingsForm { + Form { Section { TextField( "Name", @@ -49,9 +49,7 @@ struct CEWorkspaceSettingsView: View { } } } - .scrollDisabled(true) - - Spacer() + .formStyle(.grouped) Divider() HStack { Spacer() diff --git a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift index c4264bae9..82ba8294e 100644 --- a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift +++ b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift @@ -18,12 +18,8 @@ struct EditCETaskView: View { let selectedTaskIndex: Int var body: some View { - VStack { - Text("Edit Task") + VStack(spacing: 0) { CETaskFormView(task: task) - .padding(.top) - - Spacer() Divider() HStack { Button("Delete") { From 766bb81613e3fe0652e0a706cc505feb9a69d058 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Thu, 1 Aug 2024 15:15:34 -0500 Subject: [PATCH 05/15] Displaying workspace settings in a sheet instead of a window. Added minimum width to workspace settings bottons. --- .../Views/AddCETaskView.swift | 11 +++++++-- .../Views/CEWorkspaceSettingsView.swift | 18 +++++++++++---- .../Views/EditCETaskView.swift | 12 ++++++++-- .../CodeEditWindowControllerExtensions.swift | 23 ++++++++++--------- 4 files changed, 44 insertions(+), 20 deletions(-) diff --git a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift index 0f9f21f2a..577c5b3e9 100644 --- a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift +++ b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/AddCETaskView.swift @@ -22,15 +22,22 @@ struct AddCETaskView: View { CETaskFormView(task: newTask) Divider() HStack { - Button("Cancel") { + Button { dismiss() + } label: { + Text("Cancel") + .frame(minWidth: 56) } Spacer() - Button("Save") { + Button { workspaceSettingsManager.settings.tasks.append(newTask) try? workspaceSettingsManager.savePreferences() dismiss() + } label: { + Text("Save") + .frame(minWidth: 56) } + .buttonStyle(.borderedProminent) .disabled(newTask.isInvalid) } .padding() diff --git a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift index 939349f35..70772e47d 100644 --- a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift +++ b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/CEWorkspaceSettingsView.swift @@ -8,6 +8,8 @@ import SwiftUI struct CEWorkspaceSettingsView: View { + var dismiss: () -> Void + @EnvironmentObject var workspaceSettingsManager: CEWorkspaceSettings @EnvironmentObject var workspace: WorkspaceDocument @@ -16,7 +18,6 @@ struct CEWorkspaceSettingsView: View { @State var selectedTaskID: UUID? @State var showAddTaskSheet: Bool = false - let window: NSWindow? var body: some View { VStack(spacing: 0) { Form { @@ -50,13 +51,20 @@ struct CEWorkspaceSettingsView: View { } } .formStyle(.grouped) + .scrollContentBackground(.hidden) + Divider() HStack { Spacer() - Button("Done") { - window?.close() + Button { + dismiss() + } label: { + Text("Done") + .frame(minWidth: 56) } - }.padding() + .buttonStyle(.borderedProminent) + } + .padding() } .environmentObject(settingsViewModel) .sheet(isPresented: $showAddTaskSheet) { @@ -75,5 +83,5 @@ struct CEWorkspaceSettingsView: View { } #Preview { - CEWorkspaceSettingsView(window: nil) + CEWorkspaceSettingsView(dismiss: { print("Dismiss") }) } diff --git a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift index 82ba8294e..179c92c1d 100644 --- a/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift +++ b/CodeEdit/Features/CEWorkSpaceSettings/CEWorkspaceSettings/Views/EditCETaskView.swift @@ -22,21 +22,29 @@ struct EditCETaskView: View { CETaskFormView(task: task) Divider() HStack { - Button("Delete") { + Button(role: .destructive) { workspaceSettingsManager.settings.tasks.removeAll(where: { $0.id == task.id }) try? workspaceSettingsManager.savePreferences() taskManger.deleteTask(taskID: task.id) self.dismiss() + } label: { + Text("Delete") + .foregroundStyle(.red) + .frame(minWidth: 56) } Spacer() - Button("Done") { + Button { try? workspaceSettingsManager.savePreferences() self.dismiss() + } label: { + Text("Done") + .frame(minWidth: 56) } + .buttonStyle(.borderedProminent) .disabled(task.isInvalid) } .padding() diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift index 1c47aca56..4490bfc0c 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift @@ -112,17 +112,18 @@ extension CodeEditWindowController { } else { let settingsWindow = NSWindow() self.workspaceSettingsWindow = settingsWindow - let contentView = CEWorkspaceSettingsView(window: settingsWindow) - .environmentObject(workspaceSettingsManager) - .environmentObject(workspace) - .environmentObject(taskManager) - - settingsWindow.contentView = NSHostingView(rootView: contentView) - settingsWindow.titlebarAppearsTransparent = true - settingsWindow.setContentSize(NSSize(width: 515, height: 515)) - settingsWindow.makeKeyAndOrderFront(self) - - window.addCenteredChildWindow(settingsWindow, over: window) + let contentView = CEWorkspaceSettingsView( + dismiss: { self.window?.endSheet(settingsWindow) } + ) + .environmentObject(workspaceSettingsManager) + .environmentObject(workspace) + .environmentObject(taskManager) + + settingsWindow.contentView = NSHostingView(rootView: contentView) + settingsWindow.titlebarAppearsTransparent = true + settingsWindow.setContentSize(NSSize(width: 515, height: 515)) + + window.beginSheet(settingsWindow, completionHandler: nil) } } } From ddedb93305240e842606cf18c6911c32836c374e Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Thu, 1 Aug 2024 18:28:29 -0500 Subject: [PATCH 06/15] Improved the activity viewer notification details popover design --- CodeEdit.xcodeproj/project.pbxproj | 8 +- ...iew.swift => CECircularProgressView.swift} | 13 ++- .../Notificaitons/TaskNotificationView.swift | 17 +--- .../TaskNotificationsDetailView.swift | 96 ++++--------------- 4 files changed, 33 insertions(+), 101 deletions(-) rename CodeEdit/Features/ActivityViewer/Notificaitons/{CustomLoadingRingView.swift => CECircularProgressView.swift} (87%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 170b81a08..31b87ff7b 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -316,7 +316,7 @@ 617DB3D02C25AFAE00B58BFE /* TaskNotificationHandler.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3CF2C25AFAE00B58BFE /* TaskNotificationHandler.swift */; }; 617DB3D32C25AFEA00B58BFE /* TaskNotificationModel.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3D22C25AFEA00B58BFE /* TaskNotificationModel.swift */; }; 617DB3D62C25B02D00B58BFE /* TaskNotificationView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3D52C25B02D00B58BFE /* TaskNotificationView.swift */; }; - 617DB3D82C25B04D00B58BFE /* CustomLoadingRingView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3D72C25B04D00B58BFE /* CustomLoadingRingView.swift */; }; + 617DB3D82C25B04D00B58BFE /* CECircularProgressView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3D72C25B04D00B58BFE /* CECircularProgressView.swift */; }; 617DB3DA2C25B07F00B58BFE /* TaskNotificationsDetailView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3D92C25B07F00B58BFE /* TaskNotificationsDetailView.swift */; }; 617DB3DC2C25B14A00B58BFE /* ActivityViewer.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3DB2C25B14A00B58BFE /* ActivityViewer.swift */; }; 617DB3DF2C25E13800B58BFE /* TaskNotificationHandlerTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 617DB3DE2C25E13800B58BFE /* TaskNotificationHandlerTests.swift */; }; @@ -952,7 +952,7 @@ 617DB3CF2C25AFAE00B58BFE /* TaskNotificationHandler.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationHandler.swift; sourceTree = ""; }; 617DB3D22C25AFEA00B58BFE /* TaskNotificationModel.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationModel.swift; sourceTree = ""; }; 617DB3D52C25B02D00B58BFE /* TaskNotificationView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationView.swift; sourceTree = ""; }; - 617DB3D72C25B04D00B58BFE /* CustomLoadingRingView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CustomLoadingRingView.swift; sourceTree = ""; }; + 617DB3D72C25B04D00B58BFE /* CECircularProgressView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CECircularProgressView.swift; sourceTree = ""; }; 617DB3D92C25B07F00B58BFE /* TaskNotificationsDetailView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationsDetailView.swift; sourceTree = ""; }; 617DB3DB2C25B14A00B58BFE /* ActivityViewer.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActivityViewer.swift; sourceTree = ""; }; 617DB3DE2C25E13800B58BFE /* TaskNotificationHandlerTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskNotificationHandlerTests.swift; sourceTree = ""; }; @@ -2597,7 +2597,7 @@ children = ( 617DB3D52C25B02D00B58BFE /* TaskNotificationView.swift */, 617DB3D92C25B07F00B58BFE /* TaskNotificationsDetailView.swift */, - 617DB3D72C25B04D00B58BFE /* CustomLoadingRingView.swift */, + 617DB3D72C25B04D00B58BFE /* CECircularProgressView.swift */, 617DB3CF2C25AFAE00B58BFE /* TaskNotificationHandler.swift */, ); path = Notificaitons; @@ -3853,7 +3853,7 @@ B6C4F2AC2B3CC4D000B2B140 /* CommitChangedFileListItemView.swift in Sources */, 61A3E3E12C331B4A00076BD3 /* AddCETaskView.swift in Sources */, 6C82D6B329BFD88700495C54 /* NavigateCommands.swift in Sources */, - 617DB3D82C25B04D00B58BFE /* CustomLoadingRingView.swift in Sources */, + 617DB3D82C25B04D00B58BFE /* CECircularProgressView.swift in Sources */, B6CFD8112C20A8EE00E63F1A /* NSFont+WithWeight.swift in Sources */, B66A4E4C29C9179B004573B4 /* CodeEditApp.swift in Sources */, 661EF7B82BEE215300C3E577 /* ImageFileView.swift in Sources */, diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/CustomLoadingRingView.swift b/CodeEdit/Features/ActivityViewer/Notificaitons/CECircularProgressView.swift similarity index 87% rename from CodeEdit/Features/ActivityViewer/Notificaitons/CustomLoadingRingView.swift rename to CodeEdit/Features/ActivityViewer/Notificaitons/CECircularProgressView.swift index 81e8bd5ae..df8289a83 100644 --- a/CodeEdit/Features/ActivityViewer/Notificaitons/CustomLoadingRingView.swift +++ b/CodeEdit/Features/ActivityViewer/Notificaitons/CECircularProgressView.swift @@ -1,5 +1,5 @@ // -// CustomLoadingRingView.swift +// CECircularProgressView.swift // CodeEdit // // Created by Tommy Ludwig on 21.06.24. @@ -7,13 +7,15 @@ import SwiftUI -struct CustomLoadingRingView: View { +struct CECircularProgressView: View { @State private var isAnimating = false @State private var previousValue: Bool = false + var progress: Double? - var currentTaskCount: Int + var currentTaskCount: Int = 1 let lineWidth: CGFloat = 2 + var body: some View { Circle() .stroke(style: StrokeStyle(lineWidth: lineWidth)) @@ -41,6 +43,7 @@ struct CustomLoadingRingView: View { } } .rotationEffect(.degrees(-90)) + .padding(lineWidth/2) .overlay { if currentTaskCount > 1 { Text("\(currentTaskCount)") @@ -52,10 +55,10 @@ struct CustomLoadingRingView: View { #Preview { Group { - CustomLoadingRingView(currentTaskCount: 1) + CECircularProgressView(currentTaskCount: 1) .frame(width: 22, height: 22) - CustomLoadingRingView(progress: 0.65, currentTaskCount: 1) + CECircularProgressView(progress: 0.65, currentTaskCount: 1) .frame(width: 22, height: 22) } .padding() diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift index f486791f6..bec539032 100644 --- a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift +++ b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift @@ -9,7 +9,6 @@ import SwiftUI struct TaskNotificationView: View { @ObservedObject var taskNotificationHandler: TaskNotificationHandler - @State private var hovered: Bool = false @State private var isPresented: Bool = false var body: some View { @@ -19,12 +18,12 @@ struct TaskNotificationView: View { .font(.subheadline) if notification.isLoading { - CustomLoadingRingView( + CECircularProgressView( progress: notification.percentage, currentTaskCount: taskNotificationHandler.notifications.count ) .padding(.horizontal, -1) - .frame(height: 14) + .frame(height: 16) } else { if taskNotificationHandler.notifications.count > 1 { Text("\(taskNotificationHandler.notifications.count)") @@ -41,19 +40,9 @@ struct TaskNotificationView: View { } .animation(.easeInOut, value: notification) .padding(3) - .background { - if hovered { - RoundedRectangle(cornerRadius: 5) - .tint(.gray) - .foregroundStyle(.ultraThickMaterial) - } - } - .onHover { isHovered in - self.hovered = isHovered - } .padding(-3) .padding(.trailing, 3) - .popover(isPresented: $isPresented) { + .popover(isPresented: $isPresented, arrowEdge: .bottom) { TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler) }.onTapGesture { self.isPresented.toggle() diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationsDetailView.swift b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationsDetailView.swift index 9f11d20d4..4742fa817 100644 --- a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationsDetailView.swift +++ b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationsDetailView.swift @@ -12,89 +12,29 @@ struct TaskNotificationsDetailView: View { @State private var selectedTaskNotificationIndex: Int = 0 var body: some View { ScrollView { - VStack(alignment: .leading) { - if let selected = - taskNotificationHandler - .notifications[safe: selectedTaskNotificationIndex] { - Text(selected.title) - .font(.headline) - - Text(selected.id) - .foregroundStyle(.secondary) - .font(.system(size: 8)) - - Divider() - .padding(.vertical, 5) - - if let message = selected.message, !message.isEmpty { - Text(message) - .fixedSize(horizontal: false, vertical: true) - .transition(.identity) - } else { - Text("No Details") - } - - if selected.isLoading { - if let percentage = selected.percentage { - ProgressView(value: percentage) { - // Text("Progress") - } currentValueLabel: { - Text("\(String(format: "%.0f", percentage * 100))%") - }.padding(.top, 5) - } else { - ProgressView() - .progressViewStyle(.linear) - .padding(.top, 5) - } - } - - Spacer() - Divider() - - HStack { - Button(action: { - withAnimation { - selectedTaskNotificationIndex -= 1 + VStack(alignment: .leading, spacing: 15) { + ForEach(taskNotificationHandler.notifications, id: \.id) { notification in + HStack(alignment: .center, spacing: 8) { + CECircularProgressView(progress: notification.percentage) + .frame(width: 16, height: 16) + VStack(alignment: .leading) { + Text(notification.title) + .fixedSize(horizontal: false, vertical: true) + .transition(.identity) + + if let message = notification.message, !message.isEmpty { + Text(message) + .font(.subheadline) + .foregroundStyle(.secondary) } - }, label: { - Image(systemName: "chevron.left") - }) - .disabled( - selectedTaskNotificationIndex - 1 < 0 - ) - - Spacer() - - Text("\(selectedTaskNotificationIndex + 1)") - + } Spacer() - - Button(action: { - withAnimation { - selectedTaskNotificationIndex += 1 - } - }, label: { - Image(systemName: "chevron.right") - }) - .disabled( - selectedTaskNotificationIndex + 1 == taskNotificationHandler.notifications.count - ) - }.animation(.spring, value: selected) - } else { - Text("Task done") - .font(.headline) - - Divider() - .padding(.vertical, 5) - - Text("The task has been deleted and is no longer available.") - .fixedSize(horizontal: false, vertical: true) - .transition(.identity) + } } } } - .padding(5) - .frame(width: 220) + .padding(15) + .frame(minWidth: 320) .onChange(of: taskNotificationHandler.notifications) { newValue in if selectedTaskNotificationIndex >= newValue.count { selectedTaskNotificationIndex = 0 From ad43937a822d4e94cc9b3b6834f2a602603227a1 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Fri, 2 Aug 2024 14:41:15 -0500 Subject: [PATCH 07/15] Conditional display and animation of stop toolbar button --- .../CodeEditWindowController+Toolbar.swift | 49 +++++++++++++------ CodeEdit/Features/Tasks/CEActiveTask.swift | 40 ++++++++++----- CodeEdit/Features/Tasks/TaskManager.swift | 27 ++++++++++ 3 files changed, 89 insertions(+), 27 deletions(-) diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index 0aa053823..c3e58710d 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -112,28 +112,22 @@ extension CodeEditWindowController { return toolbarItem case .stopTaskSidebarItem: let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.stopTaskSidebarItem) + + guard let taskManager = workspace?.taskManager + else { return nil } + toolbarItem.label = "Stop" toolbarItem.paletteLabel = "Stop" toolbarItem.toolTip = "Stop selected task" toolbarItem.isBordered = true toolbarItem.target = self - toolbarItem.action = #selector(self.terminateActiveTask) toolbarItem.image = NSImage( systemSymbolName: "stop.fill", accessibilityDescription: nil )?.withSymbolConfiguration(.init(pointSize: 15, weight: .regular)) let view = NSHostingView( - rootView: Button { - self.terminateActiveTask() - } label: { - Label("Stop", systemImage: "stop.fill") - .labelStyle(.iconOnly) - .font(.system(size: 15, weight: .regular)) - .help("Stop selected task") - .frame(width: 28) - .offset(CGSize(width: 0, height: 1.5)) - } + rootView: StopToolbarButton(taskManager: taskManager) ) toolbarItem.view = view @@ -215,10 +209,35 @@ extension CodeEditWindowController { guard let taskManager = workspace?.taskManager else { return } taskManager.executeActiveTask() } +} - @objc - private func terminateActiveTask() { - guard let taskManager = workspace?.taskManager else { return } - taskManager.terminateActiveTask() +struct StopToolbarButton: View { + // TODO: try to get this from the environment + @ObservedObject var taskManager: TaskManager + + var body: some View { + HStack { + if let selectedTaskID = taskManager.selectedTask?.id, + let activeTask = taskManager.activeTasks[selectedTaskID], + activeTask.status == .running { + Button { + taskManager.terminateActiveTask() + } label: { + Label("Stop", systemImage: "stop.fill") + .labelStyle(.iconOnly) + .font(.system(size: 15, weight: .regular)) + .help("Stop selected task") + .frame(width: 28) + .offset(y: 1.5) + } + .frame(height: 22) + .transition(.opacity.combined(with: .move(edge: .trailing))) + } + } + .frame(width: 38, height: 22) + .animation( + .easeInOut(duration: 0.3), + value: taskManager.activeTasks[taskManager.selectedTask?.id ?? UUID()]?.status + ) } } diff --git a/CodeEdit/Features/Tasks/CEActiveTask.swift b/CodeEdit/Features/Tasks/CEActiveTask.swift index 1d1273369..51be79c0e 100644 --- a/CodeEdit/Features/Tasks/CEActiveTask.swift +++ b/CodeEdit/Features/Tasks/CEActiveTask.swift @@ -14,7 +14,11 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { @Published private(set) var output: String = "" /// The status of the task. - @Published private(set) var status: CETaskStatus = .notRunning + @Published private(set) var status: CETaskStatus = .notRunning { + didSet { + statusSubject.send(status) + } + } /// The name of the associated task. @ObservedObject var task: CETask @@ -23,11 +27,15 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { var outputPipe: Pipe? private var cancellables = Set() + private let statusSubject = PassthroughSubject() + var statusPublisher: AnyPublisher { + statusSubject.eraseToAnyPublisher() + } init(task: CETask) { self.task = task - self.process = Process() - self.outputPipe = Pipe() + self.process = nil + self.outputPipe = nil self.task.objectWillChange.sink { _ in self.objectWillChange.send() @@ -35,6 +43,8 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { } func run() { + resetProcess() + Task { guard let process, let outputPipe else { return } @@ -63,7 +73,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { process: process, command: self.task.fullCommand, environmentVariables: self.task.environmentVariablesDictionary, - shell: Shell.zsh, // TODO: Let user decide which shell he uses + shell: Shell.zsh, // TODO: Let user decide which shell to use outputPipe: outputPipe ) } catch { print(error) } @@ -103,14 +113,8 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { } func renew() { - if let process { - if process.isRunning { - process.terminate() - process.waitUntilExit() - } - self.process = Process() - outputPipe = Pipe() - } + terminateIfRunning() + resetProcess() } func suspend() { @@ -137,6 +141,18 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { } } + private func terminateIfRunning() { + if let process, process.isRunning { + process.terminate() + process.waitUntilExit() + } + } + + private func resetProcess() { + self.process = Process() + self.outputPipe = Pipe() + } + private func createStatusTaskNotification() { let userInfo: [String: Any] = [ "id": self.task.id.uuidString, diff --git a/CodeEdit/Features/Tasks/TaskManager.swift b/CodeEdit/Features/Tasks/TaskManager.swift index 935099c23..96a720b77 100644 --- a/CodeEdit/Features/Tasks/TaskManager.swift +++ b/CodeEdit/Features/Tasks/TaskManager.swift @@ -16,6 +16,7 @@ class TaskManager: ObservableObject { @ObservedObject var workspaceSettings: CEWorkspaceSettingsData private var settingsListener: AnyCancellable? + private var taskStatusListener: AnyCancellable? init(workspaceSettings: CEWorkspaceSettingsData) { self.workspaceSettings = workspaceSettings @@ -25,6 +26,22 @@ class TaskManager: ObservableObject { .sink { [weak self] _ in self?.updateSelectedTaskID() } + + taskStatusListener = $selectedTaskID + .receive(on: DispatchQueue.main) + .sink { [weak self] selectedTaskID in + guard let self = self, let taskID = selectedTaskID else { return } + if let activeTask = self.activeTasks[taskID] { + activeTask.statusPublisher + .receive(on: DispatchQueue.main) + .sink { status in + if status == .notRunning { + self.selectedTaskID = nil + } + } + .store(in: &self.cancellables) + } + } } var selectedTask: CETask? { @@ -43,6 +60,13 @@ class TaskManager: ObservableObject { return nil } + var selectedActiveTask: CEActiveTask? { + if let selectedTaskID { + return activeTasks[selectedTaskID] + } + return nil + } + var availableTasks: [CETask] { return workspaceSettings.tasks } @@ -134,6 +158,7 @@ class TaskManager: ObservableObject { return } process.terminate() + selectedTaskID = nil } /// Interrupts the task associated with the given task ID. @@ -164,4 +189,6 @@ class TaskManager: ObservableObject { terminateTask(taskID: taskID) activeTasks.removeValue(forKey: taskID) } + + private var cancellables = Set() } From e3eaae9c389eca135af7ef6b6e9bb6aec0d3824e Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Fri, 2 Aug 2024 17:28:46 -0500 Subject: [PATCH 08/15] Added activity viewer notification animations. --- .../ActivityViewer/ActivityViewer.swift | 1 + .../Notificaitons/TaskNotificationView.swift | 72 +++++++++++-------- .../Tasks/TaskDropDownView.swift | 3 - .../CodeEditWindowController+Toolbar.swift | 3 + CodeEdit/Features/Tasks/CEActiveTask.swift | 28 +++----- CodeEdit/Features/Tasks/TaskManager.swift | 24 ------- 6 files changed, 56 insertions(+), 75 deletions(-) diff --git a/CodeEdit/Features/ActivityViewer/ActivityViewer.swift b/CodeEdit/Features/ActivityViewer/ActivityViewer.swift index 1314f565e..ecac0f94a 100644 --- a/CodeEdit/Features/ActivityViewer/ActivityViewer.swift +++ b/CodeEdit/Features/ActivityViewer/ActivityViewer.swift @@ -49,6 +49,7 @@ struct ActivityViewer: View { .padding(.horizontal, 5) .padding(.vertical, 1.5) .frame(height: 22) + .clipped() .background { if colorScheme == .dark { RoundedRectangle(cornerRadius: 5) diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift index bec539032..c1d00561e 100644 --- a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift +++ b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift @@ -10,45 +10,57 @@ import SwiftUI struct TaskNotificationView: View { @ObservedObject var taskNotificationHandler: TaskNotificationHandler @State private var isPresented: Bool = false + @State var notification: TaskNotificationModel? var body: some View { - if let notification = taskNotificationHandler.notifications.first { - HStack { - Text(notification.title) - .font(.subheadline) + ZStack { + if let notification { + HStack { + Text(notification.title) + .font(.subheadline) + .transition(.asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)).combined(with: .opacity)) + .id("NotificationTitle" + notification.title) - if notification.isLoading { - CECircularProgressView( - progress: notification.percentage, - currentTaskCount: taskNotificationHandler.notifications.count - ) - .padding(.horizontal, -1) - .frame(height: 16) - } else { - if taskNotificationHandler.notifications.count > 1 { - Text("\(taskNotificationHandler.notifications.count)") - .font(.caption) - .padding(5) - .background( - Circle() - .foregroundStyle(.gray) - .opacity(0.2) - ) - .padding(-5) + if notification.isLoading { + CECircularProgressView( + progress: notification.percentage, + currentTaskCount: taskNotificationHandler.notifications.count + ) + .padding(.horizontal, -1) + .frame(height: 16) + } else { + if taskNotificationHandler.notifications.count > 1 { + Text("\(taskNotificationHandler.notifications.count)") + .font(.caption) + .padding(5) + .background( + Circle() + .foregroundStyle(.gray) + .opacity(0.2) + ) + .padding(-5) + } } } + .transition(.opacity.combined(with: .move(edge: .trailing))) + .padding(3) + .padding(-3) + .padding(.trailing, 3) + .popover(isPresented: $isPresented, arrowEdge: .bottom) { + TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler) + }.onTapGesture { + self.isPresented.toggle() + } } - .animation(.easeInOut, value: notification) - .padding(3) - .padding(-3) - .padding(.trailing, 3) - .popover(isPresented: $isPresented, arrowEdge: .bottom) { - TaskNotificationsDetailView(taskNotificationHandler: taskNotificationHandler) - }.onTapGesture { - self.isPresented.toggle() + } + .animation(.easeInOut, value: notification) + .onChange(of: taskNotificationHandler.notifications) { newValue in + withAnimation { + notification = newValue.first } } } + } #Preview { diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift index 98ca65f11..6d3ea3f93 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift @@ -89,9 +89,6 @@ struct TaskDropDownView: View { } } -// We need to observe each active task individually because: -// 1. Active tasks are nested inside TaskManager. -// 2. Reference types (like objects) do not notify observers when their internal state changes. /// `TaskView` represents a single active task and observes its state. /// - Parameter task: The task to be displayed and observed. /// - Parameter status: The status of the task to be displayed. diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index c3e58710d..1c26077d8 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -239,5 +239,8 @@ struct StopToolbarButton: View { .easeInOut(duration: 0.3), value: taskManager.activeTasks[taskManager.selectedTask?.id ?? UUID()]?.status ) + .onChange(of: taskManager.activeTasks[taskManager.selectedTask?.id ?? UUID()]?.status) { newValue in + print(newValue) + } } } diff --git a/CodeEdit/Features/Tasks/CEActiveTask.swift b/CodeEdit/Features/Tasks/CEActiveTask.swift index 51be79c0e..fc2997dca 100644 --- a/CodeEdit/Features/Tasks/CEActiveTask.swift +++ b/CodeEdit/Features/Tasks/CEActiveTask.swift @@ -34,8 +34,8 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { init(task: CETask) { self.task = task - self.process = nil - self.outputPipe = nil + self.process = Process() + self.outputPipe = Pipe() self.task.objectWillChange.sink { _ in self.objectWillChange.send() @@ -43,8 +43,6 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { } func run() { - resetProcess() - Task { guard let process, let outputPipe else { return } @@ -113,8 +111,14 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { } func renew() { - terminateIfRunning() - resetProcess() + if let process { + if process.isRunning { + process.terminate() + process.waitUntilExit() + } + self.process = Process() + outputPipe = Pipe() + } } func suspend() { @@ -141,18 +145,6 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { } } - private func terminateIfRunning() { - if let process, process.isRunning { - process.terminate() - process.waitUntilExit() - } - } - - private func resetProcess() { - self.process = Process() - self.outputPipe = Pipe() - } - private func createStatusTaskNotification() { let userInfo: [String: Any] = [ "id": self.task.id.uuidString, diff --git a/CodeEdit/Features/Tasks/TaskManager.swift b/CodeEdit/Features/Tasks/TaskManager.swift index 96a720b77..37069f71c 100644 --- a/CodeEdit/Features/Tasks/TaskManager.swift +++ b/CodeEdit/Features/Tasks/TaskManager.swift @@ -26,22 +26,6 @@ class TaskManager: ObservableObject { .sink { [weak self] _ in self?.updateSelectedTaskID() } - - taskStatusListener = $selectedTaskID - .receive(on: DispatchQueue.main) - .sink { [weak self] selectedTaskID in - guard let self = self, let taskID = selectedTaskID else { return } - if let activeTask = self.activeTasks[taskID] { - activeTask.statusPublisher - .receive(on: DispatchQueue.main) - .sink { status in - if status == .notRunning { - self.selectedTaskID = nil - } - } - .store(in: &self.cancellables) - } - } } var selectedTask: CETask? { @@ -60,13 +44,6 @@ class TaskManager: ObservableObject { return nil } - var selectedActiveTask: CEActiveTask? { - if let selectedTaskID { - return activeTasks[selectedTaskID] - } - return nil - } - var availableTasks: [CETask] { return workspaceSettings.tasks } @@ -158,7 +135,6 @@ class TaskManager: ObservableObject { return } process.terminate() - selectedTaskID = nil } /// Interrupts the task associated with the given task ID. From 3a2f4f8459359e651617e881d359a017cffe035e Mon Sep 17 00:00:00 2001 From: Khan Winter <35942988+thecoolwinter@users.noreply.github.com> Date: Fri, 2 Aug 2024 22:05:29 -0500 Subject: [PATCH 09/15] Fix Task Renew Race Condition, Stop Button Listeners --- .../Notificaitons/TaskNotificationView.swift | 5 +++- .../CodeEditWindowController+Toolbar.swift | 30 +++++++++++++++---- CodeEdit/Features/Tasks/TaskManager.swift | 19 +++++++----- .../DebugUtility/TaskOutputActionsView.swift | 4 ++- 4 files changed, 43 insertions(+), 15 deletions(-) diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift index c1d00561e..d86df90d9 100644 --- a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift +++ b/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift @@ -18,7 +18,10 @@ struct TaskNotificationView: View { HStack { Text(notification.title) .font(.subheadline) - .transition(.asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)).combined(with: .opacity)) + .transition( + .asymmetric(insertion: .move(edge: .top), removal: .move(edge: .bottom)) + .combined(with: .opacity) + ) .id("NotificationTitle" + notification.title) if notification.isLoading { diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index 1c26077d8..c489267c0 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -7,6 +7,7 @@ import AppKit import SwiftUI +import Combine extension CodeEditWindowController { internal func setupToolbar() { @@ -215,11 +216,15 @@ struct StopToolbarButton: View { // TODO: try to get this from the environment @ObservedObject var taskManager: TaskManager + /// Tracks the current selected task's status. Updated by `updateStatusListener` + @State private var currentSelectedStatus: CETaskStatus? + /// The listener that listens to the active task's status publisher. Is updated frequently as the active task + /// changes. + @State private var statusListener: AnyCancellable? + var body: some View { HStack { - if let selectedTaskID = taskManager.selectedTask?.id, - let activeTask = taskManager.activeTasks[selectedTaskID], - activeTask.status == .running { + if let currentSelectedStatus, currentSelectedStatus == .running { Button { taskManager.terminateActiveTask() } label: { @@ -237,10 +242,23 @@ struct StopToolbarButton: View { .frame(width: 38, height: 22) .animation( .easeInOut(duration: 0.3), - value: taskManager.activeTasks[taskManager.selectedTask?.id ?? UUID()]?.status + value: currentSelectedStatus ) - .onChange(of: taskManager.activeTasks[taskManager.selectedTask?.id ?? UUID()]?.status) { newValue in - print(newValue) + .onChange(of: taskManager.selectedTaskID) { _ in updateStatusListener() } + .onChange(of: taskManager.activeTasks) { _ in updateStatusListener() } + .onAppear(perform: updateStatusListener) + .onDisappear { + statusListener?.cancel() + } + } + + /// Update the ``statusListener`` to listen to a potentially new active task. + private func updateStatusListener() { + statusListener?.cancel() + currentSelectedStatus = taskManager.activeTasks[taskManager.selectedTaskID ?? UUID()]?.status + guard let id = taskManager.selectedTaskID else { return } + statusListener = taskManager.activeTasks[id]?.$status.sink { newValue in + currentSelectedStatus = newValue } } } diff --git a/CodeEdit/Features/Tasks/TaskManager.swift b/CodeEdit/Features/Tasks/TaskManager.swift index 37069f71c..0036c06f6 100644 --- a/CodeEdit/Features/Tasks/TaskManager.swift +++ b/CodeEdit/Features/Tasks/TaskManager.swift @@ -60,22 +60,27 @@ class TaskManager: ObservableObject { func executeActiveTask() { let task = workspaceSettings.tasks.first { $0.id == selectedTaskID } guard let task else { return } - runTask(task: task) + Task { + await runTask(task: task) + } } - func runTask(task: CETask) { + func runTask(task: CETask) async { // A process can only be started once, that means we have to renew the Process and Pipe - // but don't initialise a new object. + // but don't initialize a new object. if let activeTask = activeTasks[task.id] { activeTask.renew() + // Wait until the task is no longer running. + // The termination handler is asynchronous, so we avoid a race condition using this. + while activeTask.status != .notRunning { + await Task.yield() + } activeTask.run() } else { let runningTask = CEActiveTask(task: task) runningTask.run() - Task { - await MainActor.run { - activeTasks[task.id] = runningTask - } + await MainActor.run { + activeTasks[task.id] = runningTask } } } diff --git a/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputActionsView.swift b/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputActionsView.swift index 1e7b4df49..2dbf78d5b 100644 --- a/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputActionsView.swift +++ b/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputActionsView.swift @@ -18,7 +18,9 @@ struct TaskOutputActionsView: View { Spacer() Button { - taskManager.runTask(task: activeTask.task) + Task { + await taskManager.runTask(task: activeTask.task) + } } label: { Image(systemName: "memories") .foregroundStyle(.green) From 1bf5a0cb30ce484afe5a9d4ebd55d327d33f6d69 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sat, 3 Aug 2024 10:38:06 -0500 Subject: [PATCH 10/15] Fixed SwiftLint error and reorganized Task folder structure --- CodeEdit.xcodeproj/project.pbxproj | 24 +++++++- .../CodeEditWindowController+Toolbar.swift | 53 +--------------- .../Tasks/{ => Models}/CEActiveTask.swift | 1 + .../Tasks/{ => Models}/CETaskStatus.swift | 0 .../Tasks/Views/StopToolbarButton.swift | 60 +++++++++++++++++++ 5 files changed, 84 insertions(+), 54 deletions(-) rename CodeEdit/Features/Tasks/{ => Models}/CEActiveTask.swift (99%) rename CodeEdit/Features/Tasks/{ => Models}/CETaskStatus.swift (100%) create mode 100644 CodeEdit/Features/Tasks/Views/StopToolbarButton.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 31b87ff7b..933edf300 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -531,6 +531,7 @@ B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; }; B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */; }; B69BFDC72B0686910050D9A6 /* GitClient+Initiate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */; }; + B69D3EDE2C5E85A2005CF43A /* StopToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */; }; B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */; }; B6AB09A12AAABAAE0003A3A6 /* EditorTabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09A02AAABAAE0003A3A6 /* EditorTabs.swift */; }; B6AB09A32AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */; }; @@ -1161,6 +1162,7 @@ B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = ""; }; B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsAccountLink.swift; sourceTree = ""; }; B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Initiate.swift"; sourceTree = ""; }; + B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopToolbarButton.swift; sourceTree = ""; }; B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSSHKeyView.swift; sourceTree = ""; }; B6AB09A02AAABAAE0003A3A6 /* EditorTabs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabs.swift; sourceTree = ""; }; B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarLeadingAccessories.swift; sourceTree = ""; }; @@ -2640,9 +2642,9 @@ 61D435C52C29684100D032B8 /* Tasks */ = { isa = PBXGroup; children = ( + B69D3EDF2C5E85B8005CF43A /* Models */, + B69D3EDC2C5E856F005CF43A /* Views */, 61D435CB2C29699800D032B8 /* TaskManager.swift */, - 61D435CD2C2969C300D032B8 /* CEActiveTask.swift */, - 61D435D12C2969D800D032B8 /* CETaskStatus.swift */, ); path = Tasks; sourceTree = ""; @@ -3259,6 +3261,23 @@ path = Settings; sourceTree = ""; }; + B69D3EDC2C5E856F005CF43A /* Views */ = { + isa = PBXGroup; + children = ( + B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */, + ); + path = Views; + sourceTree = ""; + }; + B69D3EDF2C5E85B8005CF43A /* Models */ = { + isa = PBXGroup; + children = ( + 61D435CD2C2969C300D032B8 /* CEActiveTask.swift */, + 61D435D12C2969D800D032B8 /* CETaskStatus.swift */, + ); + path = Models; + sourceTree = ""; + }; B6AB09AB2AAACBF70003A3A6 /* Tabs */ = { isa = PBXGroup; children = ( @@ -4044,6 +4063,7 @@ 587B9E9929301D8F00AC7927 /* GitChangedFile.swift in Sources */, 6C147C4B29A32A7B0089B630 /* Environment+SplitEditor.swift in Sources */, 2897E1C72979A29200741E32 /* TrackableScrollView.swift in Sources */, + B69D3EDE2C5E85A2005CF43A /* StopToolbarButton.swift in Sources */, 58F2EB0E292FB2B0004A9BDE /* SoftwareUpdater.swift in Sources */, 587B9E9529301D8F00AC7927 /* BitBucketUser.swift in Sources */, 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */, diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index c489267c0..6d6c25914 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -68,7 +68,7 @@ extension CodeEditWindowController { } } - // swiftlint:disable:next function_body_length + // swiftlint:disable:next function_body_length cyclomatic_complexity func toolbar( _ toolbar: NSToolbar, itemForItemIdentifier itemIdentifier: NSToolbarItem.Identifier, @@ -211,54 +211,3 @@ extension CodeEditWindowController { taskManager.executeActiveTask() } } - -struct StopToolbarButton: View { - // TODO: try to get this from the environment - @ObservedObject var taskManager: TaskManager - - /// Tracks the current selected task's status. Updated by `updateStatusListener` - @State private var currentSelectedStatus: CETaskStatus? - /// The listener that listens to the active task's status publisher. Is updated frequently as the active task - /// changes. - @State private var statusListener: AnyCancellable? - - var body: some View { - HStack { - if let currentSelectedStatus, currentSelectedStatus == .running { - Button { - taskManager.terminateActiveTask() - } label: { - Label("Stop", systemImage: "stop.fill") - .labelStyle(.iconOnly) - .font(.system(size: 15, weight: .regular)) - .help("Stop selected task") - .frame(width: 28) - .offset(y: 1.5) - } - .frame(height: 22) - .transition(.opacity.combined(with: .move(edge: .trailing))) - } - } - .frame(width: 38, height: 22) - .animation( - .easeInOut(duration: 0.3), - value: currentSelectedStatus - ) - .onChange(of: taskManager.selectedTaskID) { _ in updateStatusListener() } - .onChange(of: taskManager.activeTasks) { _ in updateStatusListener() } - .onAppear(perform: updateStatusListener) - .onDisappear { - statusListener?.cancel() - } - } - - /// Update the ``statusListener`` to listen to a potentially new active task. - private func updateStatusListener() { - statusListener?.cancel() - currentSelectedStatus = taskManager.activeTasks[taskManager.selectedTaskID ?? UUID()]?.status - guard let id = taskManager.selectedTaskID else { return } - statusListener = taskManager.activeTasks[id]?.$status.sink { newValue in - currentSelectedStatus = newValue - } - } -} diff --git a/CodeEdit/Features/Tasks/CEActiveTask.swift b/CodeEdit/Features/Tasks/Models/CEActiveTask.swift similarity index 99% rename from CodeEdit/Features/Tasks/CEActiveTask.swift rename to CodeEdit/Features/Tasks/Models/CEActiveTask.swift index fc2997dca..9ef513cee 100644 --- a/CodeEdit/Features/Tasks/CEActiveTask.swift +++ b/CodeEdit/Features/Tasks/Models/CEActiveTask.swift @@ -80,6 +80,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { func handleProcessFinished(terminationStatus: Int32) async { handleTerminationStatus(terminationStatus) + print(terminationStatus) if terminationStatus == 0 { await updateOutput("\nFinished running \(task.name).\n\n") await updateTaskStatus(to: .finished) diff --git a/CodeEdit/Features/Tasks/CETaskStatus.swift b/CodeEdit/Features/Tasks/Models/CETaskStatus.swift similarity index 100% rename from CodeEdit/Features/Tasks/CETaskStatus.swift rename to CodeEdit/Features/Tasks/Models/CETaskStatus.swift diff --git a/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift b/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift new file mode 100644 index 000000000..f6553081e --- /dev/null +++ b/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift @@ -0,0 +1,60 @@ +// +// StopToolbarButton.swift +// CodeEdit +// +// Created by Austin Condiff on 8/3/24. +// + +import SwiftUI +import Combine + +struct StopToolbarButton: View { + // TODO: try to get this from the environment + @ObservedObject var taskManager: TaskManager + + /// Tracks the current selected task's status. Updated by `updateStatusListener` + @State private var currentSelectedStatus: CETaskStatus? + /// The listener that listens to the active task's status publisher. Is updated frequently as the active task + /// changes. + @State private var statusListener: AnyCancellable? + + var body: some View { + HStack { + if let currentSelectedStatus, currentSelectedStatus == .running { + Button { + taskManager.terminateActiveTask() + } label: { + Label("Stop", systemImage: "stop.fill") + .labelStyle(.iconOnly) + .font(.system(size: 15, weight: .regular)) + .help("Stop selected task") + .frame(width: 28) + .offset(y: 1.5) + } + .frame(height: 22) + .transition(.opacity.combined(with: .move(edge: .trailing))) + } + } + .frame(width: 38, height: 22) + .animation( + .easeInOut(duration: 0.3), + value: currentSelectedStatus + ) + .onChange(of: taskManager.selectedTaskID) { _ in updateStatusListener() } + .onChange(of: taskManager.activeTasks) { _ in updateStatusListener() } + .onAppear(perform: updateStatusListener) + .onDisappear { + statusListener?.cancel() + } + } + + /// Update the ``statusListener`` to listen to a potentially new active task. + private func updateStatusListener() { + statusListener?.cancel() + currentSelectedStatus = taskManager.activeTasks[taskManager.selectedTaskID ?? UUID()]?.status + guard let id = taskManager.selectedTaskID else { return } + statusListener = taskManager.activeTasks[id]?.$status.sink { newValue in + currentSelectedStatus = newValue + } + } +} From 0ee0759cc769e699dbecf1f8da9ae9f2a7c4a0a1 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 4 Aug 2024 01:50:57 -0500 Subject: [PATCH 11/15] Resolved PR issues. When running a task, the task output appears in the utility area and drawer opens if closed. --- CodeEdit.xcodeproj/project.pbxproj | 22 +++-- .../CECircularProgressView.swift | 0 .../TaskNotificationHandler.swift | 0 .../TaskNotificationView.swift | 0 .../TaskNotificationsDetailView.swift | 0 .../ActivityViewer/Tasks/ActiveTaskView.swift | 21 +++++ .../Tasks/DropdownMenuItemStyleModifier.swift | 24 ++---- .../Tasks/TaskDropDownView.swift | 85 ------------------- .../ActivityViewer/Tasks/TaskView.swift | 31 +++++++ .../Tasks/TasksPopoverMenuItem.swift | 55 ++++++++++++ .../CodeEditWindowController+Toolbar.swift | 34 ++------ .../CodeEditWindowControllerExtensions.swift | 5 +- .../Features/Tasks/Models/CEActiveTask.swift | 2 +- CodeEdit/Features/Tasks/TaskManager.swift | 4 +- .../Tasks/Views/StopToolbarButton.swift | 34 ++++++++ .../DebugUtility/TaskOutputView.swift | 2 +- .../DebugUtility/TaskSidebarTileView.swift | 25 ------ .../DebugUtility/UtilityAreaDebugView.swift | 63 +++++++++++--- .../ViewModels/UtilityAreaViewModel.swift | 2 + .../UtilityArea/Views/UtilityAreaView.swift | 12 +-- 20 files changed, 235 insertions(+), 186 deletions(-) rename CodeEdit/Features/ActivityViewer/{Notificaitons => Notifications}/CECircularProgressView.swift (100%) rename CodeEdit/Features/ActivityViewer/{Notificaitons => Notifications}/TaskNotificationHandler.swift (100%) rename CodeEdit/Features/ActivityViewer/{Notificaitons => Notifications}/TaskNotificationView.swift (100%) rename CodeEdit/Features/ActivityViewer/{Notificaitons => Notifications}/TaskNotificationsDetailView.swift (100%) create mode 100644 CodeEdit/Features/ActivityViewer/Tasks/ActiveTaskView.swift create mode 100644 CodeEdit/Features/ActivityViewer/Tasks/TaskView.swift create mode 100644 CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift delete mode 100644 CodeEdit/Features/UtilityArea/DebugUtility/TaskSidebarTileView.swift diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index 933edf300..bfff21cc0 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -325,7 +325,6 @@ 618725A62C29F02500987354 /* DropdownMenuItemStyleModifier.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618725A52C29F02500987354 /* DropdownMenuItemStyleModifier.swift */; }; 618725A82C29F05500987354 /* OptionMenuItemView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618725A72C29F05500987354 /* OptionMenuItemView.swift */; }; 618725AB2C29F2C000987354 /* TaskDropDownView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618725AA2C29F2C000987354 /* TaskDropDownView.swift */; }; - 618725B02C2DBBE900987354 /* TaskSidebarTileView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618725AF2C2DBBE900987354 /* TaskSidebarTileView.swift */; }; 618725B22C2DBC0C00987354 /* TaskOutputView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618725B12C2DBC0C00987354 /* TaskOutputView.swift */; }; 618725B42C2DBC2800987354 /* TaskOutputActionsView.swift in Sources */ = {isa = PBXBuildFile; fileRef = 618725B32C2DBC2800987354 /* TaskOutputActionsView.swift */; }; 6195E30D2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift in Sources */ = {isa = PBXBuildFile; fileRef = 6195E30C2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift */; }; @@ -532,6 +531,9 @@ B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */; }; B69BFDC72B0686910050D9A6 /* GitClient+Initiate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */; }; B69D3EDE2C5E85A2005CF43A /* StopToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */; }; + B69D3EE12C5F5357005CF43A /* TaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EE02C5F5357005CF43A /* TaskView.swift */; }; + B69D3EE32C5F536B005CF43A /* ActiveTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EE22C5F536B005CF43A /* ActiveTaskView.swift */; }; + B69D3EE52C5F54B3005CF43A /* TasksPopoverMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EE42C5F54B3005CF43A /* TasksPopoverMenuItem.swift */; }; B6A43C5D29FC4AF00027E0E0 /* CreateSSHKeyView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */; }; B6AB09A12AAABAAE0003A3A6 /* EditorTabs.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09A02AAABAAE0003A3A6 /* EditorTabs.swift */; }; B6AB09A32AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift in Sources */ = {isa = PBXBuildFile; fileRef = B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */; }; @@ -962,7 +964,6 @@ 618725A52C29F02500987354 /* DropdownMenuItemStyleModifier.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = DropdownMenuItemStyleModifier.swift; sourceTree = ""; }; 618725A72C29F05500987354 /* OptionMenuItemView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = OptionMenuItemView.swift; sourceTree = ""; }; 618725AA2C29F2C000987354 /* TaskDropDownView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskDropDownView.swift; sourceTree = ""; }; - 618725AF2C2DBBE900987354 /* TaskSidebarTileView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskSidebarTileView.swift; sourceTree = ""; }; 618725B12C2DBC0C00987354 /* TaskOutputView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskOutputView.swift; sourceTree = ""; }; 618725B32C2DBC2800987354 /* TaskOutputActionsView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskOutputActionsView.swift; sourceTree = ""; }; 6195E30C2B64044F007261CA /* WorkspaceDocument+SearchState+FindTests.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "WorkspaceDocument+SearchState+FindTests.swift"; sourceTree = ""; }; @@ -1163,6 +1164,9 @@ B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsAccountLink.swift; sourceTree = ""; }; B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Initiate.swift"; sourceTree = ""; }; B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopToolbarButton.swift; sourceTree = ""; }; + B69D3EE02C5F5357005CF43A /* TaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskView.swift; sourceTree = ""; }; + B69D3EE22C5F536B005CF43A /* ActiveTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveTaskView.swift; sourceTree = ""; }; + B69D3EE42C5F54B3005CF43A /* TasksPopoverMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksPopoverMenuItem.swift; sourceTree = ""; }; B6A43C5C29FC4AF00027E0E0 /* CreateSSHKeyView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = CreateSSHKeyView.swift; sourceTree = ""; }; B6AB09A02AAABAAE0003A3A6 /* EditorTabs.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabs.swift; sourceTree = ""; }; B6AB09A22AAABFEC0003A3A6 /* EditorTabBarLeadingAccessories.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = EditorTabBarLeadingAccessories.swift; sourceTree = ""; }; @@ -2560,7 +2564,7 @@ children = ( 617DB3DB2C25B14A00B58BFE /* ActivityViewer.swift */, 618725A22C29EFE200987354 /* Tasks */, - 618725A92C29F09900987354 /* Notificaitons */, + 618725A92C29F09900987354 /* Notifications */, 617DB3D12C25AFD300B58BFE /* Models */, ); path = ActivityViewer; @@ -2587,6 +2591,9 @@ children = ( 618725A02C29EFCC00987354 /* SchemeDropDownView.swift */, 618725AA2C29F2C000987354 /* TaskDropDownView.swift */, + B69D3EE02C5F5357005CF43A /* TaskView.swift */, + B69D3EE22C5F536B005CF43A /* ActiveTaskView.swift */, + B69D3EE42C5F54B3005CF43A /* TasksPopoverMenuItem.swift */, 618725A32C29F00400987354 /* WorkspaceMenuItemView.swift */, 618725A72C29F05500987354 /* OptionMenuItemView.swift */, 618725A52C29F02500987354 /* DropdownMenuItemStyleModifier.swift */, @@ -2594,7 +2601,7 @@ path = Tasks; sourceTree = ""; }; - 618725A92C29F09900987354 /* Notificaitons */ = { + 618725A92C29F09900987354 /* Notifications */ = { isa = PBXGroup; children = ( 617DB3D52C25B02D00B58BFE /* TaskNotificationView.swift */, @@ -2602,7 +2609,7 @@ 617DB3D72C25B04D00B58BFE /* CECircularProgressView.swift */, 617DB3CF2C25AFAE00B58BFE /* TaskNotificationHandler.swift */, ); - path = Notificaitons; + path = Notifications; sourceTree = ""; }; 618725B92C33107C00987354 /* CEWorkspaceSettings */ = { @@ -3226,7 +3233,6 @@ children = ( B62AEDB42A1FE295009A9F52 /* UtilityAreaDebugView.swift */, 618725B32C2DBC2800987354 /* TaskOutputActionsView.swift */, - 618725AF2C2DBBE900987354 /* TaskSidebarTileView.swift */, 618725B12C2DBC0C00987354 /* TaskOutputView.swift */, ); path = DebugUtility; @@ -3967,6 +3973,7 @@ 6CC17B512C43311900834E2C /* ProjectNavigatorViewController+NSOutlineViewDataSource.swift in Sources */, 587B9E6429301D8F00AC7927 /* GitLabCommit.swift in Sources */, B6E55C3B2A95368E003ECC7D /* EditorTabsOverflowShadow.swift in Sources */, + B69D3EE12C5F5357005CF43A /* TaskView.swift in Sources */, 58A5DFA229339F6400D1BD5D /* KeybindingManager.swift in Sources */, B62AEDB32A1FD95B009A9F52 /* UtilityAreaTerminalView.swift in Sources */, 661EF7BD2BEE215300C3E577 /* LoadingFileView.swift in Sources */, @@ -4016,6 +4023,7 @@ 587B9E6329301D8F00AC7927 /* GitLabAccount.swift in Sources */, 6C1CC99B2B1E7CBC0002349B /* FindNavigatorIndexBar.swift in Sources */, 285FEC7027FE4B9800E57D53 /* ProjectNavigatorTableViewCell.swift in Sources */, + B69D3EE32C5F536B005CF43A /* ActiveTaskView.swift in Sources */, 300051672BBD3A5D00A98562 /* ServiceContainer.swift in Sources */, 6CB9144B29BEC7F100BC47F2 /* (null) in Sources */, 587B9E7429301D8F00AC7927 /* URL+URLParameters.swift in Sources */, @@ -4087,7 +4095,6 @@ 587B9DA629300ABD00AC7927 /* ToolbarBranchPicker.swift in Sources */, 6C6BD6F629CD145F00235D17 /* ExtensionInfo.swift in Sources */, 04BA7C202AE2D92B00584E1C /* GitClient+Status.swift in Sources */, - 618725B02C2DBBE900987354 /* TaskSidebarTileView.swift in Sources */, 58F2EB05292FB2B0004A9BDE /* Settings.swift in Sources */, 6CBD1BC62978DE53006639D5 /* Font+Caption3.swift in Sources */, 30E6D0012A6E505200A58B20 /* NavigatorSidebarViewModel.swift in Sources */, @@ -4221,6 +4228,7 @@ 611192002B08CCD700D4459B /* SearchIndexer+Memory.swift in Sources */, 587B9E8129301D8F00AC7927 /* PublicKey.swift in Sources */, 611191FE2B08CCD200D4459B /* SearchIndexer+File.swift in Sources */, + B69D3EE52C5F54B3005CF43A /* TasksPopoverMenuItem.swift in Sources */, 669A50532C380C8E00304CD8 /* Collection+subscript_safe.swift in Sources */, 5B241BF32B6DDBFF0016E616 /* IgnorePatternListItemView.swift in Sources */, 6CB52DC92AC8DC3E002E75B3 /* CEWorkspaceFileManager+FileManagement.swift in Sources */, diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/CECircularProgressView.swift b/CodeEdit/Features/ActivityViewer/Notifications/CECircularProgressView.swift similarity index 100% rename from CodeEdit/Features/ActivityViewer/Notificaitons/CECircularProgressView.swift rename to CodeEdit/Features/ActivityViewer/Notifications/CECircularProgressView.swift diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationHandler.swift b/CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationHandler.swift similarity index 100% rename from CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationHandler.swift rename to CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationHandler.swift diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift b/CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationView.swift similarity index 100% rename from CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationView.swift rename to CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationView.swift diff --git a/CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationsDetailView.swift b/CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationsDetailView.swift similarity index 100% rename from CodeEdit/Features/ActivityViewer/Notificaitons/TaskNotificationsDetailView.swift rename to CodeEdit/Features/ActivityViewer/Notifications/TaskNotificationsDetailView.swift diff --git a/CodeEdit/Features/ActivityViewer/Tasks/ActiveTaskView.swift b/CodeEdit/Features/ActivityViewer/Tasks/ActiveTaskView.swift new file mode 100644 index 000000000..eab82ba5f --- /dev/null +++ b/CodeEdit/Features/ActivityViewer/Tasks/ActiveTaskView.swift @@ -0,0 +1,21 @@ +// +// ActiveTaskView.swift +// CodeEdit +// +// Created by Austin Condiff on 8/4/24. +// + +import SwiftUI + +// We need to observe each active task individually because: +// 1. Active tasks are nested inside TaskManager. +// 2. Reference types (like objects) do not notify observers when their internal state changes. +/// `ActiveTaskView` represents a single active task and observes its state. +/// - Parameter activeTask: The active task to be displayed and observed. +struct ActiveTaskView: View { + @ObservedObject var activeTask: CEActiveTask + + var body: some View { + TaskView(task: activeTask.task, status: activeTask.status) + } +} diff --git a/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift b/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift index 0285955a1..859a7ade2 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/DropdownMenuItemStyleModifier.swift @@ -12,28 +12,14 @@ struct DropdownMenuItemStyleModifier: ViewModifier { func body(content: Content) -> some View { content - .background(isHovering ? AnyView(HighlightedBackground()) : AnyView(Color.clear)) + .background( + isHovering + ? AnyView(EffectView(.selection, blendingMode: .withinWindow, emphasized: true)) + : AnyView(Color.clear) + ) .foregroundColor(isHovering ? Color(NSColor.white) : .primary) .onHover(perform: { hovering in self.isHovering = hovering }) } } -struct SelectionVisualEffectView: NSViewRepresentable { - func makeNSView(context: Context) -> NSVisualEffectView { - let view = NSVisualEffectView() - view.material = .selection - view.blendingMode = .withinWindow - view.isEmphasized = true - return view - } - - func updateNSView(_ nsView: NSVisualEffectView, context: Context) {} -} - -struct HighlightedBackground: View { - var body: some View { - SelectionVisualEffectView() - .frame(maxWidth: .infinity, maxHeight: .infinity) - } -} diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift index 6d3ea3f93..72de62545 100644 --- a/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskDropDownView.swift @@ -88,88 +88,3 @@ struct TaskDropDownView: View { .frame(minWidth: 215) } } - -/// `TaskView` represents a single active task and observes its state. -/// - Parameter task: The task to be displayed and observed. -/// - Parameter status: The status of the task to be displayed. -struct TaskView: View { - @ObservedObject var task: CETask - var status: CETaskStatus - - var body: some View { - HStack(spacing: 5) { -// Label(task.name, systemImage: "gearshape") -// .labelStyle(.titleAndIcon) - Image(systemName: "gearshape") - Text(task.name) - Spacer(minLength: 0) - } - .padding(.trailing, 7.5) - .overlay(alignment: .trailing) { - Circle() - .fill(status.color) - .frame(width: 5, height: 5) - .padding(.trailing, 2.5) - } - } -} - -// We need to observe each active task individually because: -// 1. Active tasks are nested inside TaskManager. -// 2. Reference types (like objects) do not notify observers when their internal state changes. -/// `ActiveTaskView` represents a single active task and observes its state. -/// - Parameter activeTask: The active task to be displayed and observed. -struct ActiveTaskView: View { - @ObservedObject var activeTask: CEActiveTask - - var body: some View { - TaskView(task: activeTask.task, status: activeTask.status) - } -} - -struct TasksPopoverMenuItem: View { - @Environment(\.dismiss) - private var dismiss - - @ObservedObject var taskManager: TaskManager - var task: CETask - - var body: some View { - HStack(spacing: 5) { - selectionIndicator - popoverContent - } - .padding(.vertical, 4) - .padding(.horizontal, 8) - .modifier(DropdownMenuItemStyleModifier()) - .onTapGesture { - taskManager.selectedTaskID = task.id - dismiss() - } - .clipShape(RoundedRectangle(cornerRadius: 5)) - } - - private var selectionIndicator: some View { - Group { - if taskManager.selectedTaskID == task.id { - Image(systemName: "checkmark") - .fontWeight(.bold) - .imageScale(.small) - .frame(width: 10) - } else { - Spacer() - .frame(width: 10) - } - } - } - - private var popoverContent: some View { - Group { - if let activeTask = taskManager.activeTasks[task.id] { - ActiveTaskView(activeTask: activeTask) - } else { - TaskView(task: task, status: taskManager.taskStatus(taskID: task.id)) - } - } - } -} diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TaskView.swift b/CodeEdit/Features/ActivityViewer/Tasks/TaskView.swift new file mode 100644 index 000000000..92afa0726 --- /dev/null +++ b/CodeEdit/Features/ActivityViewer/Tasks/TaskView.swift @@ -0,0 +1,31 @@ +// +// TaskView.swift +// CodeEdit +// +// Created by Austin Condiff on 8/4/24. +// + +import SwiftUI + +/// `TaskView` represents a single active task and observes its state. +/// - Parameter task: The task to be displayed and observed. +/// - Parameter status: The status of the task to be displayed. +struct TaskView: View { + @ObservedObject var task: CETask + var status: CETaskStatus + + var body: some View { + HStack(spacing: 5) { + Image(systemName: "gearshape") + Text(task.name) + Spacer(minLength: 0) + } + .padding(.trailing, 7.5) + .overlay(alignment: .trailing) { + Circle() + .fill(status.color) + .frame(width: 5, height: 5) + .padding(.trailing, 2.5) + } + } +} diff --git a/CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift b/CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift new file mode 100644 index 000000000..0cb3a02c2 --- /dev/null +++ b/CodeEdit/Features/ActivityViewer/Tasks/TasksPopoverMenuItem.swift @@ -0,0 +1,55 @@ +// +// TasksPopoverMenuItem.swift +// CodeEdit +// +// Created by Austin Condiff on 8/4/24. +// + +import SwiftUI + +struct TasksPopoverMenuItem: View { + @Environment(\.dismiss) + private var dismiss + + @ObservedObject var taskManager: TaskManager + var task: CETask + + var body: some View { + HStack(spacing: 5) { + selectionIndicator + popoverContent + } + .padding(.vertical, 4) + .padding(.horizontal, 8) + .modifier(DropdownMenuItemStyleModifier()) + .onTapGesture { + taskManager.selectedTaskID = task.id + dismiss() + } + .clipShape(RoundedRectangle(cornerRadius: 5)) + } + + private var selectionIndicator: some View { + Group { + if taskManager.selectedTaskID == task.id { + Image(systemName: "checkmark") + .fontWeight(.bold) + .imageScale(.small) + .frame(width: 10) + } else { + Spacer() + .frame(width: 10) + } + } + } + + private var popoverContent: some View { + Group { + if let activeTask = taskManager.activeTasks[task.id] { + ActiveTaskView(activeTask: activeTask) + } else { + TaskView(task: task, status: taskManager.taskStatus(taskID: task.id)) + } + } + } +} diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index 6d6c25914..a3076af54 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -117,16 +117,6 @@ extension CodeEditWindowController { guard let taskManager = workspace?.taskManager else { return nil } - toolbarItem.label = "Stop" - toolbarItem.paletteLabel = "Stop" - toolbarItem.toolTip = "Stop selected task" - toolbarItem.isBordered = true - toolbarItem.target = self - toolbarItem.image = NSImage( - systemSymbolName: "stop.fill", - accessibilityDescription: nil - )?.withSymbolConfiguration(.init(pointSize: 15, weight: .regular)) - let view = NSHostingView( rootView: StopToolbarButton(taskManager: taskManager) ) @@ -140,23 +130,19 @@ extension CodeEditWindowController { toolbarItem.toolTip = "Start selected task" toolbarItem.isBordered = true toolbarItem.target = self - toolbarItem.action = #selector(self.runActiveTask) toolbarItem.image = NSImage( systemSymbolName: "play.fill", accessibilityDescription: nil )?.withSymbolConfiguration(.init(scale: .large)) + guard let taskManager = workspace?.taskManager + else { return nil } + guard let workspace = workspace + else { return nil } + let view = NSHostingView( - rootView: Button { - self.runActiveTask() - } label: { - Label("Start", systemImage: "play.fill") - .labelStyle(.iconOnly) - .font(.system(size: 18, weight: .regular)) - .help("Start selected task") - .frame(width: 28) - .offset(CGSize(width: 0, height: 2.5)) - } + rootView: StartTaskToolbarButton(taskManager: taskManager) + .environmentObject(workspace) ) toolbarItem.view = view @@ -204,10 +190,4 @@ extension CodeEditWindowController { return NSToolbarItem(itemIdentifier: itemIdentifier) } } - - @objc - private func runActiveTask() { - guard let taskManager = workspace?.taskManager else { return } - taskManager.executeActiveTask() - } } diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift index 4490bfc0c..6c487cefd 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowControllerExtensions.swift @@ -113,7 +113,10 @@ extension CodeEditWindowController { let settingsWindow = NSWindow() self.workspaceSettingsWindow = settingsWindow let contentView = CEWorkspaceSettingsView( - dismiss: { self.window?.endSheet(settingsWindow) } + dismiss: { [weak self, weak settingsWindow] in + guard let settingsWindow else { return } + self?.window?.endSheet(settingsWindow) + } ) .environmentObject(workspaceSettingsManager) .environmentObject(workspace) diff --git a/CodeEdit/Features/Tasks/Models/CEActiveTask.swift b/CodeEdit/Features/Tasks/Models/CEActiveTask.swift index 9ef513cee..16d0b021c 100644 --- a/CodeEdit/Features/Tasks/Models/CEActiveTask.swift +++ b/CodeEdit/Features/Tasks/Models/CEActiveTask.swift @@ -80,7 +80,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { func handleProcessFinished(terminationStatus: Int32) async { handleTerminationStatus(terminationStatus) - print(terminationStatus) + if terminationStatus == 0 { await updateOutput("\nFinished running \(task.name).\n\n") await updateTaskStatus(to: .finished) diff --git a/CodeEdit/Features/Tasks/TaskManager.swift b/CodeEdit/Features/Tasks/TaskManager.swift index 0036c06f6..9b2c5c468 100644 --- a/CodeEdit/Features/Tasks/TaskManager.swift +++ b/CodeEdit/Features/Tasks/TaskManager.swift @@ -12,11 +12,11 @@ import Combine class TaskManager: ObservableObject { @Published var activeTasks: [UUID: CEActiveTask] = [:] @Published var selectedTaskID: UUID? + @Published var taskShowingOutput: UUID? @ObservedObject var workspaceSettings: CEWorkspaceSettingsData private var settingsListener: AnyCancellable? - private var taskStatusListener: AnyCancellable? init(workspaceSettings: CEWorkspaceSettingsData) { self.workspaceSettings = workspaceSettings @@ -170,6 +170,4 @@ class TaskManager: ObservableObject { terminateTask(taskID: taskID) activeTasks.removeValue(forKey: taskID) } - - private var cancellables = Set() } diff --git a/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift b/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift index f6553081e..d5514cc85 100644 --- a/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift +++ b/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift @@ -58,3 +58,37 @@ struct StopToolbarButton: View { } } } + +struct StartTaskToolbarButton: View { + @UpdatingWindowController var windowController: CodeEditWindowController? + + // TODO: try to get this from the environment + @ObservedObject var taskManager: TaskManager + @EnvironmentObject var workspace: WorkspaceDocument + + var utilityAreaCollapsed: Bool { + windowController?.workspace?.utilityAreaModel?.isCollapsed ?? true + } + + var body: some View { + Button { + self.runActiveTask() + if utilityAreaCollapsed { + CommandManager.shared.executeCommand("open.drawer") + } + workspace.utilityAreaModel?.selectedTab = .debugConsole + taskManager.taskShowingOutput = taskManager.selectedTaskID + } label: { + Label("Start", systemImage: "play.fill") + .labelStyle(.iconOnly) + .font(.system(size: 18, weight: .regular)) + .help("Start selected task") + .frame(width: 28) + .offset(CGSize(width: 0, height: 2.5)) + } + } + + private func runActiveTask() { + taskManager.executeActiveTask() + } +} diff --git a/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputView.swift b/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputView.swift index 3ba8c49fd..bd375e4fc 100644 --- a/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputView.swift +++ b/CodeEdit/Features/UtilityArea/DebugUtility/TaskOutputView.swift @@ -16,6 +16,6 @@ struct TaskOutputView: View { .textSelection(.enabled) } .frame(maxWidth: .infinity, maxHeight: .infinity, alignment: .leading) - .padding() + .padding(10) } } diff --git a/CodeEdit/Features/UtilityArea/DebugUtility/TaskSidebarTileView.swift b/CodeEdit/Features/UtilityArea/DebugUtility/TaskSidebarTileView.swift deleted file mode 100644 index a421be14c..000000000 --- a/CodeEdit/Features/UtilityArea/DebugUtility/TaskSidebarTileView.swift +++ /dev/null @@ -1,25 +0,0 @@ -// -// TaskSidebarTileView.swift -// CodeEdit -// -// Created by Tommy Ludwig on 27.06.24. -// - -import SwiftUI - -struct TaskSidebarTileView: View { - @ObservedObject var activeTask: CEActiveTask - var body: some View { - HStack { - Image(systemName: "gearshape") - .imageScale(.medium) - Text(activeTask.task.name) - - Spacer() - - Circle() - .fill(activeTask.status.color) - .frame(width: 5, height: 5) - } - } -} diff --git a/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift b/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift index b6f7e5c4b..0486c1f99 100644 --- a/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift +++ b/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift @@ -8,12 +8,23 @@ import SwiftUI struct UtilityAreaDebugView: View { + @AppSettings(\.theme.matchAppearance) + private var matchAppearance + @AppSettings(\.terminal.darkAppearance) + private var darkAppearance + @AppSettings(\.theme.useThemeBackground) + private var useThemeBackground + + @Environment(\.colorScheme) + private var colorScheme + @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel @EnvironmentObject private var taskManager: TaskManager - @State var tabSelection: UUID? @State private var scrollProxy: ScrollViewProxy? + @StateObject private var themeModel: ThemeModel = .shared + @Namespace var bottomID var body: some View { @@ -27,10 +38,10 @@ struct UtilityAreaDebugView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) Spacer() } - .opacity(tabSelection == nil ? 1 : 0) + .opacity(taskManager.taskShowingOutput == nil ? 1 : 0) - if let tabSelection, - let activeTask = taskManager.activeTasks[tabSelection] { + if let taskShowingOutput = taskManager.taskShowingOutput, + let activeTask = taskManager.activeTasks[taskShowingOutput] { ScrollViewReader { proxy in VStack { ScrollView { @@ -64,7 +75,26 @@ struct UtilityAreaDebugView: View { bottomID: _bottomID ) } - .background(EffectView(.contentBackground)) + .background { + if utilityAreaViewModel.selectedTerminals.isEmpty { + EffectView(.contentBackground) + } else if useThemeBackground { + Color(nsColor: backgroundColor) + } else { + if colorScheme == .dark { + EffectView(.underPageBackground) + } else { + EffectView(.contentBackground) + } + } + } + .colorScheme( + utilityAreaViewModel.selectedTerminals.isEmpty + ? colorScheme + : matchAppearance && darkAppearance + ? themeModel.selectedDarkTheme?.appearance == .dark ? .dark : .light + : themeModel.selectedTheme?.appearance == .dark ? .dark : .light + ) } } } leadingSidebar: { _ in @@ -75,14 +105,12 @@ struct UtilityAreaDebugView: View { .frame(maxWidth: .infinity, maxHeight: .infinity) .opacity(taskManager.activeTasks.isEmpty ? 1 : 0) - List(selection: $tabSelection) { + List(selection: $taskManager.taskShowingOutput) { ForEach(Array(taskManager.activeTasks.keys), id: \.self) { taskID in if let activeTask = taskManager.activeTasks[taskID] { - TaskSidebarTileView(activeTask: activeTask) + ActiveTaskView(activeTask: activeTask) .onTapGesture { - withAnimation { - self.tabSelection = taskID - } + taskManager.taskShowingOutput = taskID } .contextMenu( ContextMenu { @@ -101,9 +129,20 @@ struct UtilityAreaDebugView: View { .paneToolbar { Spacer() } // Background } }.onReceive(taskManager.$activeTasks) { newTasks in - if tabSelection == nil { - self.tabSelection = newTasks.first?.key + if taskManager.taskShowingOutput == nil { + taskManager.taskShowingOutput = newTasks.first?.key } } } + + /// Returns the `background` color of the selected theme + private var backgroundColor: NSColor { + if let selectedTheme = matchAppearance && darkAppearance + ? themeModel.selectedDarkTheme + : themeModel.selectedTheme, + let index = themeModel.themes.firstIndex(of: selectedTheme) { + return NSColor(themeModel.themes[index].terminal.background.swiftColor) + } + return .windowBackgroundColor + } } diff --git a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift index b713ed70e..cf7388edf 100644 --- a/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift +++ b/CodeEdit/Features/UtilityArea/ViewModels/UtilityAreaViewModel.swift @@ -12,6 +12,8 @@ import SwiftUI /// A model class to host and manage data for the Utility area. class UtilityAreaViewModel: ObservableObject { + @Published var selectedTab: UtilityAreaTab? = .terminal + @Published var terminals: [UtilityAreaTerminal] = [] @Published var selectedTerminals: Set = [] diff --git a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift index bd897f902..97bb62445 100644 --- a/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift +++ b/CodeEdit/Features/UtilityArea/Views/UtilityAreaView.swift @@ -21,12 +21,10 @@ struct UtilityAreaView: View { @StateObject private var themeModel: ThemeModel = .shared - @State var selection: UtilityAreaTab? = .terminal - var body: some View { VStack(spacing: 0) { - if let selection { - selection + if let selectedTab = utilityAreaViewModel.selectedTab { + selectedTab } else { Text("Tab not found") .frame(maxWidth: .infinity, maxHeight: .infinity) @@ -34,7 +32,11 @@ struct UtilityAreaView: View { } .safeAreaInset(edge: .leading, spacing: 0) { HStack(spacing: 0) { - AreaTabBar(items: $utilityAreaViewModel.tabItems, selection: $selection, position: .side) + AreaTabBar( + items: $utilityAreaViewModel.tabItems, + selection: $utilityAreaViewModel.selectedTab, + position: .side + ) Divider() .overlay(Color(nsColor: colorScheme == .dark ? .black : .clear)) } From 6ef8036dc3743f20b6dc496a0fac249a71dcc507 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 4 Aug 2024 01:53:02 -0500 Subject: [PATCH 12/15] Fixed SwiftLint errors --- .../UtilityArea/DebugUtility/UtilityAreaDebugView.swift | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift b/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift index 0486c1f99..0c98f6e5f 100644 --- a/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift +++ b/CodeEdit/Features/UtilityArea/DebugUtility/UtilityAreaDebugView.swift @@ -17,14 +17,14 @@ struct UtilityAreaDebugView: View { @Environment(\.colorScheme) private var colorScheme - + @EnvironmentObject private var utilityAreaViewModel: UtilityAreaViewModel @EnvironmentObject private var taskManager: TaskManager @State private var scrollProxy: ScrollViewProxy? @StateObject private var themeModel: ThemeModel = .shared - + @Namespace var bottomID var body: some View { From 2f008a4c3734a865c4012c44a775e5183595909a Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 4 Aug 2024 16:25:36 -0500 Subject: [PATCH 13/15] PR issues --- CodeEdit.xcodeproj/project.pbxproj | 12 ++++-- .../CodeEditWindowController+Toolbar.swift | 17 ++------ .../Tasks/Views/StartTaskToolbarButton.swift | 37 ++++++++++++++++++ ...tton.swift => StopTaskToolbarButton.swift} | 39 +------------------ 4 files changed, 50 insertions(+), 55 deletions(-) create mode 100644 CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift rename CodeEdit/Features/Tasks/Views/{StopToolbarButton.swift => StopTaskToolbarButton.swift} (63%) diff --git a/CodeEdit.xcodeproj/project.pbxproj b/CodeEdit.xcodeproj/project.pbxproj index bfff21cc0..6056648be 100644 --- a/CodeEdit.xcodeproj/project.pbxproj +++ b/CodeEdit.xcodeproj/project.pbxproj @@ -527,10 +527,11 @@ B67DB0F62AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DB0F52AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift */; }; B67DB0F92AFDF638002DC647 /* IconButtonStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DB0F82AFDF638002DC647 /* IconButtonStyle.swift */; }; B67DB0FC2AFDF71F002DC647 /* IconToggleStyle.swift in Sources */ = {isa = PBXBuildFile; fileRef = B67DB0FB2AFDF71F002DC647 /* IconToggleStyle.swift */; }; + B68108042C60287F008B27C1 /* StartTaskToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B68108032C60287F008B27C1 /* StartTaskToolbarButton.swift */; }; B685DE7929CC9CCD002860C8 /* StatusBarIcon.swift in Sources */ = {isa = PBXBuildFile; fileRef = B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */; }; B697937A29FF5668002027EC /* AccountsSettingsAccountLink.swift in Sources */ = {isa = PBXBuildFile; fileRef = B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */; }; B69BFDC72B0686910050D9A6 /* GitClient+Initiate.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */; }; - B69D3EDE2C5E85A2005CF43A /* StopToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */; }; + B69D3EDE2C5E85A2005CF43A /* StopTaskToolbarButton.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EDD2C5E85A2005CF43A /* StopTaskToolbarButton.swift */; }; B69D3EE12C5F5357005CF43A /* TaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EE02C5F5357005CF43A /* TaskView.swift */; }; B69D3EE32C5F536B005CF43A /* ActiveTaskView.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EE22C5F536B005CF43A /* ActiveTaskView.swift */; }; B69D3EE52C5F54B3005CF43A /* TasksPopoverMenuItem.swift in Sources */ = {isa = PBXBuildFile; fileRef = B69D3EE42C5F54B3005CF43A /* TasksPopoverMenuItem.swift */; }; @@ -1160,10 +1161,11 @@ B67DB0F52AFC2A7A002DC647 /* FindNavigatorToolbarBottom.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = FindNavigatorToolbarBottom.swift; sourceTree = ""; }; B67DB0F82AFDF638002DC647 /* IconButtonStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconButtonStyle.swift; sourceTree = ""; }; B67DB0FB2AFDF71F002DC647 /* IconToggleStyle.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = IconToggleStyle.swift; sourceTree = ""; }; + B68108032C60287F008B27C1 /* StartTaskToolbarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StartTaskToolbarButton.swift; sourceTree = ""; }; B685DE7829CC9CCD002860C8 /* StatusBarIcon.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StatusBarIcon.swift; sourceTree = ""; }; B697937929FF5668002027EC /* AccountsSettingsAccountLink.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = AccountsSettingsAccountLink.swift; sourceTree = ""; }; B69BFDC62B0686910050D9A6 /* GitClient+Initiate.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = "GitClient+Initiate.swift"; sourceTree = ""; }; - B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopToolbarButton.swift; sourceTree = ""; }; + B69D3EDD2C5E85A2005CF43A /* StopTaskToolbarButton.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = StopTaskToolbarButton.swift; sourceTree = ""; }; B69D3EE02C5F5357005CF43A /* TaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TaskView.swift; sourceTree = ""; }; B69D3EE22C5F536B005CF43A /* ActiveTaskView.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = ActiveTaskView.swift; sourceTree = ""; }; B69D3EE42C5F54B3005CF43A /* TasksPopoverMenuItem.swift */ = {isa = PBXFileReference; lastKnownFileType = sourcecode.swift; path = TasksPopoverMenuItem.swift; sourceTree = ""; }; @@ -3270,7 +3272,8 @@ B69D3EDC2C5E856F005CF43A /* Views */ = { isa = PBXGroup; children = ( - B69D3EDD2C5E85A2005CF43A /* StopToolbarButton.swift */, + B69D3EDD2C5E85A2005CF43A /* StopTaskToolbarButton.swift */, + B68108032C60287F008B27C1 /* StartTaskToolbarButton.swift */, ); path = Views; sourceTree = ""; @@ -3958,6 +3961,7 @@ 6C147C4D29A32AA30089B630 /* EditorAreaView.swift in Sources */, B6152B802ADAE421004C6012 /* CodeEditWindowControllerExtensions.swift in Sources */, 587B9E7B29301D8F00AC7927 /* GitHubRouter.swift in Sources */, + B68108042C60287F008B27C1 /* StartTaskToolbarButton.swift in Sources */, 201169E22837B3D800F92B46 /* SourceControlNavigatorChangesView.swift in Sources */, 850C631029D6B01D00E1444C /* SettingsView.swift in Sources */, 77A01E6D2BC3EA2A00F0EA38 /* NSWindow+Child.swift in Sources */, @@ -4071,7 +4075,7 @@ 587B9E9929301D8F00AC7927 /* GitChangedFile.swift in Sources */, 6C147C4B29A32A7B0089B630 /* Environment+SplitEditor.swift in Sources */, 2897E1C72979A29200741E32 /* TrackableScrollView.swift in Sources */, - B69D3EDE2C5E85A2005CF43A /* StopToolbarButton.swift in Sources */, + B69D3EDE2C5E85A2005CF43A /* StopTaskToolbarButton.swift in Sources */, 58F2EB0E292FB2B0004A9BDE /* SoftwareUpdater.swift in Sources */, 587B9E9529301D8F00AC7927 /* BitBucketUser.swift in Sources */, 587B9E7C29301D8F00AC7927 /* GitHubRepositoryRouter.swift in Sources */, diff --git a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift index a3076af54..b0d612b3e 100644 --- a/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift +++ b/CodeEdit/Features/Documents/Controllers/CodeEditWindowController+Toolbar.swift @@ -118,27 +118,16 @@ extension CodeEditWindowController { else { return nil } let view = NSHostingView( - rootView: StopToolbarButton(taskManager: taskManager) + rootView: StopTaskToolbarButton(taskManager: taskManager) ) toolbarItem.view = view return toolbarItem case .startTaskSidebarItem: let toolbarItem = NSToolbarItem(itemIdentifier: NSToolbarItem.Identifier.startTaskSidebarItem) - toolbarItem.label = "Start" - toolbarItem.paletteLabel = "Start" - toolbarItem.toolTip = "Start selected task" - toolbarItem.isBordered = true - toolbarItem.target = self - toolbarItem.image = NSImage( - systemSymbolName: "play.fill", - accessibilityDescription: nil - )?.withSymbolConfiguration(.init(scale: .large)) - guard let taskManager = workspace?.taskManager - else { return nil } - guard let workspace = workspace - else { return nil } + guard let taskManager = workspace?.taskManager else { return nil } + guard let workspace = workspace else { return nil } let view = NSHostingView( rootView: StartTaskToolbarButton(taskManager: taskManager) diff --git a/CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift b/CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift new file mode 100644 index 000000000..2d9315270 --- /dev/null +++ b/CodeEdit/Features/Tasks/Views/StartTaskToolbarButton.swift @@ -0,0 +1,37 @@ +// +// StartTaskToolbarButton.swift +// CodeEdit +// +// Created by Austin Condiff on 8/4/24. +// + +import SwiftUI + +struct StartTaskToolbarButton: View { + @UpdatingWindowController var windowController: CodeEditWindowController? + + @ObservedObject var taskManager: TaskManager + @EnvironmentObject var workspace: WorkspaceDocument + + var utilityAreaCollapsed: Bool { + windowController?.workspace?.utilityAreaModel?.isCollapsed ?? true + } + + var body: some View { + Button { + taskManager.executeActiveTask() + if utilityAreaCollapsed { + CommandManager.shared.executeCommand("open.drawer") + } + workspace.utilityAreaModel?.selectedTab = .debugConsole + taskManager.taskShowingOutput = taskManager.selectedTaskID + } label: { + Label("Start", systemImage: "play.fill") + .labelStyle(.iconOnly) + .font(.system(size: 18, weight: .regular)) + .help("Start selected task") + .frame(width: 28) + .offset(CGSize(width: 0, height: 2.5)) + } + } +} diff --git a/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift b/CodeEdit/Features/Tasks/Views/StopTaskToolbarButton.swift similarity index 63% rename from CodeEdit/Features/Tasks/Views/StopToolbarButton.swift rename to CodeEdit/Features/Tasks/Views/StopTaskToolbarButton.swift index d5514cc85..837a834b0 100644 --- a/CodeEdit/Features/Tasks/Views/StopToolbarButton.swift +++ b/CodeEdit/Features/Tasks/Views/StopTaskToolbarButton.swift @@ -1,5 +1,5 @@ // -// StopToolbarButton.swift +// StopTaskToolbarButton.swift // CodeEdit // // Created by Austin Condiff on 8/3/24. @@ -8,8 +8,7 @@ import SwiftUI import Combine -struct StopToolbarButton: View { - // TODO: try to get this from the environment +struct StopTaskToolbarButton: View { @ObservedObject var taskManager: TaskManager /// Tracks the current selected task's status. Updated by `updateStatusListener` @@ -58,37 +57,3 @@ struct StopToolbarButton: View { } } } - -struct StartTaskToolbarButton: View { - @UpdatingWindowController var windowController: CodeEditWindowController? - - // TODO: try to get this from the environment - @ObservedObject var taskManager: TaskManager - @EnvironmentObject var workspace: WorkspaceDocument - - var utilityAreaCollapsed: Bool { - windowController?.workspace?.utilityAreaModel?.isCollapsed ?? true - } - - var body: some View { - Button { - self.runActiveTask() - if utilityAreaCollapsed { - CommandManager.shared.executeCommand("open.drawer") - } - workspace.utilityAreaModel?.selectedTab = .debugConsole - taskManager.taskShowingOutput = taskManager.selectedTaskID - } label: { - Label("Start", systemImage: "play.fill") - .labelStyle(.iconOnly) - .font(.system(size: 18, weight: .regular)) - .help("Start selected task") - .frame(width: 28) - .offset(CGSize(width: 0, height: 2.5)) - } - } - - private func runActiveTask() { - taskManager.executeActiveTask() - } -} From 3c4b9c2ec4863eff4161943a8f1b3e4c10c90162 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 4 Aug 2024 16:30:13 -0500 Subject: [PATCH 14/15] Removed statusSubject and statusPublisher --- CodeEdit/Features/Tasks/Models/CEActiveTask.swift | 10 +--------- 1 file changed, 1 insertion(+), 9 deletions(-) diff --git a/CodeEdit/Features/Tasks/Models/CEActiveTask.swift b/CodeEdit/Features/Tasks/Models/CEActiveTask.swift index 16d0b021c..56bc07c5f 100644 --- a/CodeEdit/Features/Tasks/Models/CEActiveTask.swift +++ b/CodeEdit/Features/Tasks/Models/CEActiveTask.swift @@ -14,11 +14,7 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { @Published private(set) var output: String = "" /// The status of the task. - @Published private(set) var status: CETaskStatus = .notRunning { - didSet { - statusSubject.send(status) - } - } + @Published private(set) var status: CETaskStatus = .notRunning /// The name of the associated task. @ObservedObject var task: CETask @@ -27,10 +23,6 @@ class CEActiveTask: ObservableObject, Identifiable, Hashable { var outputPipe: Pipe? private var cancellables = Set() - private let statusSubject = PassthroughSubject() - var statusPublisher: AnyPublisher { - statusSubject.eraseToAnyPublisher() - } init(task: CETask) { self.task = task From 30dd961196104e560bdaa1ae8d90e635a5ee2723 Mon Sep 17 00:00:00 2001 From: Austin Condiff Date: Sun, 4 Aug 2024 16:33:06 -0500 Subject: [PATCH 15/15] Fixed bug where you could not rerun a task that was finished --- CodeEdit/Features/Tasks/TaskManager.swift | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CodeEdit/Features/Tasks/TaskManager.swift b/CodeEdit/Features/Tasks/TaskManager.swift index 9b2c5c468..0ba8dda2f 100644 --- a/CodeEdit/Features/Tasks/TaskManager.swift +++ b/CodeEdit/Features/Tasks/TaskManager.swift @@ -72,7 +72,7 @@ class TaskManager: ObservableObject { activeTask.renew() // Wait until the task is no longer running. // The termination handler is asynchronous, so we avoid a race condition using this. - while activeTask.status != .notRunning { + while activeTask.status == .running { await Task.yield() } activeTask.run()