Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions LocalPackage/Sources/DataSource/Entities/AppState.swift
Original file line number Diff line number Diff line change
Expand Up @@ -33,6 +33,7 @@ public struct AppState: Sendable {
public var runnerBundleLists = AsyncStreamBundle<[RunnerBundle]>()
public var runnerBundles = AsyncStreamBundle<RunnerBundle>()
public var runnerSpeeds = AsyncStreamBundle<Float>()
public var runnerPauses = AsyncStreamBundle<Bool>()

init(
name: String = "",
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -22,6 +22,7 @@ extension String {
static let customMetricsConfiguration = "CUSTOM_METRICS_CONFIGURATION"
static let customRunners = "CUSTOM_RUNNERS"
static let isFlippedHorizontally = "IS_FLIPPED_HORIZONTALLY"
static let isRunnerPaused = "IS_RUNNER_PAUSED"
static let metricsBarConfiguration = "METRICS_BAR_CONFIGURATION"
static let runnerID = "RUNNER_ID"
static let speedDecreasesUnderLoad = "SPEED_DECREASES_UNDER_LOAD"
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -38,6 +38,11 @@ public struct UserDefaultsRepository: Sendable {
nonmutating set { userDefaultsClient.set(newValue, .isFlippedHorizontally) }
}

public var isRunnerPaused: Bool {
get { userDefaultsClient.bool(.isRunnerPaused) }
nonmutating set { userDefaultsClient.set(newValue, .isRunnerPaused) }
}

public var updateInterval: UpdateInterval {
get { UpdateInterval(rawValue: userDefaultsClient.integer(.updateInterval)) ?? .default }
nonmutating set { userDefaultsClient.set(newValue.rawValue, .updateInterval) }
Expand Down Expand Up @@ -108,6 +113,7 @@ public struct UserDefaultsRepository: Sendable {
.runnerID: RunnerKind.cat.id,
.speedDecreasesUnderLoad: false,
.isFlippedHorizontally: false,
.isRunnerPaused: false,
.updateInterval: UpdateInterval.default.rawValue,
])
if ProcessInfo.needsShowAllData {
Expand Down
6 changes: 6 additions & 0 deletions LocalPackage/Sources/Model/Services/RunnerService.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,6 +84,12 @@ struct RunnerService {
try update(runner: .default)
}
loadRunnerBundleList()
appStateClient.send(\.runnerPauses, userDefaultsRepository.isRunnerPaused)
}

func updateRunnerPaused(_ isPaused: Bool) {
userDefaultsRepository.isRunnerPaused = isPaused
appStateClient.send(\.runnerPauses, isPaused)
}

func updateRunnerSpeed(from cpuInfo: CPUInfo?) {
Expand Down
10 changes: 10 additions & 0 deletions LocalPackage/Sources/Model/Stores/Dashboard.swift
Original file line number Diff line number Diff line change
Expand Up @@ -30,6 +30,7 @@ public final class Dashboard: Composable {
private let nsWorkspaceClient: NSWorkspaceClient
private let userDefaultsRepository: UserDefaultsRepository
private let logService: LogService
private let runnerService: RunnerService

@ObservationIgnored private var task: Task<Void, Never>?

Expand All @@ -38,6 +39,7 @@ public final class Dashboard: Composable {
public var cpuRingBuffer: RingBuffer
public var memoryRingBuffer: RingBuffer
public var customMetricsBundles: [CustomMetricsBundle]
public var isPaused: Bool
public let isPreview: Bool
public let action: (Action) async -> Void

Expand All @@ -48,6 +50,7 @@ public final class Dashboard: Composable {
cpuRingBuffer: RingBuffer = .init(),
memoryRingBuffer: RingBuffer = .init(),
customMetricsBundles: [CustomMetricsBundle] = [],
isPaused: Bool? = nil,
isPreview: Bool? = nil,
action: @escaping (Action) async -> Void = { _ in }
) {
Expand All @@ -56,11 +59,13 @@ public final class Dashboard: Composable {
self.nsWorkspaceClient = appDependencies.nsWorkspaceClient
self.userDefaultsRepository = .init(appDependencies.userDefaultsClient)
self.logService = .init(appDependencies)
self.runnerService = .init(appDependencies)
self.appName = appName ?? appStateClient.withLock(\.name)
self.systemInfoBundle = systemInfoBundle
self.cpuRingBuffer = cpuRingBuffer
self.memoryRingBuffer = memoryRingBuffer
self.customMetricsBundles = customMetricsBundles
self.isPaused = isPaused ?? userDefaultsRepository.isRunnerPaused
self.isPreview = isPreview ?? ProcessInfo.isPreview
self.action = action
}
Expand Down Expand Up @@ -104,6 +109,10 @@ public final class Dashboard: Composable {
case .reportIssueButtonTapped:
_ = nsWorkspaceClient.open(URL.githubIssues)

case .pauseButtonTapped:
isPaused.toggle()
runnerService.updateRunnerPaused(isPaused)

case .quitButtonTapped:
nsAppClient.terminate(nil)

Expand All @@ -130,6 +139,7 @@ public final class Dashboard: Composable {
case aboutButtonTapped(AttributedString)
case openSourceLicenseButtonTapped(OpenWindowActionWrapper)
case reportIssueButtonTapped
case pauseButtonTapped
case quitButtonTapped
case debugSleepButtonTapped
case debugWakeUpButtonTapped
Expand Down
15 changes: 14 additions & 1 deletion LocalPackage/Sources/Model/Stores/RunnerBar.swift
Original file line number Diff line number Diff line change
Expand Up @@ -82,6 +82,12 @@ public final class RunnerBar: Composable {
self?.update(runnerSpeed: value)
}
}
group.addImmediateTask {
let stream = appStateClient.withLock(\.runnerPauses.stream)
for await value in stream {
self?.update(isPaused: value)
}
}
}
}

Expand Down Expand Up @@ -134,6 +140,10 @@ public final class RunnerBar: Composable {
eventBridge?.setSpeed(runnerSpeed)
}

private func update(isPaused: Bool) {
eventBridge?.setPaused(isPaused)
}

public enum Action: Sendable {
case task(String, EventBridge)
case onDisappear
Expand All @@ -144,19 +154,22 @@ public final class RunnerBar: Composable {
public var setFrames: @MainActor @Sendable ([NSImage], Bool) -> Void
public var setColor: @MainActor @Sendable (CGColor, Bool) -> Void
public var setSpeed: @MainActor @Sendable (Float) -> Void
public var setPaused: @MainActor @Sendable (Bool) -> Void

public init(
getBundleImage: @escaping @MainActor @Sendable (String) -> NSImage,
setSize: @escaping @MainActor @Sendable (CGSize) -> Void,
setFrames: @escaping @MainActor @Sendable ([NSImage], Bool) -> Void,
setColor: @escaping @MainActor @Sendable (CGColor, Bool) -> Void,
setSpeed: @escaping @MainActor @Sendable (Float) -> Void
setSpeed: @escaping @MainActor @Sendable (Float) -> Void,
setPaused: @escaping @MainActor @Sendable (Bool) -> Void
) {
self.getBundleImage = getBundleImage
self.setSize = setSize
self.setFrames = setFrames
self.setColor = setColor
self.setSpeed = setSpeed
self.setPaused = setPaused
}
}
}
Expand Down
132 changes: 132 additions & 0 deletions LocalPackage/Sources/UserInterface/Resources/Localizable.xcstrings
Original file line number Diff line number Diff line change
Expand Up @@ -3565,6 +3565,72 @@
}
}
},
"pauseAnimation" : {
"comment" : "A label for the \"Pause Animation\" menu item.",
"isCommentAutoGenerated" : true,
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Animation pausieren"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pause Animation"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Pausar animación"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Suspendre l’animation"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "アニメーションを一時停止"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "애니메이션 일시정지"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Приостановить анимацию"
}
},
"vi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tạm dừng hoạt ảnh"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "暂停动画"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "暫停動畫"
}
}
}
},
"preview" : {
"comment" : "A label for a preview image.",
"isCommentAutoGenerated" : true,
Expand Down Expand Up @@ -4159,6 +4225,72 @@
}
}
},
"resumeAnimation" : {
"comment" : "A label for the \"Resume Animation\" menu item.",
"isCommentAutoGenerated" : true,
"localizations" : {
"de" : {
"stringUnit" : {
"state" : "translated",
"value" : "Animation fortsetzen"
}
},
"en" : {
"stringUnit" : {
"state" : "translated",
"value" : "Resume Animation"
}
},
"es" : {
"stringUnit" : {
"state" : "translated",
"value" : "Reanudar animación"
}
},
"fr" : {
"stringUnit" : {
"state" : "translated",
"value" : "Reprendre l’animation"
}
},
"ja" : {
"stringUnit" : {
"state" : "translated",
"value" : "アニメーションを再開"
}
},
"ko" : {
"stringUnit" : {
"state" : "translated",
"value" : "애니메이션 재생"
}
},
"ru" : {
"stringUnit" : {
"state" : "translated",
"value" : "Продолжить анимацию"
}
},
"vi" : {
"stringUnit" : {
"state" : "translated",
"value" : "Tiếp tục hoạt ảnh"
}
},
"zh-Hans" : {
"stringUnit" : {
"state" : "translated",
"value" : "继续动画"
}
},
"zh-Hant" : {
"stringUnit" : {
"state" : "translated",
"value" : "繼續動畫"
}
}
}
},
"runnerGalleryDescription" : {
"comment" : "A link to the runner gallery.",
"isCommentAutoGenerated" : true,
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -35,6 +35,7 @@ struct DashboardView: View {
MenuView(
appName: store.appName,
isPreview: store.isPreview,
isPaused: store.isPaused,
buttonTapped: { action in
await store.send(action)
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -26,6 +26,7 @@ struct MenuView: View {
@Environment(\.openWindow) private var openWindow
var appName: String
var isPreview: Bool
var isPaused: Bool
var buttonTapped: (Dashboard.Action) async -> Void

private var aboutBody: AttributedString {
Expand Down Expand Up @@ -67,6 +68,25 @@ struct MenuView: View {
Image(.activityMonitor)
}
}
Button {
Task {
await buttonTapped(.pauseButtonTapped)
}
} label: {
if isPaused {
Label {
Text("resumeAnimation", bundle: .module)
} icon: {
Image(systemName: "play.fill")
}
} else {
Label {
Text("pauseAnimation", bundle: .module)
} icon: {
Image(systemName: "pause.fill")
}
}
}
Divider()
Button {
Task {
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -65,7 +65,8 @@ struct RunnerBarView: View {
setSize: { backgroundLayer.setSize($0) },
setFrames: { runnerLayer.setFrames($0, $1) },
setColor: { runnerLayer.setColor($0, $1) },
setSpeed: { runnerLayer.setSpeed($0) }
setSpeed: { runnerLayer.setSpeed($0) },
setPaused: { runnerLayer.setPaused($0) }
)
))
}
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -25,6 +25,8 @@ final class RunnerLayer: CALayer {
private var width = CGFloat.zero
private let maskLayer = CALayer()
private var keyFrameAnimation = CAKeyframeAnimation(keyPath: "contents")
private var lastSpeed = Float(1.0)
private var isPaused = false

init(gap: CGFloat) {
self.gap = gap
Expand Down Expand Up @@ -69,6 +71,7 @@ final class RunnerLayer: CALayer {
add(keyFrameAnimation, forKey: "running")
}
CATransaction.commit()
applySpeed(isPaused ? .zero : lastSpeed)
}

func setColor(_ tintColor: CGColor, _ isTemplate: Bool) {
Expand All @@ -79,6 +82,18 @@ final class RunnerLayer: CALayer {
}

func setSpeed(_ speed: Float) {
lastSpeed = speed
guard !isPaused else { return }
applySpeed(speed)
}

func setPaused(_ paused: Bool) {
guard paused != isPaused else { return }
isPaused = paused
applySpeed(paused ? .zero : lastSpeed)
}

private func applySpeed(_ speed: Float) {
CATransaction.begin()
CATransaction.setDisableActions(true)
if mask == nil {
Expand Down
Loading