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

Remove StreamDeckView and macro #42

Merged
merged 4 commits into from
Sep 23, 2024
Merged
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
11 changes: 11 additions & 0 deletions Changelog.md
Original file line number Diff line number Diff line change
Expand Up @@ -5,6 +5,17 @@ All notable changes to this project will be documented in this file.
The format is based on [Keep a Changelog](http://keepachangelog.com/)
and this project adheres to [Semantic Versioning](http://semver.org/).

## [1.1.0] - 2024-09-23

Removes the need for a macro.

You can now declare your Stream Deck layouts as regular Swift UI views. There is no need to implement `StreamDeckView` anymore.

### Removed

- The dependency on [Stream Deck Kit - Macros](https://github.com/elgatosf/streamdeck-kit-macros)
- The `StreamDeckView` protocol

## [1.0.0] - 2024-08-22

This is the first official release. 🎉
Expand Down
9 changes: 4 additions & 5 deletions Documentation/GettingStarted.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,7 @@ If you want to add it to your own libraries `Package.swift`, use this code inste

```swift
dependencies: [
.package(url: "https://github.com/elgatosf/streamdeck-kit-ipad.git", upToNextMajor: "1.0.0")
.package(url: "https://github.com/elgatosf/streamdeck-kit-ipad.git", upToNextMajor: "1.1.0")
]
```

Expand All @@ -50,15 +50,14 @@ This code snippet demonstrates rendering a blue color across all buttons and dis



To render content on specific areas, utilize the [Stream Deck Layout](Layout/README.md) system. `StreamDeckLayout` with the `@StreamDeckView` Macro provides predefined layout views to position content on a Stream Deck.
To render content on specific areas, utilize the [Stream Deck Layout](Layout/README.md) system. `StreamDeckLayout` provides predefined layout views to position content on a Stream Deck.

```swift
import SwiftUI
import StreamDeckKit

@StreamDeckView
struct MyFirstStreamDeckLayout {
var streamDeckBody: some View {
struct MyFirstStreamDeckLayout: View {
var body: some View {
StreamDeckLayout {
// Define key area
// Use StreamDeckKeyAreaLayout for rendering separate keys
Expand Down
33 changes: 18 additions & 15 deletions Documentation/Layout/Animated.md
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
# Basic animations

As described in [Handling state changes](./Stateful.md), the `StreamDeckLayout` combined with the `@StreamDeckView` Macro is used to automatically update the image rendered on your Stream Deck Device on view state changes.
As described in [Handling state changes](./Stateful.md), the `StreamDeckLayout` is used to automatically update the image rendered on your Stream Deck Device on view state changes.

Due to the underlying transformation of an SwiftUI view to an image that can be rendered on your Stream Deck device, SwiftUI's animations do not work as you might expect. However, the following example shows how you can create animations regardless, leveraging incremental state changes over time.

Expand All @@ -19,10 +19,11 @@ For Stream Deck +, this layout will be rendered and react to interactions as fol
import StreamDeckKit
import SwiftUI

@StreamDeckView
struct AnimatedStreamDeckLayout {

var streamDeckBody: some View {
@Environment(\.streamDeckViewContext.device) var streamDeck

var body: some View {
StreamDeckLayout {
StreamDeckKeyAreaLayout { _ in
// To react to state changes within each StreamDeckKeyView, extract the view, just as you normally would in SwiftUI
Expand All @@ -42,19 +43,20 @@ struct AnimatedStreamDeckLayout {
}
}

@StreamDeckView
struct MyKeyView {
struct MyKeyView: View {

@State private var isPressed: Bool?
@State private var scale: CGFloat = 1.0
@State private var rotationDegree: Double = .zero

@Environment(\.streamDeckViewContext.index) var viewIndex

var streamDeckBody: some View {
var body: some View {
StreamDeckKeyView { pressed in
self.isPressed = pressed
} content: {
VStack {
Text("\(viewIndex)") // `viewIndex` is provided by the `@StreamDeckView` macro
Text("\(viewIndex)")
Text(isPressed == true ? "Key down" : "Key up")
}
.scaleEffect(scale) // Update the scale depending on the state
Expand Down Expand Up @@ -103,15 +105,17 @@ struct AnimatedStreamDeckLayout {
}
}

@StreamDeckView
struct MyDialView {
struct MyDialView: View {

@State private var isPressed: Bool?

@State private var position: CGPoint = .zero
@State private var targetPosition: CGPoint?

@Environment(\.streamDeckViewContext.size) var viewSize
@Environment(\.streamDeckViewContext.index) var viewIndex

var streamDeckBody: some View {
var body: some View {
StreamDeckDialView { rotations in
self.position.x += CGFloat(rotations)
} press: { pressed in
Expand Down Expand Up @@ -151,15 +155,14 @@ struct AnimatedStreamDeckLayout {
if isPressed == nil || isPressed == true {
self.position = CGPoint(
x: viewSize.width / 2,
y: viewSize.height / 2 // `viewSize` is provided by the `@StreamDeckView` macro
y: viewSize.height / 2
)
}
}
}
}

@StreamDeckView
struct MyNeoPanelView {

struct MyNeoPanelView: View {

@State private var offset: Double = 0
@State private var targetOffset: Double = 0
Expand All @@ -168,7 +171,7 @@ struct AnimatedStreamDeckLayout {

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

var streamDeckBody: some View {
var body: some View {
// Use StreamDeckNeoPanelLayout for Stream Deck Neo
StreamDeckNeoPanelLayout { touched in
targetOffset -= touched ? 50 : 0
Expand Down
13 changes: 9 additions & 4 deletions Documentation/Layout/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -2,7 +2,7 @@

The `StreamDeckLayout` view is a fundamental component for building layouts for Stream Deck devices using SwiftUI. It provides a way to define the key area view with its keys and window view with its dials for a Stream Deck layout. This layout can be used to draw a customized layout onto a Stream Deck device and to recognize Stream Deck interactions in the SwiftUI way.

A `StreamDeckLayout` combined with the `@StreamDeckView` Macro does the heavy lifting for you by automatically recognizing view updates, and triggering an update of the rendered image on your Stream Deck device.
A `StreamDeckLayout` does the heavy lifting for you by automatically recognizing view updates, and triggering an update of the rendered image on your Stream Deck device.

The general structure of `StreamDeckLayout` is as follows:

Expand Down Expand Up @@ -39,10 +39,15 @@ Here's an example of how to create a basic static `StreamDeckLayout`. For exampl
import SwiftUI
import StreamDeckKit

@StreamDeckView
struct StatelessStreamDeckLayout {
struct StatelessStreamDeckLayout: View {

var streamDeckBody: some View {
// Use the `streamDeckViewContext` environment variable to get information about
// the device, the view size or the current key index.
// Values will be different, depending on which layout level (e.g. window, key
// or background) we are.
@Environment(\.streamDeckViewContext.device) var streamDeck

var body: some View {
StreamDeckLayout {
// Define key area
// Use StreamDeckKeyAreaLayout for rendering separate keys
Expand Down
34 changes: 17 additions & 17 deletions Documentation/Layout/Stateful.md
Original file line number Diff line number Diff line change
@@ -1,8 +1,6 @@
# Handling state changes

As described in [The layout system](./README.md), the `StreamDeckLayout` combined with the `@StreamDeckView` Macro does the heavy lifting for you by automatically recognizing view updates, and triggering an update of the rendered image on your Stream Deck device.

To update your `StreamDeckLayout` on events like key presses or dial rotations, the view that should react to state changes needs to be extracted in its own view, just as you would normally do with SwiftUI. If that view is annotated with the `@StreamDeckView` Macro, context-dependent variables like the `viewIndex` and `viewSize` are available in that view's scope.
As described in [The layout system](./README.md), the `StreamDeckLayout` does the heavy lifting for you by automatically recognizing view updates, and triggering an update of the rendered image on your Stream Deck device.

## Example

Expand All @@ -19,10 +17,11 @@ For Stream Deck +, this layout will be rendered and react to interactions as fol
import StreamDeckKit
import SwiftUI

@StreamDeckView
struct StatefulStreamDeckLayout {

var streamDeckBody: some View {
@Environment(\.streamDeckViewContext.device) var streamDeck

var body: some View {
StreamDeckLayout {
StreamDeckKeyAreaLayout { _ in
// To react to state changes within each StreamDeckKeyView, extract the view, just as you normally would in SwiftUI
Expand All @@ -42,17 +41,18 @@ struct StatefulStreamDeckLayout {
}
}

@StreamDeckView
struct MyKeyView {
struct MyKeyView: View {

@State private var isPressed: Bool = false

@Environment(\.streamDeckViewContext.index) var viewIndex

var streamDeckBody: some View {
var body: some View {
StreamDeckKeyView { pressed in
self.isPressed = pressed
} content: {
VStack {
Text("\(viewIndex)") // `viewIndex` is provided by the `@StreamDeckView` macro
Text("\(viewIndex)")
Text(isPressed ? "Key down" : "Key up")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
Expand All @@ -61,13 +61,14 @@ struct StatefulStreamDeckLayout {
}
}

@StreamDeckView
struct MyDialView {
struct MyDialView: View {

@State private var offset: CGSize = .zero
@State private var scale: CGFloat = 1

@Environment(\.streamDeckViewContext.size) var viewSize

var streamDeckBody: some View {
var body: some View {
StreamDeckDialView { rotations in
self.scale = min(max(scale + CGFloat(rotations) / 10, 0.5), 5)
} press: { pressed in
Expand All @@ -78,7 +79,7 @@ struct StatefulStreamDeckLayout {
} touch: { location in
self.offset = CGSize(
width: location.x - viewSize.width / 2,
height: location.y - viewSize.height / 2 // `viewSize` is provided by the `@StreamDeckView` macro
height: location.y - viewSize.height / 2
)
} content: {
Text("\(viewIndex)")
Expand All @@ -89,16 +90,15 @@ struct StatefulStreamDeckLayout {
}
}
}

@StreamDeckView
struct MyNeoPanelView {

struct MyNeoPanelView: View {

@State private var offset: Double = 0
@State private var date: Date = .now

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

var streamDeckBody: some View {
var body: some View {
// Use StreamDeckNeoPanelLayout for Stream Deck Neo
StreamDeckNeoPanelLayout { touched in
offset -= touched ? 5 : 0
Expand Down
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import StreamDeckKit
import SwiftUI

@StreamDeckView
struct StatelessStreamDeckLayout {
struct StatelessStreamDeckLayout: View {

var streamDeckBody: some View {
@Environment(\.streamDeckViewContext.device) var streamDeck

var body: some View {
StreamDeckLayout {
// Define key area
// Use StreamDeckKeyAreaLayout for rendering separate keys
Expand Down
30 changes: 16 additions & 14 deletions Example/Example App/Examples/2_StatefulStreamDeckLayout.swift
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,11 @@
import StreamDeckKit
import SwiftUI

@StreamDeckView
struct StatefulStreamDeckLayout {
struct StatefulStreamDeckLayout: View {

var streamDeckBody: some View {
@Environment(\.streamDeckViewContext.device) var streamDeck

var body: some View {
StreamDeckLayout {
StreamDeckKeyAreaLayout { _ in
// To react to state changes within each StreamDeckKeyView, extract the view, just as you normally would in SwiftUI
Expand All @@ -31,17 +32,17 @@ struct StatefulStreamDeckLayout {
}
}

@StreamDeckView
struct MyKeyView {
struct MyKeyView: View {

@Environment(\.streamDeckViewContext.index) var viewIndex
@State private var isPressed: Bool = false

var streamDeckBody: some View {
var body: some View {
StreamDeckKeyView { pressed in
self.isPressed = pressed
} content: {
VStack {
Text("\(viewIndex)") // `viewIndex` is provided by the `@StreamDeckView` macro
Text("\(viewIndex)")
Text(isPressed ? "Key down" : "Key up")
}
.frame(maxWidth: .infinity, maxHeight: .infinity)
Expand All @@ -50,13 +51,15 @@ struct StatefulStreamDeckLayout {
}
}

@StreamDeckView
struct MyDialView {
struct MyDialView: View {

@Environment(\.streamDeckViewContext.index) var viewIndex
@Environment(\.streamDeckViewContext.size) var viewSize

@State private var offset: CGSize = .zero
@State private var scale: CGFloat = 1

var streamDeckBody: some View {
var body: some View {
StreamDeckDialView { rotations in
self.scale = min(max(scale + CGFloat(rotations) / 10, 0.5), 5)
} press: { pressed in
Expand All @@ -67,7 +70,7 @@ struct StatefulStreamDeckLayout {
} touch: { location in
self.offset = CGSize(
width: location.x - viewSize.width / 2,
height: location.y - viewSize.height / 2 // `viewSize` is provided by the `@StreamDeckView` macro
height: location.y - viewSize.height / 2
)
} content: {
Text("\(viewIndex)")
Expand All @@ -79,15 +82,14 @@ struct StatefulStreamDeckLayout {
}
}

@StreamDeckView
struct MyNeoPanelView {
struct MyNeoPanelView: View {

@State private var offset: Double = 0
@State private var date: Date = .now

let timer = Timer.publish(every: 1, on: .main, in: .common).autoconnect()

var streamDeckBody: some View {
var body: some View {
// Use StreamDeckNeoPanelLayout for Stream Deck Neo
StreamDeckNeoPanelLayout { touched in
offset -= touched ? 5 : 0
Expand Down
Loading