Skip to content

Commit

Permalink
Add support for new window and fill key API
Browse files Browse the repository at this point in the history
  • Loading branch information
beyama committed Feb 6, 2024
1 parent 0046bf5 commit 5c737d2
Show file tree
Hide file tree
Showing 32 changed files with 570 additions and 369 deletions.
2 changes: 1 addition & 1 deletion Example/Example App/StreamDeckHandler.swift
Original file line number Diff line number Diff line change
Expand Up @@ -120,7 +120,7 @@ extension StreamDeck {
guard let keySize = capabilities.keySize else { return }
set(
view: DeckKeyView(index: index, size: keySize, background: isPressed ? .green : .gray),
to: index
at: index
)
}
}
Expand Down
30 changes: 18 additions & 12 deletions Sources/StreamDeckCApi/include/StreamDeckDriverShared.h
Original file line number Diff line number Diff line change
Expand Up @@ -18,9 +18,11 @@ typedef enum {
SDExternalMethod_setBrightness = 2,
SDExternalMethod_subscribeToKeyActions = 3,
SDExternalMethod_setKeyImage = 4,
SDExternalMethod_setFullscreenImage = 5,
SDExternalMethod_setImageOnXY = 6,
SDExternalMethod_fillDisplay = 7,
SDExternalMethod_setScreenImage = 5,
SDExternalMethod_setWindowImage = 6,
SDExternalMethod_setWindowImageAtXY = 7,
SDExternalMethod_fillScreen = 8,
SDExternalMethod_fillKey = 9,
SDNumberOfExternalMethods // Has to be last
} SDExternalMethod;

Expand Down Expand Up @@ -52,12 +54,12 @@ typedef struct SDDeviceCapabilities {
uint8_t keyRows;
uint8_t keyColumns;
uint8_t dialCount;
uint16_t displayWidth;
uint16_t displayHeight;
uint16_t touchDisplayX;
uint16_t touchDisplayY;
uint16_t touchDisplayWidth;
uint16_t touchDisplayHeight;
uint16_t screenWidth;
uint16_t screenHeight;
uint16_t windowX;
uint16_t windowY;
uint16_t windowWidth;
uint16_t windowHeight;
uint16_t keyAreaX;
uint16_t keyAreaY;
uint16_t keyAreaWidth;
Expand All @@ -66,9 +68,13 @@ typedef struct SDDeviceCapabilities {
uint16_t keyVerticalSpacing;
affine_t imageTransform;
SDImageFormat imageFormat;
bool hasSetFullscreenImageSupport;
bool hasSetImageOnXYSupport;
bool hasFillDisplaySupport;
bool hasSetBrightnessSupport;
bool hasSetKeyImageSupport;
bool hasSetScreenImageSupport;
bool hasSetWindowImageSupport;
bool hasSetWindowImageAtXYSupport;
bool hasFillScreenSupport;
bool hasFillKeySupport;
} SDDeviceCapabilities;

typedef enum {
Expand Down
22 changes: 10 additions & 12 deletions Sources/StreamDeckKit/Extensions/DeviceCapabilities+Rect.swift
Original file line number Diff line number Diff line change
Expand Up @@ -18,20 +18,20 @@ extension DeviceCapabilities {
}

public var keyAreaTrailingSpacing: CGFloat {
guard let displayWidth = displaySize?.width,
guard let screenWidth = screenSize?.width,
let keyAreaWidth = keyAreaRect?.width
else { return 0 }

return displayWidth - (keyAreaLeadingSpacing + keyAreaWidth)
return screenWidth - (keyAreaLeadingSpacing + keyAreaWidth)
}

public var keyAreaBottomSpacing: CGFloat {
guard let displayHeight = displaySize?.height,
guard let screenHeight = screenSize?.height,
let keyAreaHeight = keyAreaRect?.height,
let touchDisplayHeight = touchDisplayRect?.height
let windowHeight = windowRect?.height
else { return 0 }

return displayHeight - (keyAreaTopSpacing + keyAreaHeight + touchDisplayHeight)
return screenHeight - (keyAreaTopSpacing + keyAreaHeight + windowHeight)
}

public func getKeyRect(_ key: Int) -> CGRect {
Expand All @@ -49,27 +49,25 @@ extension DeviceCapabilities {
}

public func getTouchAreaSectionDeviceRect(_ section: Int) -> CGRect {
guard let touchDisplayRect = touchDisplayRect else { return .zero }
guard dialCount > 0, let windowRect = windowRect else { return .zero }

let sectionWidth = Int(touchDisplayRect.width) / dialCount
let sectionWidth = Int(windowRect.width) / dialCount
return .init(
x: sectionWidth * section,
y: 0,
width: sectionWidth,
height: Int(touchDisplayRect.height)
height: Int(windowRect.height)
)
}

public func getTouchAreaSectionRect(_ section: Int) -> CGRect {
let rect = getTouchAreaSectionDeviceRect(section)

guard !rect.isEmpty,
let touchDisplayRect = touchDisplayRect
else { return .zero }
guard !rect.isEmpty, let windowRect = windowRect else { return .zero }

return .init(
x: rect.origin.x,
y: touchDisplayRect.origin.y,
y: windowRect.origin.y,
width: rect.width,
height: rect.height
)
Expand Down
138 changes: 104 additions & 34 deletions Sources/StreamDeckKit/Extensions/StreamDeck+OperationQueue.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,16 +12,19 @@ extension StreamDeck {
enum Operation {
case setInputEventHandler(InputEventHandler)
case setBrightness(Int)
case setImageOnKey(image: UIImage, key: Int, scaleAspectFit: Bool)
case setFullscreenImage(image: UIImage, scaleAspectFit: Bool)
case setTouchAreaImage(image: UIImage, at: CGRect, scaleAspectFit: Bool)
case fillDisplay(color: UIColor)
case setKeyImage(image: UIImage, key: Int, scaleAspectFit: Bool)
case setScreenImage(image: UIImage, scaleAspectFit: Bool)
case setWindowImage(image: UIImage, scaleAspectFit: Bool)
case setWindowImageAt(image: UIImage, at: CGRect, scaleAspectFit: Bool)
case fillScreen(color: UIColor)
case fillKey(color: UIColor, key: Int)
case task(() async -> Void)
case close

var isDrawingOperation: Bool {
switch self {
case .setImageOnKey, .setFullscreenImage, .setTouchAreaImage, .fillDisplay:
case .setKeyImage, .setScreenImage, .setWindowImage,
.setWindowImageAt, .fillScreen, .fillKey:
return true
default: return false
}
Expand All @@ -47,21 +50,42 @@ extension StreamDeck {
case .setInputEventHandler, .setBrightness, .task:
break

case let .setImageOnKey(_, key, _):
case let .setKeyImage(_, key, _):
wasReplaced = operationsQueue.replaceFirst { pending in
if case let .setImageOnKey(_, pendingKey, _) = pending, key == pendingKey {
if case let .setKeyImage(_, pendingKey, _) = pending, key == pendingKey {
return operation
} else if case let .fillKey(_, pendingKey) = pending, key == pendingKey {
return operation
} else {
return nil
}
}

case .setFullscreenImage, .fillDisplay:
case .setScreenImage, .fillScreen:
operationsQueue.removeAll(where: \.isDrawingOperation)

case let .setTouchAreaImage(_, rect, _):
case .setWindowImage:
wasReplaced = operationsQueue.replaceFirst { pending in
switch pending {
case .setWindowImage, .setWindowImageAt: return operation
default: return nil
}
}

case let .setWindowImageAt(_, rect, _):
wasReplaced = operationsQueue.replaceFirst { pending in
if case let .setWindowImageAt(_, pendingRect, _) = pending, rect.contains(pendingRect) {
return operation
} else {
return nil
}
}

case let .fillKey(_, key):
wasReplaced = operationsQueue.replaceFirst { pending in
if case let .setTouchAreaImage(_, pendingRect, _) = pending, rect.contains(pendingRect) {
if case let .fillKey(_, pendingKey) = pending, key == pendingKey {
return operation
} else if case let .setKeyImage(_, pendingKey, _) = pending, key == pendingKey {
return operation
} else {
return nil
Expand All @@ -88,50 +112,79 @@ extension StreamDeck {
}

case let .setBrightness(brightness):
guard capabilities.hasSetBrightnessSupport else { return }
client.setBrightness(min(max(brightness, 0), 100))

case let .setImageOnKey(image, key, scaleAspectFit):
guard let keySize = capabilities.keySize,
case let .setKeyImage(image, key, scaleAspectFit):
guard capabilities.hasSetKeyImageSupport,
let keySize = capabilities.keySize,
let data = transform(image, size: keySize, scaleAspectFit: scaleAspectFit)
else { return }

client.setImage(data, toButtonAt: key)
client.setKeyImage(data, at: key)

case let .setFullscreenImage(image, scaleAspectFit):
guard let displaySize = capabilities.displaySize else { return }
case let .setScreenImage(image, scaleAspectFit):
guard let displaySize = capabilities.screenSize else { return }

if capabilities.hasSetFullscreenImageSupport {
if capabilities.hasSetScreenImageSupport {
guard let data = transform(image, size: displaySize, scaleAspectFit: scaleAspectFit)
else { return }

client.setFullscreenImage(data)
client.setScreenImage(data)
} else {
fakeSetFullscreenImage(image, scaleAspectFit: scaleAspectFit)
fakeSetScreenImage(image, scaleAspectFit: scaleAspectFit)
}

case let .setTouchAreaImage(image, rect, scaleAspectFit):
guard capabilities.hasSetImageOnXYSupport,
case let .setWindowImage(image, scaleAspectFit):
guard capabilities.hasSetWindowImageSupport,
let size = capabilities.windowRect?.size,
let data = transform(image, size: size, scaleAspectFit: scaleAspectFit)
else { return }

client.setWindowImage(data)

case let .setWindowImageAt(image, rect, scaleAspectFit):
guard capabilities.hasSetWindowImageAtXYSupport,
let data = transform(image, size: rect.size, scaleAspectFit: scaleAspectFit)
else { return }

client.setImage(data, x: Int(rect.origin.x), y: Int(rect.origin.y), w: Int(rect.width), h: Int(rect.height))
client.setWindowImage(data, at: rect)

case let .fillDisplay(color):
if capabilities.hasFillDisplaySupport {
case let .fillScreen(color):
if capabilities.hasFillScreenSupport {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0

color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

client.fillDisplay(
client.fillScreen(
red: UInt8(min(255 * red, 255)),
green: UInt8(min(255 * green, 255)),
blue: UInt8(min(255 * blue, 255))
)
} else {
fakeFillDisplay(color)
fakeFillScreen(color)
}

case let .fillKey(color, index):
if capabilities.hasFillKeySupport {
var red: CGFloat = 0
var green: CGFloat = 0
var blue: CGFloat = 0
var alpha: CGFloat = 0

color.getRed(&red, green: &green, blue: &blue, alpha: &alpha)

client.fillKey(
red: UInt8(min(255 * red, 255)),
green: UInt8(min(255 * green, 255)),
blue: UInt8(min(255 * blue, 255)),
at: index
)
} else {
fakeFillKey(color, at: index)
}

case let .task(task):
Expand All @@ -155,20 +208,21 @@ extension StreamDeck {
// MARK: Emulated Stream Deck hardware functions
private extension StreamDeck {

func fakeSetFullscreenImage(_ image: UIImage, scaleAspectFit: Bool = true) {
guard let displaySize = capabilities.displaySize,
func fakeSetScreenImage(_ image: UIImage, scaleAspectFit: Bool = true) {
guard capabilities.hasSetKeyImageSupport,
let screenSize = capabilities.screenSize,
let keySize = capabilities.keySize
else { return }

let newImage: UIImage
if image.size == displaySize {
if image.size == screenSize {
newImage = image
} else {
let format = UIGraphicsImageRendererFormat(for: .init(displayScale: 1))
let renderer = UIGraphicsImageRenderer(size: displaySize, format: format)
let renderer = UIGraphicsImageRenderer(size: screenSize, format: format)
let drawingAction = Self.transformDrawingAction(
image: image,
size: displaySize,
size: screenSize,
transform: .identity,
scaleAspectFit: scaleAspectFit
)
Expand All @@ -187,12 +241,14 @@ private extension StreamDeck {
guard let data = transform(keyImage, size: keySize, scaleAspectFit: false)
else { return }

client.setImage(data, toButtonAt: index)
client.setKeyImage(data, at: index)
}
}

func fakeFillDisplay(_ color: UIColor) {
guard let keySize = capabilities.keySize else { return }
func fakeFillScreen(_ color: UIColor) {
guard capabilities.hasSetKeyImageSupport,
let keySize = capabilities.keySize
else { return }

let format = UIGraphicsImageRendererFormat(for: .init(displayScale: 1))
let renderer = UIGraphicsImageRenderer(size: keySize, format: format)
Expand All @@ -204,7 +260,21 @@ private extension StreamDeck {
else { return }

for index in 0 ..< capabilities.keyCount {
client.setImage(data, toButtonAt: index)
client.setKeyImage(data, at: index)
}
}

func fakeFillKey(_ color: UIColor, at index: Int) {
guard capabilities.hasSetKeyImageSupport,
let keySize = capabilities.keySize,
let image = UIImage.colored(color, size: keySize)
else { return }

guard let data = transform(image, size: keySize, scaleAspectFit: false)
else { return }

for index in 0 ..< capabilities.keyCount {
client.setKeyImage(data, at: index)
}
}

Expand Down
Loading

0 comments on commit 5c737d2

Please sign in to comment.