From 0040c2305048e97493ac9482c1479ea50d4ebc92 Mon Sep 17 00:00:00 2001 From: nimbold <11913706+nimbold@users.noreply.github.com> Date: Thu, 4 Jun 2026 14:29:13 +0330 Subject: [PATCH] style: refine UI layouts, toolbars, and chunk map aesthetics - Make global and contextual Start/Stop toolbar buttons mutually exclusive in main window - Relocate Add Downloads primary action buttons from top toolbar to standard bottom Action Bar - Expand Download Properties window width for better readability and condense font sizes - Wrap Advanced Transfer settings in a native DisclosureGroup to reduce scrolling - Redesign Chunk Map with rounded corners, larger boxes, and system accent colors --- Sources/Firelink/AddDownloadsView.swift | 54 ++++++++--------- Sources/Firelink/ChunkMapView.swift | 19 +++--- Sources/Firelink/ContentView.swift | 59 ++++++++----------- Sources/Firelink/DownloadPropertiesView.swift | 21 +++---- 4 files changed, 69 insertions(+), 84 deletions(-) diff --git a/Sources/Firelink/AddDownloadsView.swift b/Sources/Firelink/AddDownloadsView.swift index 18a387a..53b9dc3 100644 --- a/Sources/Firelink/AddDownloadsView.swift +++ b/Sources/Firelink/AddDownloadsView.swift @@ -38,9 +38,10 @@ struct AddDownloadsView: View { } .padding(12) } - } - .toolbar { + Divider() actionBar + .padding(16) + .background(.background) } .frame(minWidth: 640, idealWidth: 680, minHeight: 500, idealHeight: 540) .onChange(of: linkText) { _, newValue in @@ -218,49 +219,42 @@ struct AddDownloadsView: View { } } - @ToolbarContentBuilder - private var actionBar: some ToolbarContent { - ToolbarItem(placement: .status) { - HStack { - Text(actionMessage) - .font(.caption) - .foregroundStyle(.secondary) - .lineLimit(1) - - if metadataTask != nil { - Button { - metadataTask?.cancel() - metadataTask = nil - } label: { - Image(systemName: "xmark.circle.fill") - .foregroundStyle(.secondary) - } - .buttonStyle(.plain) + private var actionBar: some View { + HStack { + Text(actionMessage) + .font(.caption) + .foregroundStyle(.secondary) + .lineLimit(1) + + if metadataTask != nil { + Button { + metadataTask?.cancel() + metadataTask = nil + } label: { + Image(systemName: "xmark.circle.fill") + .foregroundStyle(.secondary) } + .buttonStyle(.plain) } - } - ToolbarItem(placement: .cancellationAction) { + Spacer() + Button("Cancel") { dismiss() } - } + .keyboardShortcut(.cancelAction) - ToolbarItemGroup(placement: .primaryAction) { - Button { + Button("Add to Queue") { addDownloads(start: false) - } label: { - Label("Add to Queue", systemImage: "list.bullet") } .disabled(!canAddDownloads) - Button { + Button("Start Downloads") { addDownloads(start: true) - } label: { - Label("Start Downloads", systemImage: "play.fill") } .buttonStyle(.borderedProminent) .disabled(!canAddDownloads) + .keyboardShortcut(.defaultAction) } } diff --git a/Sources/Firelink/ChunkMapView.swift b/Sources/Firelink/ChunkMapView.swift index fcdfb6f..806851c 100644 --- a/Sources/Firelink/ChunkMapView.swift +++ b/Sources/Firelink/ChunkMapView.swift @@ -114,8 +114,9 @@ struct ChunkGrid: View { var body: some View { let itemPieces = pieces Canvas { context, size in - let boxSize: CGFloat = 6 - let spacing: CGFloat = 1 + let boxSize: CGFloat = 10 + let spacing: CGFloat = 2 + let cornerSize = CGSize(width: 2, height: 2) let width = size.width let x: CGFloat = 0 @@ -126,10 +127,10 @@ struct ChunkGrid: View { var cy = y for piece in itemPieces { if piece { - p.addRect(CGRect(x: cx, y: cy, width: boxSize - spacing, height: boxSize - spacing)) + p.addRoundedRect(in: CGRect(x: cx, y: cy, width: boxSize - spacing, height: boxSize - spacing), cornerSize: cornerSize) } cx += boxSize - if cx >= width { + if cx + boxSize > width { cx = 0 cy += boxSize } @@ -141,19 +142,19 @@ struct ChunkGrid: View { var cy: CGFloat = 0 for piece in itemPieces { if !piece { - p.addRect(CGRect(x: cx, y: cy, width: boxSize - spacing, height: boxSize - spacing)) + p.addRoundedRect(in: CGRect(x: cx, y: cy, width: boxSize - spacing, height: boxSize - spacing), cornerSize: cornerSize) } cx += boxSize - if cx >= width { + if cx + boxSize > width { cx = 0 cy += boxSize } } } - context.fill(pendingPath, with: .color(Color.gray.opacity(0.3))) - context.fill(completedPath, with: .color(Color.green)) + context.fill(pendingPath, with: .color(Color.primary.opacity(0.08))) + context.fill(completedPath, with: .color(Color.accentColor)) } - .frame(minHeight: 80) + .frame(minHeight: 140) } } diff --git a/Sources/Firelink/ContentView.swift b/Sources/Firelink/ContentView.swift index 372e791..25fe883 100644 --- a/Sources/Firelink/ContentView.swift +++ b/Sources/Firelink/ContentView.swift @@ -102,45 +102,34 @@ struct ContentView: View { } } - ToolbarItem { - Button { - controller.startQueue(queueID: queueID) - } label: { - Label("Start Queue", systemImage: "play.fill") - } - } - - ToolbarItem { - Button { - controller.pauseActiveDownloads(queueID: queueID) - } label: { - Label("Stop Downloads", systemImage: "stop.fill") - } - .disabled(!hasActiveDownloads(in: queueID)) - } - ToolbarItemGroup { - if !selectedItems.isEmpty { - if selectedItems.contains(where: { $0.status == .downloading }) { - Button { - for item in selectedItems where item.status == .downloading { - controller.pause(item) - } - } label: { - Label("Stop", systemImage: "stop.fill") - } - } - - if selectedItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) { - Button { - for item in selectedItems where item.status == .paused || item.status == .failed || item.status == .canceled { - controller.resume(item) - } - } label: { - Label("Start", systemImage: "play.fill") + let canStop = selectedItems.isEmpty ? hasActiveDownloads(in: queueID) : selectedItems.contains(where: { $0.status == .downloading }) + Button { + if selectedItems.isEmpty { + controller.pauseActiveDownloads(queueID: queueID) + } else { + for item in selectedItems where item.status == .downloading { + controller.pause(item) } } + } label: { + Label(selectedItems.isEmpty ? "Stop All" : "Stop", systemImage: "stop.fill") } + .disabled(!canStop) + + let canStart = selectedItems.isEmpty ? true : selectedItems.contains(where: { $0.status == .paused || $0.status == .failed || $0.status == .canceled }) + Button { + if selectedItems.isEmpty { + controller.startQueue(queueID: queueID) + } else { + for item in selectedItems where item.status == .paused || item.status == .failed || item.status == .canceled { + controller.resume(item) + } + } + } label: { + Label(selectedItems.isEmpty ? "Start Queue" : "Start", systemImage: "play.fill") + } + .disabled(!canStart) } } .background { diff --git a/Sources/Firelink/DownloadPropertiesView.swift b/Sources/Firelink/DownloadPropertiesView.swift index 33a928d..0085d84 100644 --- a/Sources/Firelink/DownloadPropertiesView.swift +++ b/Sources/Firelink/DownloadPropertiesView.swift @@ -46,6 +46,7 @@ struct DownloadPropertiesView: View { @State private var cookieText: String @State private var mirrorText: String @State private var errorMessage = "" + @State private var showsAdvancedTransfer = false init(item: DownloadItem) { self.item = item @@ -83,11 +84,11 @@ struct DownloadPropertiesView: View { Form { Section("Download") { TextField("URL", text: $urlText) - .font(.system(.body, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) TextField("File name", text: $fileName) HStack { TextField("Save location", text: $destinationPath) - .font(.system(.body, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) Button { selectDestination() } label: { @@ -124,7 +125,7 @@ struct DownloadPropertiesView: View { } } - Section("Advanced Transfer") { + DisclosureGroup("Advanced Transfer", isExpanded: $showsAdvancedTransfer) { Toggle("Checksum", isOn: $checksumEnabled) if checksumEnabled { Picker("Algorithm", selection: $checksumAlgorithm) { @@ -133,24 +134,24 @@ struct DownloadPropertiesView: View { } } TextField("Expected digest", text: $checksumValue) - .font(.system(.body, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) } VStack(alignment: .leading, spacing: 6) { Text("Headers") TextEditor(text: $headerText) - .font(.system(.body, design: .monospaced)) - .frame(minHeight: 70) + .font(.system(.callout, design: .monospaced)) + .frame(minHeight: 60) } TextField("Cookies", text: $cookieText) - .font(.system(.body, design: .monospaced)) + .font(.system(.callout, design: .monospaced)) VStack(alignment: .leading, spacing: 6) { Text("Mirrors") TextEditor(text: $mirrorText) - .font(.system(.body, design: .monospaced)) - .frame(minHeight: 70) + .font(.system(.callout, design: .monospaced)) + .frame(minHeight: 60) } } @@ -187,7 +188,7 @@ struct DownloadPropertiesView: View { .padding(14) .background(.bar) } - .frame(width: 620, height: 760) + .frame(width: 720, height: 720) } private var matchingLoginText: String {