From 2d3596d5afb3ee11501adec5e18d460548c409b0 Mon Sep 17 00:00:00 2001 From: Alexander Jentz Date: Tue, 27 Feb 2024 16:02:11 +0100 Subject: [PATCH 1/4] Add support for Stream Deck Pedal in StreamDeckLayout --- .../Layout/StreamDeckLayout.swift | 27 +++++++------- .../Layout/StreamDeckLayoutRenderer.swift | 10 ++---- .../Layout/StreamDeckViewContext.swift | 1 + .../Helper/StreamDeckRobot.swift | 7 ++-- .../StreamDeckLayoutTests.swift | 35 +++++++++++++++++++ 5 files changed, 56 insertions(+), 24 deletions(-) diff --git a/Sources/StreamDeckKit/Layout/StreamDeckLayout.swift b/Sources/StreamDeckKit/Layout/StreamDeckLayout.swift index 78b02df0..a75dbdef 100644 --- a/Sources/StreamDeckKit/Layout/StreamDeckLayout.swift +++ b/Sources/StreamDeckKit/Layout/StreamDeckLayout.swift @@ -56,21 +56,20 @@ public struct StreamDeckLayout: View { let caps = context.device.capabilities VStack(alignment: .leading, spacing: 0) { - if let keyAreaSize = caps.keyAreaRect?.size { - let keyAreaContext = context.with( - dirtyMarker: .screen, - size: keyAreaSize, - index: -1 - ) + let keyAreaSize = caps.keyAreaRect?.size ?? .zero + let keyAreaContext = context.with( + dirtyMarker: .screen, + size: keyAreaSize, + index: -1 + ) - keyArea() - .frame(width: keyAreaSize.width, height: keyAreaSize.height) - .padding(.top, caps.keyAreaTopSpacing) - .padding(.leading, caps.keyAreaLeadingSpacing) - .padding(.trailing, caps.keyAreaTrailingSpacing) - .padding(.bottom, caps.keyAreaBottomSpacing) - .environment(\.streamDeckViewContext, keyAreaContext) - } + keyArea() + .frame(width: keyAreaSize.width, height: keyAreaSize.height) + .padding(.top, caps.keyAreaTopSpacing) + .padding(.leading, caps.keyAreaLeadingSpacing) + .padding(.trailing, caps.keyAreaTrailingSpacing) + .padding(.bottom, caps.keyAreaBottomSpacing) + .environment(\.streamDeckViewContext, keyAreaContext) if let windowRect = caps.windowRect { let windowSize = windowRect.size diff --git a/Sources/StreamDeckKit/Layout/StreamDeckLayoutRenderer.swift b/Sources/StreamDeckKit/Layout/StreamDeckLayoutRenderer.swift index b98f6d4b..1804cfbb 100644 --- a/Sources/StreamDeckKit/Layout/StreamDeckLayoutRenderer.swift +++ b/Sources/StreamDeckKit/Layout/StreamDeckLayoutRenderer.swift @@ -37,16 +37,10 @@ final class StreamDeckLayoutRenderer { init() {} - @MainActor - init(content: Content, device: StreamDeck) { - render(content, on: device) - } - @MainActor func render(_ content: Content, on device: StreamDeck) { cancellable?.cancel() - - dirtyViews = .init([.screen]) + dirtyViews = [.screen] let context = StreamDeckViewContext( device: device, @@ -90,7 +84,7 @@ final class StreamDeckLayoutRenderer { defer { dirtyViews.removeAll(keepingCapacity: true) } - log("requires updates of \(Array(dirtyViews))") + log("requires updates of \(dirtyViews)") guard !dirtyViews.contains(.screen) else { log("complete screen required") diff --git a/Sources/StreamDeckKit/Layout/StreamDeckViewContext.swift b/Sources/StreamDeckKit/Layout/StreamDeckViewContext.swift index 20df39ee..c9aa3274 100644 --- a/Sources/StreamDeckKit/Layout/StreamDeckViewContext.swift +++ b/Sources/StreamDeckKit/Layout/StreamDeckViewContext.swift @@ -61,6 +61,7 @@ public struct StreamDeckViewContext { @MainActor public func updateRequired() { + guard size != .zero else { return } // Pedal device.renderer.updateRequired(dirtyMarker) } diff --git a/Tests/StreamDeckSDKTests/Helper/StreamDeckRobot.swift b/Tests/StreamDeckSDKTests/Helper/StreamDeckRobot.swift index a5550766..bd0ed573 100644 --- a/Tests/StreamDeckSDKTests/Helper/StreamDeckRobot.swift +++ b/Tests/StreamDeckSDKTests/Helper/StreamDeckRobot.swift @@ -63,6 +63,7 @@ final class StreamDeckRobot { func use( _ product: StreamDeckProduct, rendering content: Content, + waitForLayout: Bool = true, file: StaticString = #file, line: UInt = #line ) async throws { @@ -70,8 +71,10 @@ final class StreamDeckRobot { await device.render(content) - try await recorder.$screens.waitFor(file: file, line: line) { - !$0.isEmpty + if waitForLayout { + try await recorder.$screens.waitFor(file: file, line: line) { + !$0.isEmpty + } } } diff --git a/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift b/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift index fbb600d4..dc062c87 100644 --- a/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift +++ b/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift @@ -134,4 +134,39 @@ final class StreamDeckLayoutTests: XCTestCase { await robot.assertSnapshot(\.windowImages[section].image, as: .image, named: "section_\(section)") } } + + // MARK: Pedal + + func test_key_events_on_pedal() async throws { + var events = [(index: Int, pressed: Bool)]() + + try await robot.use(.pedal, rendering: StreamDeckLayout(keyArea: { + StreamDeckKeyAreaLayout { context in + StreamDeckKeyView(action: { pressed in + events.append((index: context.index, pressed: pressed)) + }) { EmptyView() } + } + }), waitForLayout: false) + + for key in 0 ..< 3 { + try await robot.keyPress(key, pressed: true, waitForLayout: false) + try await robot.keyPress(key, pressed: false, waitForLayout: false) + } + + await robot.digest() + + robot.assertEqual(\.screens.count, 0) + robot.assertEqual(\.keys.count, 0) + + XCTAssertEqual(events.count, 6) + + for key in 0 ..< 3 { + XCTAssertEqual(events[key * 2].index, key) + XCTAssertEqual(events[key * 2 + 1].index, key) + + XCTAssertTrue(events[key * 2].pressed) + XCTAssertFalse(events[key * 2 + 1].pressed) + } + } + } From 4217f11dcb122eb96efb98e3a4a65cfaf7985231 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christiane=20G=C3=B6hring?= <38751603+zahnooo@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:08:49 +0100 Subject: [PATCH 2/4] Update build-all-targets.yml --- .github/workflows/build-all-targets.yml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/workflows/build-all-targets.yml b/.github/workflows/build-all-targets.yml index da57fb7a..13ec9215 100644 --- a/.github/workflows/build-all-targets.yml +++ b/.github/workflows/build-all-targets.yml @@ -22,4 +22,4 @@ jobs: - name: Build Example run: | cd Example - set -o pipefail && xcodebuild -scheme "Example App" -destination "platform=iOS Simulator,name=iPhone 15,OS=latest" | xcpretty + set -o pipefail && xcodebuild -scheme "StreamDeckKitExample App" -destination "platform=iOS Simulator,name=iPhone 15,OS=latest" | xcpretty From 9cd7036619f1a27404cbcbc4272a6f77b0a2a432 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Christiane=20G=C3=B6hring?= <38751603+zahnooo@users.noreply.github.com> Date: Tue, 27 Feb 2024 16:12:37 +0100 Subject: [PATCH 3/4] Update build-all-targets.yml --- .github/workflows/build-all-targets.yml | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/.github/workflows/build-all-targets.yml b/.github/workflows/build-all-targets.yml index 13ec9215..3b6a1059 100644 --- a/.github/workflows/build-all-targets.yml +++ b/.github/workflows/build-all-targets.yml @@ -17,9 +17,9 @@ jobs: - uses: actions/checkout@v4 - name: Build Simulator - run: set -o pipefail && xcodebuild -scheme StreamDeckSimulator -destination "platform=iOS Simulator,name=iPhone 15,OS=latest" | xcpretty + run: set -o pipefail && xcodebuild -scheme StreamDeckSimulator -destination "platform=iOS Simulator,name=iPad Air (5th generation),OS=latest" | xcpretty - name: Build Example run: | cd Example - set -o pipefail && xcodebuild -scheme "StreamDeckKitExample App" -destination "platform=iOS Simulator,name=iPhone 15,OS=latest" | xcpretty + set -o pipefail && xcodebuild -scheme "StreamDeckKitExample App" -destination "platform=iOS Simulator,name=iPad Air (5th generation),OS=latest" | xcpretty From 9747dcab99db9f9901ce95ad714105c593b23a78 Mon Sep 17 00:00:00 2001 From: Alexander Jentz Date: Wed, 28 Feb 2024 08:34:46 +0100 Subject: [PATCH 4/4] Fix Swiftlint issues --- Sources/StreamDeckSimulator/Views/SimulatorDialView.swift | 2 +- .../Views/StreamDeckSimulator.PreviewView.swift | 2 +- Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift | 4 ++-- 3 files changed, 4 insertions(+), 4 deletions(-) diff --git a/Sources/StreamDeckSimulator/Views/SimulatorDialView.swift b/Sources/StreamDeckSimulator/Views/SimulatorDialView.swift index 81d53d1c..03f45b3a 100644 --- a/Sources/StreamDeckSimulator/Views/SimulatorDialView.swift +++ b/Sources/StreamDeckSimulator/Views/SimulatorDialView.swift @@ -55,7 +55,7 @@ struct SimulatorDialPressButton: View { .updating($isPressed) { _, state, _ in state = true } - + GeometryReader { metrics in let strokeWidth: CGFloat = metrics.size.width * 0.05 Circle() diff --git a/Sources/StreamDeckSimulator/Views/StreamDeckSimulator.PreviewView.swift b/Sources/StreamDeckSimulator/Views/StreamDeckSimulator.PreviewView.swift index 15708856..a94d3973 100644 --- a/Sources/StreamDeckSimulator/Views/StreamDeckSimulator.PreviewView.swift +++ b/Sources/StreamDeckSimulator/Views/StreamDeckSimulator.PreviewView.swift @@ -48,7 +48,7 @@ public extension StreamDeckSimulator { @State private var showDeviceBezels: Bool @State private var showKeyAreaBorders: Bool - + /// Creates an instance of `StreamDeckSimulator`. /// - Parameters: /// - product: The kind of simulator to show. diff --git a/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift b/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift index dc062c87..48c116f5 100644 --- a/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift +++ b/Tests/StreamDeckSDKTests/StreamDeckLayoutTests.swift @@ -142,9 +142,9 @@ final class StreamDeckLayoutTests: XCTestCase { try await robot.use(.pedal, rendering: StreamDeckLayout(keyArea: { StreamDeckKeyAreaLayout { context in - StreamDeckKeyView(action: { pressed in + StreamDeckKeyView { pressed in events.append((index: context.index, pressed: pressed)) - }) { EmptyView() } + } content: { EmptyView() } } }), waitForLayout: false)