Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Feature/macro #22

Merged
merged 17 commits into from
Feb 22, 2024
Merged
Show file tree
Hide file tree
Changes from 9 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
4 changes: 2 additions & 2 deletions Example/Example App/StreamDeckLayoutView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -26,8 +26,8 @@ struct StreamDeckLayoutView: View {
}
}

struct NumberDisplayKey: StreamDeckView {
@Environment(\.streamDeckViewContext) var context
@StreamDeckView
struct NumberDisplayKey {
@State var isPressed: Bool = false

var emoji: String { emojis[context.index] }
Expand Down
Original file line number Diff line number Diff line change
@@ -0,0 +1,14 @@
{
"pins" : [
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "64889f0c732f210a935a0ad7cda38f77f876262d",
"version" : "509.1.1"
}
}
],
"version" : 2
}
22 changes: 18 additions & 4 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -2,10 +2,11 @@
// The swift-tools-version declares the minimum version of Swift required to build this package.

import PackageDescription
import CompilerPluginSupport

let package = Package(
name: "StreamDeckKit",
platforms: [.iOS(.v17)],
platforms: [.iOS(.v17), .macOS(.v10_15)],
products: [
.library(
name: "StreamDeckKit",
Expand All @@ -20,7 +21,11 @@ let package = Package(
.package(
url: "https://github.com/pointfreeco/swift-snapshot-testing",
from: "1.12.0"
)
),
.package(
url: "https://github.com/apple/swift-syntax.git",
from: "509.0.0"
)
],
targets: [
.target(
Expand All @@ -30,18 +35,27 @@ let package = Package(
),
.target(
name: "StreamDeckKit",
dependencies: ["StreamDeckCApi"]
dependencies: ["StreamDeckCApi", "StreamDeckMacro"]
),
.target(
name: "StreamDeckCApi",
linkerSettings: [.linkedFramework("IOKit")]
),
.macro(
name: "StreamDeckMacro",
dependencies: [
.product(name: "SwiftSyntaxMacros", package: "swift-syntax"),
.product(name: "SwiftCompilerPlugin", package: "swift-syntax")
]
),
.testTarget(
name: "StreamDeckSDKTests",
dependencies: [
"StreamDeckKit",
"StreamDeckMacro",
"StreamDeckSimulator",
.product(name: "SnapshotTesting", package: "swift-snapshot-testing")
.product(name: "SnapshotTesting", package: "swift-snapshot-testing"),
.product(name: "SwiftSyntaxMacrosTestSupport", package: "swift-syntax")
]
)
]
Expand Down
4 changes: 2 additions & 2 deletions Sources/StreamDeckKit/Layout/Environment+Ext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,9 +7,9 @@

import SwiftUI

public struct StreamDeckViewContextKey: EnvironmentKey {
struct StreamDeckViewContextKey: EnvironmentKey {

public static var defaultValue: StreamDeckViewContext = .init(
static var defaultValue: StreamDeckViewContext = .init(
device: StreamDeck(
client: StreamDeckClientDummy(),
info: .init(),
Expand Down
19 changes: 0 additions & 19 deletions Sources/StreamDeckKit/Layout/StreamDeck+Layout.swift

This file was deleted.

21 changes: 5 additions & 16 deletions Sources/StreamDeckKit/Layout/StreamDeckLayoutRenderer.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,24 +13,17 @@ final class StreamDeckLayoutRenderer {

private var cancellable: AnyCancellable?

private let imageSubject = PassthroughSubject<UIImage, Never>()

public var imagePublisher: AnyPublisher<UIImage, Never> {
imageSubject.eraseToAnyPublisher()
}

private var dirtyViews = [DirtyMarker]()

public init() {
}
init() {}

@MainActor
public init<Content: View>(content: Content, device: StreamDeck) {
init<Content: View>(content: Content, device: StreamDeck) {
render(content, on: device)
}

@MainActor
public func render<Content: View>(_ content: Content, on device: StreamDeck) {
func render<Content: View>(_ content: Content, on device: StreamDeck) {
cancellable?.cancel()

dirtyViews = .init([.screen])
Expand All @@ -39,9 +32,7 @@ final class StreamDeckLayoutRenderer {
device: device,
dirtyMarker: .screen,
size: device.capabilities.screenSize ?? .zero
) { [weak self] in
self?.updateRequired($0)
}
)

let view = content
.environment(\.streamDeckViewContext, context)
Expand All @@ -58,7 +49,7 @@ final class StreamDeckLayoutRenderer {
}
}

public func stop() {
func stop() {
cancellable?.cancel()
}

Expand All @@ -72,8 +63,6 @@ final class StreamDeckLayoutRenderer {
print("!!! Layout did change")
let caps = device.capabilities

imageSubject.send(image)

guard !dirtyViews.isEmpty else {
print("!!! no dirty views")
return
Expand Down
34 changes: 4 additions & 30 deletions Sources/StreamDeckKit/Layout/StreamDeckViewContext.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,31 +8,11 @@
import Foundation

/// Provides information about the current context (screen, key-area, key, window, dial) in SwiftUI environments.
///
/// You can access the current context via the environment like this:
/// ```swift
/// @Environment(\.streamDeckViewContext) var context
/// ```
public struct StreamDeckViewContext {

private final class IDGenerator {
private var _id: UInt64 = 0
var next: UInt64 {
if _id == UInt64.max {
_id = 0
}
_id += 1
return _id
}
}

typealias DirtyHandler = (DirtyMarker) -> Void

/// The Stream Deck device object.
public let device: StreamDeck

private(set) var dirtyMarker: DirtyMarker

/// The size of the current drawing area.
///
/// Depending on if you access this value in a key area, a window or a key.
Expand All @@ -43,29 +23,23 @@ public struct StreamDeckViewContext {
/// The value will be valid, when the current drawing area represents an input element like a key. Otherwise it will be `-1`.
public private(set) var index: Int

private let onDirty: DirtyHandler?

private let idGenerator = IDGenerator()

public var nextID: UInt64 { idGenerator.next }
private(set) var dirtyMarker: DirtyMarker

init(
device: StreamDeck,
dirtyMarker: DirtyMarker,
size: CGSize,
index: Int = -1,
onDirty: StreamDeckViewContext.DirtyHandler? = nil
index: Int = -1
) {
self.device = device
self.dirtyMarker = dirtyMarker
self.size = size
self.index = index
self.onDirty = onDirty
}

@MainActor
func updateRequired() {
onDirty?(dirtyMarker)
public func updateRequired() {
device.renderer.updateRequired(dirtyMarker)
}

func with(dirtyMarker: DirtyMarker, size: CGSize, index: Int) -> Self {
Expand Down
13 changes: 13 additions & 0 deletions Sources/StreamDeckKit/Models/StreamDeck.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,6 +7,7 @@

import Combine
import Foundation
import SwiftUI
import UIKit

/// An object that represents a physical Stream Deck device.
Expand All @@ -27,6 +28,8 @@ public final class StreamDeck {
var operationsTask: Task<Void, Never>?
var didSetInputEventHandler = false

let renderer = StreamDeckLayoutRenderer()

private let inputEventsSubject = PassthroughSubject<InputEvent, Never>()

/// A publisher of user input events.
Expand Down Expand Up @@ -68,7 +71,10 @@ public final class StreamDeck {
self.client = client
self.info = info
self.capabilities = capabilities

startOperationTask()

onClose(renderer.stop)
}

/// Check if the hardware supports the given feature.
Expand Down Expand Up @@ -174,4 +180,11 @@ public final class StreamDeck {
enqueueOperation(.showLogo)
}

/// Render the provided content on this device as long as the device remains open.
/// - Parameter content: The SwiftUI view to render on this device.
@MainActor
func render<Content: View>(_ content: Content) {
renderer.render(content, on: self)
}

}
30 changes: 18 additions & 12 deletions Sources/StreamDeckKit/Views/StreamDeckView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -7,15 +7,27 @@

import SwiftUI

private var _id: UInt64 = 0

public var _nextID: UInt64 {
if _id == UInt64.max {
_id = 0
}
_id += 1
return _id
}

/// Protocol for views rendered on StreamDeck.
/// This automatically tells StreamDeckLayout that the drawing area of this view needs to be updated on the device.
///
/// - Note: Use this implicitly by applying the ``StreamDeckView()`` macro.
///
/// ```swift
/// struct NumberDisplayKey: StreamDeckView {
/// @Environment(\.streamDeckViewContext) var context
/// @StreamDeckView
/// struct NumberDisplayKey {
/// @State var isPressed: Bool = false
///
/// var body: some View {
/// var streamDeckBody: some View {
/// StreamDeckKeyView { isPressed in
/// self.isPressed = isPressed
/// } content: {
Expand All @@ -32,12 +44,6 @@ public protocol StreamDeckView: View {
@MainActor @ViewBuilder var streamDeckBody: Self.Content { get }
Copy link
Collaborator

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please also add documentation for streamDeckBody and context properties.

}

extension StreamDeckView {
@MainActor
public var body: some View {
streamDeckBody
.onChange(of: context.nextID) { _, _ in
context.updateRequired()
}
}
}
@attached(extension, conformances: StreamDeckView)
@attached(member, names: named(context), named(body))
public macro StreamDeckView() = #externalMacro(module: "StreamDeckMacro", type: "StreamDeckMacro")
Loading